"use client"; import { useCallback, useEffect, useMemo, useState } from "react"; import { IconBox, IconCopy, IconLoader, IconRefresh, IconTrash, } from "@tabler/icons-react"; import { Popover, PopoverContent, PopoverTrigger } from "@kandev/ui/popover"; import { Button } from "@kandev/ui/button"; import { toast } from "sonner"; import { fetchTaskEnvironment, resetTaskEnvironment, type TaskEnvironment, } from "@/lib/api/domains/task-environment-api"; import { TaskResetEnvConfirmDialog } from "./task-reset-env-confirm-dialog"; type ExecutorSettingsButtonProps = { taskId?: string | null; disabled?: boolean; }; export function ExecutorSettingsButton({ taskId, disabled }: ExecutorSettingsButtonProps) { const [open, setOpen] = useState(false); const [env, setEnv] = useState(null); const [loading, setLoading] = useState(false); const [resetDialogOpen, setResetDialogOpen] = useState(false); const [isResetting, setIsResetting] = useState(false); const loadEnv = useCallback(async () => { if (!taskId) { setEnv(null); return; } setLoading(true); try { const data = await fetchTaskEnvironment(taskId); setEnv(data); } catch { // 404 when there's no environment yet — normal state, not an error setEnv(null); } finally { setLoading(false); } }, [taskId]); useEffect(() => { if (open) { void loadEnv(); } }, [open, loadEnv]); const handleReset = useCallback( async ({ pushBranch }: { pushBranch: boolean }) => { if (!taskId) return; setIsResetting(true); try { await resetTaskEnvironment(taskId, { push_branch: pushBranch }); toast.success("Environment reset"); setEnv(null); setResetDialogOpen(false); setOpen(false); } catch (err) { const msg = err instanceof Error ? err.message : "Unknown error"; toast.error(`Reset failed: ${msg}`); } finally { setIsResetting(false); } }, [taskId], ); if (!taskId) return null; return ( <>
); } function EnvironmentInfo({ env, loading, }: { env: TaskEnvironment | null; loading: boolean; }) { if (loading && !env) { return (
); } if (!env) { return (

No environment yet

An environment is created when you start a session on this task.

); } return (
{formatExecutorType(env.executor_type)} {env.status}
); } function EnvironmentFields({ env }: { env: TaskEnvironment }) { const fields = useMemo(() => buildFields(env), [env]); if (fields.length === 0) { return

No resource details available.

; } return (
{fields.map((f) => ( ))}
); } function Field({ label, value, copy, }: { label: string; value: string; copy?: boolean; }) { return (
{label}
{value} {copy && ( )}
); } type FieldRow = { label: string; value: string; copy?: boolean }; function buildFields(env: TaskEnvironment): FieldRow[] { const rows: FieldRow[] = []; if (env.worktree_path) { rows.push({ label: "Worktree", value: env.worktree_path, copy: true }); } if (env.worktree_branch) { rows.push({ label: "Branch", value: env.worktree_branch, copy: true }); } if (env.container_id) { rows.push({ label: "Container", value: env.container_id.slice(0, 12), copy: true }); rows.push({ label: "Shell", value: `docker exec -it ${env.container_id.slice(0, 12)} bash`, copy: true, }); } if (env.sandbox_id) { rows.push({ label: "Sprite", value: env.sandbox_id, copy: true }); } if (env.agent_execution_id) { rows.push({ label: "Execution", value: env.agent_execution_id, copy: true }); } rows.push({ label: "Environment ID", value: env.id, copy: true }); return rows; } function formatExecutorType(type: string): string { switch (type) { case "local_pc": case "worktree": return "Local (worktree)"; case "local_docker": return "Local Docker"; case "sprites": return "Sprites sandbox"; case "remote_docker": return "Remote Docker"; default: return type || "Unknown executor"; } }