import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useEffect, useState } from "react"; import { Button } from "@/components/ui/button"; import { Combobox, ComboboxContent, ComboboxEmpty, ComboboxInput, ComboboxItem, ComboboxList, } from "@/components/ui/combobox"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Skeleton } from "@/components/ui/skeleton"; import { InstructionsEditor } from "@/components/InstructionsEditor"; import { ApiError, api, isGithubReauthError, loginUrl } from "@/lib/api"; import { normalizeRepoFullName } from "@/lib/repo"; function formatMutationError(e: Error): string { return isGithubReauthError(e) ? "GitHub token expired — sign in again using the link above." : e.message; } export function AgentInstructionsPanel() { const qc = useQueryClient(); const [error, setError] = useState(null); const [addRepo, setAddRepo] = useState(""); const [selected, setSelected] = useState(null); const [draft, setDraft] = useState(""); const instructions = useQuery({ queryKey: ["agentInstructions"], queryFn: api.listAgentInstructions, }); const repos = useQuery({ queryKey: ["repos"], queryFn: async () => { try { return await api.repos(); } catch (e) { if (e instanceof ApiError && e.status === 401) return { installations: [], repositories: [] }; throw e; } }, }); const detail = useQuery({ queryKey: ["agentInstruction", selected], queryFn: () => api.getAgentInstructions(selected!), enabled: !!selected, }); useEffect(() => { if (detail.data) setDraft(detail.data.instructions ?? ""); }, [detail.data?.instructions, detail.data?.full_name]); const create = useMutation({ mutationFn: (full_name: string) => api.createAgentInstructions(full_name), onSuccess: (record) => { void qc.invalidateQueries({ queryKey: ["agentInstructions"] }); setSelected(record.full_name); setError(null); }, onError: (e: Error) => setError(formatMutationError(e)), }); const save = useMutation({ mutationFn: ({ full_name, value }: { full_name: string; value: string }) => api.saveAgentInstructions(full_name, value), onSuccess: () => { void qc.invalidateQueries({ queryKey: ["agentInstructions"] }); void qc.invalidateQueries({ queryKey: ["agentInstruction", selected] }); setError(null); }, onError: (e: Error) => setError(formatMutationError(e)), }); const remove = useMutation({ mutationFn: (full_name: string) => api.deleteAgentInstructions(full_name), onSuccess: (_data, full_name) => { void qc.invalidateQueries({ queryKey: ["agentInstructions"] }); if (selected === full_name) { setSelected(null); setDraft(""); } setError(null); }, onError: (e: Error) => setError(formatMutationError(e)), }); if (instructions.isLoading) { return ; } const configured = new Set((instructions.data ?? []).map((s) => s.full_name)); const suggestedRepos = (repos.data?.repositories ?? []).filter( (r) => !configured.has(r.full_name), ); const normalizedAddRepo = normalizeRepoFullName(addRepo); const canAdd = normalizedAddRepo !== null && !configured.has(normalizedAddRepo); const active = detail.data ?? instructions.data?.find((s) => s.full_name === selected) ?? null; const dirty = active != null && draft !== (active.instructions ?? ""); const handleAdd = () => { if (!normalizedAddRepo || !canAdd) return; void create .mutateAsync(normalizedAddRepo) .then(() => setAddRepo("")) .catch(() => undefined); }; const githubReauth = (repos.isError && isGithubReauthError(repos.error)) || (error !== null && /github token|re-login required/i.test(error)); return (
{githubReauth && (
Your GitHub connection expired.{" "} Sign in with GitHub again {" "} to list installed repos.
)}
setAddRepo(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault(); handleAdd(); } }} className="sm:flex-1" />
{suggestedRepos.length > 0 && ( r.full_name)} value={addRepo} onValueChange={(v) => setAddRepo(typeof v === "string" ? v : "")} > No matches {suggestedRepos.map((r) => ( {r.full_name} {r.private && ( private )} ))} )}

Repositories

{(instructions.data ?? []).length === 0 ? (

No repositories yet.

) : (
    {(instructions.data ?? []).map((s) => (
  • ))}
)}
{!selected || !active ? (

Select a repository above to view or edit its custom agent instructions.

) : ( <>

{active.full_name}

{dirty && ( Unsaved changes )}
)} {error &&

{error}

}
); }