import { AK_ANNOTATION_KEY_SOURCE_EVENT, AK_ANNOTATION_KEY_SOURCE_URL, AK_LABEL_KEY_GITHUB_SUBJECT } from "@vtit-agent-coding/shared"; import { ArrowLeft, ExternalLink, FileText, KeyRound, MessageSquare, Pencil, RefreshCw, X } from "lucide-react"; import { useEffect, useState } from "react"; import ReactMarkdown from "react-markdown"; import { Link, useParams } from "react-router-dom"; import rehypeHighlight from "rehype-highlight"; import remarkGfm from "remark-gfm"; import { toast } from "sonner"; import { AmaSessionChat } from "../components/ChatPanel"; import { formatRelative } from "../components/TaskDetailFields"; import { Badge } from "../components/ui/badge"; import { Button } from "../components/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "../components/ui/dialog"; import { Label } from "../components/ui/label"; import { Sheet, SheetContent, SheetDescription, SheetTitle } from "../components/ui/sheet"; import { Skeleton } from "../components/ui/skeleton"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../components/ui/table"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "../components/ui/tabs"; import { Textarea } from "../components/ui/textarea"; import { useBoard, useBoardMaintainer, useBoardMaintainerMemories, useBoardMaintainerRuns, useBoardMaintainerSessions, useBoardMaintainerVariables, useUpdateBoardMaintainerVariables, } from "../hooks/useBoard"; interface MaintainerRun { id: string; scheduled_for: string | null; heartbeat_at: string | null; triggered_at: string | null; status: "claimed" | "queued" | "dispatching" | "dispatched" | "failed"; session_id: string | null; error_message: string | null; metadata: Record; created_at?: string; updated_at?: string; } interface MaintainerMemory { id: string; path: string; content: string; metadata: Record; created_at: string; updated_at: string; } interface MaintainerVariable { name: string; } interface MaintainerSession { id: string; title?: string | null; state?: string | null; agentId?: string | null; createdAt?: string | null; startedAt?: string | null; updatedAt?: string | null; stoppedAt?: string | null; metadata?: Record; } interface GithubSubject { event: string | null; action: string | null; repository: string | null; repositoryUrl: string | null; subjectType: "issue" | "pull" | null; subjectNumber: number | null; subjectTitle: string | null; subjectUrl: string | null; } export function MaintainerDetailPage() { const { boardId, maintainerId } = useParams<{ boardId: string; maintainerId: string }>(); const { board, loading: boardLoading } = useBoard(boardId); const { maintainer, loading: maintainerLoading, refresh: refreshMaintainer } = useBoardMaintainer(boardId, maintainerId); const { runs, loading: runsLoading, refresh: refreshRuns } = useBoardMaintainerRuns(boardId, maintainerId); const { sessions, loading: sessionsLoading, refresh: refreshSessions } = useBoardMaintainerSessions(maintainerId); const { memories, loading: memoriesLoading, error: memoriesError, refresh: refreshMemories } = useBoardMaintainerMemories(boardId, maintainerId); const { variables, loading: variablesLoading, error: variablesError, refresh: refreshVariables, } = useBoardMaintainerVariables(boardId, maintainerId); const updateVariables = useUpdateBoardMaintainerVariables(boardId, maintainerId); const [selectedPath, setSelectedPath] = useState(null); const [selectedSession, setSelectedSession] = useState(null); const [variablesDialogOpen, setVariablesDialogOpen] = useState(false); useEffect(() => { if (memories.length === 0) { setSelectedPath(null); return; } if (selectedPath && memories.some((memory: MaintainerMemory) => memory.path === selectedPath)) return; const heartbeat = memories.find((memory: MaintainerMemory) => memory.path === "HEARTBEAT.md"); setSelectedPath((heartbeat ?? memories[0]).path); }, [memories, selectedPath]); if (boardLoading || maintainerLoading) return ; if (!board || !maintainer || !boardId) return ; const selectedMemory = memories.find((memory: MaintainerMemory) => memory.path === selectedPath) ?? null; async function refreshAll() { await Promise.all([refreshMaintainer(), refreshSessions(), refreshRuns(), refreshMemories(), refreshVariables()]); } async function saveVariables(nextVariables: Record) { try { await updateVariables.mutateAsync(nextVariables); setVariablesDialogOpen(false); } catch (error) { toast.error((error as Error).message || "Failed to save variables"); } } return (
Board settings

{board.name}

Board maintainer

{maintainer.status}
{maintainer.last_error_message ? (
{maintainer.last_error_message}
) : null} Sessions {sessions.length} Memory {memories.length} Variables {variables.length} Activity {runs.length} setVariablesDialogOpen(true)} />
!open && setSelectedSession(null)} />
); } function Metric({ label, value }: { label: string; value: string }) { return (
{label}
{value}
); } function SessionsPanel({ sessions, loading, onOpenSession, }: { sessions: MaintainerSession[]; loading: boolean; onOpenSession: (session: MaintainerSession) => void; }) { if (loading) { return (
); } if (sessions.length === 0) { return (

No sessions yet.

); } return (
Session Subject State Last activity {sessions.map((session) => { const github = githubSubjectFromMetadata(session.metadata); return ( {session.state ?? "unknown"} {sessionLastActivity(session) ? formatRelative(sessionLastActivity(session)!) : "unknown"} ); })}
); } function VariablesPanel({ variables, loading, error, onEdit, }: { variables: MaintainerVariable[]; loading: boolean; error: unknown; onEdit: () => void; }) { if (loading) { return (
); } if (error) { return (
{(error as Error).message || "Failed to load variables"}
); } return (

User variables

Stored in the user-variables credential.

{variables.length === 0 ? ( ) : (
Name Value {variables.map((variable) => ( {variable.name} secret ))}
)}
); } function VariablesDialog({ open, variables, saving, onOpenChange, onSave, }: { open: boolean; variables: MaintainerVariable[]; saving: boolean; onOpenChange: (open: boolean) => void; onSave: (variables: Record) => Promise; }) { const [envText, setEnvText] = useState(""); const [error, setError] = useState(null); useEffect(() => { if (!open) return; setEnvText(""); setError(null); }, [open]); async function submit() { try { const nextVariables = parseEnvText(envText); setError(null); await onSave(nextVariables); } catch (err) { setError((err as Error).message || "Invalid variables"); } } return ( User variables Values are write-only. Saving updates the user-variables credential.
{variables.length > 0 ? (
Current keys
{variables.map((variable) => ( {variable.name} ))}
) : null}