import { Icon } from "../Icon"; import { useState } from "react"; import { del, patch } from "../../api"; import { useFetch } from "../../hooks"; import { useT } from "../../i18n"; import type { AgentInfo } from "../../types"; import { ModelSelect } from "../ModelSelect"; import { confirmDialog, promptDialog, toast } from "../../dialogs"; type Patch = { path: string[]; value: unknown }; export interface ToolInfo { name: string; read_only: boolean; description: string; } /** Tool checkboxes: nothing checked = all tools. */ export function ToolPicker({ tools, value, onChange, readOnly }: { tools: ToolInfo[]; value: string[]; onChange: (v: string[]) => void; readOnly: boolean }) { const t = useT(); const toggle = (n: string) => onChange(value.includes(n) ? value.filter((x) => x !== n) : [...value, n]); // Names configured by hand (e.g. MCP patterns) stay selected even if not listed. const extra = value.filter((v) => !tools.some((x) => x.name === v)); return (
{value.length ? t("agents.toolsSome", { n: value.length }) : t("agents.toolsAll")}
{tools.map((x) => ( ))} {extra.map((n) => ( ))}
); } const WRITER_PROMPT = `You are a technical writer. Produce clear, well-structured text (Markdown) for documentation, summaries, step-by-step guides and translations. Return only the finished text.`; function AgentRow({ a, raw, tools, onSaved }: { a: AgentInfo; raw: any; tools: ToolInfo[]; onSaved: (ok: boolean, text: string) => void }) { const t = useT(); const [open, setOpen] = useState(!!raw && !raw.prompt && !raw.description && raw.mode === "primary"); const [form, setForm] = useState({ model: a.model ?? "", max_parallel: a.max_parallel ? String(a.max_parallel) : "", description: raw?.description ?? "", tools: (raw?.tools ?? []) as string[], read_only: !!(raw?.read_only ?? a.read_only), style: (raw?.style ?? a.style ?? "coding") as "coding" | "chat", prompt: raw?.prompt ?? "", mode: raw?.mode ?? a.mode, refine_rounds: String(raw?.refine?.rounds ?? a.refine?.rounds ?? 0), refine_model: raw?.refine?.model ?? "", refine_criteria: raw?.refine?.criteria ?? "", refine_after_todos: (raw?.refine?.after_todos ?? a.refine?.after_todos ?? true) as boolean, reverse_tools: !!(raw?.reverse_tools ?? a.reverse), }); const builtin = a.source === "builtin" || (a.source === "config" && !raw?.prompt && ["build", "plan", "craft", "chat", "general", "explore"].includes(a.name)); const save = async () => { const base = ["agents", a.name]; const patches: Patch[] = [ { path: [...base, "model"], value: form.model || null }, { path: [...base, "max_parallel"], value: form.max_parallel ? Number(form.max_parallel) : null }, ]; if (a.mode === "primary") { const rounds = Math.max(0, Math.min(10, Number(form.refine_rounds) || 0)); patches.push({ path: [...base, "refine"], value: rounds || form.refine_model || form.refine_criteria ? { rounds, model: form.refine_model || null, criteria: form.refine_criteria || null, after_todos: form.refine_after_todos } : null, }); } if (a.name !== "craft") patches.push({ path: [...base, "reverse_tools"], value: form.reverse_tools || null }); if (!builtin || form.description) patches.push({ path: [...base, "description"], value: form.description || null }); if (!builtin) { patches.push({ path: [...base, "mode"], value: form.mode }); patches.push({ path: [...base, "tools"], value: form.tools.length ? form.tools : null }); patches.push({ path: [...base, "read_only"], value: form.read_only || null }); patches.push({ path: [...base, "style"], value: form.style === "chat" ? "chat" : null }); patches.push({ path: [...base, "prompt"], value: form.prompt || null }); } try { await patch("/config", { patches }); onSaved(true, t("settings.saved")); } catch (e: any) { onSaved(false, e.message); } }; const removeFile = async () => { if (!(await confirmDialog({ message: t("agents.deleteFile", { path: a.path ?? a.name }), danger: true }))) return; try { await del(`/agents/${encodeURIComponent(a.name)}`); onSaved(true, t("settings.saved")); } catch (e: any) { onSaved(false, e.message); } }; const reset = async () => { if (!(await confirmDialog({ message: t(builtin ? "agents.resetConfirm" : "agents.deleteConfirm", { name: a.name }), danger: true }))) return; try { await patch("/config", { patches: [{ path: ["agents", a.name], value: null }] }); onSaved(true, t("settings.saved")); } catch (e: any) { onSaved(false, e.message); } }; return (

{a.name} {a.mode === "primary" ? t("agents.modePrimary") : t("agents.modeSubagent")} {t(`agents.source.${a.source}` as any)} {a.read_only && {t("agents.readOnly")}} {!builtin && (a.source === "config" || a.source === "file") && ( )}

{a.description}
{t("agents.model")}: {a.model || t("agents.sessionModel")} {a.max_parallel ? {t("agents.maxParallelShort", { n: a.max_parallel })} : null} {a.refine?.rounds ? {t("agents.refineShort", { n: a.refine.rounds })} : null}
{open && ( <>
setForm({ ...form, model: v })} emptyLabel={t("agents.sessionModel")} />
{t("agents.modelHint")}
{a.mode !== "primary" && ( <> setForm({ ...form, max_parallel: e.target.value })} /> )} {a.mode === "primary" && ( <> setForm({ ...form, refine_rounds: e.target.value })} /> setForm({ ...form, refine_model: v })} emptyLabel={t("agents.sessionModel")} />