"use client"; /** * מתאמי-סוכנים (Adapter) — /operations panel. * * Migrate any committee agent between run-engines (Claude / Gemini / DeepSeek / Codex) * in BOTH companies at once, with a preflight that prevents the silent crash * (frontmatter `---` breaks the gemini/deepseek CLI; model must match provider; * gemini excludeTools is global). Built to the Claude Design mockup * `02d-operations-adapters.html`. The actual migration runs host-side * (scripts/migrate_agent_adapter.py) via the court-fetch bridge — see * useMigrateAdapter / POST /api/operations/agents/migrate-adapter. */ import { useState } from "react"; import { toast } from "sonner"; import { Card, CardContent } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Switch } from "@/components/ui/switch"; import { Label } from "@/components/ui/label"; import { Skeleton } from "@/components/ui/skeleton"; import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem, } from "@/components/ui/select"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, } from "@/components/ui/dialog"; import { usePaperclipAgents, type AgentPair, } from "@/lib/api/paperclip-agents"; import { useMigrateAdapter, type MigrateAdapterRequest, type MigrateAdapterResult, } from "@/lib/api/operations"; const ADAPTERS = [ { value: "claude_local", label: "Claude", cls: "bg-info-bg text-info border-info/40" }, { value: "gemini_local", label: "Gemini", cls: "bg-warn-bg text-warn border-warn/40" }, { value: "deepseek_local", label: "DeepSeek", cls: "bg-gold-wash text-gold-deep border-gold/40" }, { value: "codex_local", label: "Codex", cls: "bg-success-bg text-success border-success/40" }, ] as const; // Bilingual role subtitle under the agent name (mockup 02d) — display-only, // keyed by the agent's Hebrew name. Falls back to none for unmapped agents. const ROLE_SUBTITLE: Record = { "עוזר משפטי": "CEO · מתזמר", "מנתח משפטי": "analyst", "כותב החלטה": "writer", "מנהל ידע": "אוצֵר-הלכות", "בודק איכות": "qa", "הגהת מסמכים": "proofreader", "שטן מליץ": "red-team (Gemini)", }; function adapterCls(a: string | null | undefined): string { return ADAPTERS.find((x) => x.value === a)?.cls ?? "bg-rule-soft text-ink-muted border-rule"; } function adapterLabel(a: string | null | undefined): string { return ADAPTERS.find((x) => x.value === a)?.label ?? a ?? "—"; } /** A pending confirmation: the apply/revert to run, plus its preflight result. */ type PendingDialog = { title: string; body: MigrateAdapterRequest; // action is "apply" or "revert" preflight: MigrateAdapterResult | null; loadingPreflight: boolean; relax: boolean; showRelax: boolean; }; export function AgentAdaptersPanel() { const { data, isLoading } = usePaperclipAgents(); const migrate = useMigrateAdapter(); const [targets, setTargets] = useState>({}); const [dlg, setDlg] = useState(null); const busy = migrate.isPending; const currentOf = (p: AgentPair) => p.master?.adapter_type ?? p.mirror?.adapter_type ?? null; const modelOf = (p: AgentPair) => p.master?.model ?? p.mirror?.model ?? ""; const isAsymmetric = (p: AgentPair) => !!p.master && !!p.mirror && p.master.adapter_type !== p.mirror.adapter_type; const targetFor = (p: AgentPair) => targets[p.name] ?? currentOf(p) ?? "claude_local"; // Run a preflight (check) and open the confirm dialog pre-loaded with its output. async function openMigrate(agent: string, to: string, title: string) { setDlg({ title, body: { action: "apply", agent, to, relax_tools: true }, preflight: null, loadingPreflight: true, relax: true, showRelax: false }); try { const res = await migrate.mutateAsync({ action: "check", agent, to }); const conflict = /excludeTools|כלי-כתיבה/.test(res.stdout); setDlg((d) => d && { ...d, preflight: res, loadingPreflight: false, showRelax: conflict, relax: conflict }); } catch (e) { setDlg((d) => d && { ...d, loadingPreflight: false, preflight: { ok: false, exit_code: -1, stdout: "", stderr: String(e) } }); } } function openRevert(agent: string, title: string) { setDlg({ title, body: { action: "revert", agent }, preflight: null, loadingPreflight: false, relax: false, showRelax: false }); } // Confirm → run the dialog's apply/revert and toast on the script's exit code. async function confirmDialog() { if (!dlg) return; const body = { ...dlg.body, relax_tools: dlg.relax }; try { const res = await migrate.mutateAsync(body); if (res.exit_code === 0) { toast.success(dlg.body.action === "revert" ? "הוחזר ל-Claude" : "המעבר הושלם בשתי החברות"); } else { toast.error("הפעולה נכשלה — ראה פלט"); // keep dialog open so the operator sees the failure output setDlg((d) => d && { ...d, preflight: res, loadingPreflight: false }); return; } } catch (e) { toast.error(`שגיאה: ${String(e)}`); return; } setDlg(null); } const preflightBlocked = !!dlg?.preflight && dlg.body.action === "apply" && dlg.preflight.exit_code !== 0; return (

העברת כל סוכן בין מנועי-ההרצה (Claude · Gemini · DeepSeek · Codex), בשתי החברות יחד. כל מעבר עובר preflight שמונע קריסה. מעבר ל-Gemini/DeepSeek = fallback מופחת-איכות — להחזיר ל-Claude כשטוקני-Claude חוזרים.

{/* Emergency fallback bar */}
מצב-חירום · טוקני-Claude
כשנגמרים הטוקנים — להפיל את כל הצוות ל-Gemini או ל-Codex בלחיצה, ולהחזיר כשחוזרים.
{isLoading || !data ? ( ) : (
תפקיד מתאם נוכחי מודל העבר ל־ מצב {data.pairs.map((p) => { const cur = currentOf(p); const tgt = targetFor(p); const migrated = cur !== "claude_local" && cur !== null; const asym = isAsymmetric(p); const subtitle = ROLE_SUBTITLE[p.name]; return (
{p.name}
{subtitle ? (
{subtitle}
) : null}
{adapterLabel(cur)} {modelOf(p) || "(default)"}
{migrated ? ( ) : null}
{asym ? ( ⚠ א-סימטרי ) : migrated ? ( מועבר · fallback ) : ( תקין )}
); })}
)}
{/* Confirm dialog: preflight output + apply/revert */} !o && setDlg(null)}> {dlg?.title} {dlg?.body.action === "revert" ? "שחזור מדויק למתאם ולמודל שהיו לפני המעבר, בשתי החברות." : "המעבר חל על שתי החברות (CMP + CMPA). Gemini/DeepSeek/Codex = fallback מופחת-איכות."} {dlg?.body.action === "apply" ? (
preflight:
{dlg.loadingPreflight ? ( ) : (
                  {(dlg.preflight?.stdout || "") + (dlg.preflight?.stderr || "") || "—"}
                
)} {dlg.showRelax ? (
setDlg((d) => d && { ...d, relax: c })} />
) : null}
) : null}
); }