"use client"; import { useState } from "react"; import { AlertCircle, Bot, ChevronDown, ChevronUp, PauseCircle, PlayCircle, } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; import { Skeleton } from "@/components/ui/skeleton"; import { usePaperclipAgents, type AgentPair, type DriftEntry, type PaperclipAgent, } from "@/lib/api/paperclip-agents"; const ROLE_LABEL: Record = { ceo: "CEO", researcher: "מחקר", engineer: "כתיבה", qa: "בקרה", general: "כללי", }; const FIELD_LABEL: Record = { model: "מודל", effort: "effort", timeoutSec: "timeout (שניות)", maxTurnsPerRun: "max turns", desiredSkills: "skills", instructionsBundleMode: "bundle mode", instructionsEntryFile: "entry file", graceSec: "grace (שניות)", cooldownSec: "cooldown (שניות)", wakeOnDemand: "wake on demand", maxConcurrentRuns: "max concurrent", budget_monthly_cents: "תקציב חודשי", status: "סטטוס", }; function formatCents(cents: number | null): string { if (cents == null) return "—"; return `$${(cents / 100).toFixed(2)}`; } function StatusBadge({ agent }: { agent: PaperclipAgent }) { const status = agent.status ?? "unknown"; if (status === "paused" || status === "terminated") { return ( {status === "paused" ? "מושהה" : "סיים"} ); } return ( פעיל ); } function FieldRow({ label, master, mirror, drifted, mono, }: { label: string; master: React.ReactNode; mirror: React.ReactNode; drifted: boolean; mono?: boolean; }) { const cellBase = `tabular-nums text-[0.82rem] ${mono ? "font-mono" : ""}`; const cellCls = (val: React.ReactNode) => `${cellBase} px-2 py-1 rounded ${ drifted ? "bg-warn-bg text-warn border border-warn/40" : "text-ink" } ${val == null || val === "—" ? "text-ink-light" : ""}`; return (
{label}
{master ?? "—"}
{mirror ?? "—"}
); } function PairCard({ pair }: { pair: AgentPair }) { const [expanded, setExpanded] = useState(false); const driftFields = new Set(pair.drift.map((d) => d.field)); const driftCount = pair.drift.length; const pairMissing = driftFields.has("_pair_missing"); const a = pair.master ?? pair.mirror; if (!a) return null; const fieldVal = ( side: "master" | "mirror", key: keyof PaperclipAgent, ): React.ReactNode => { const agent = pair[side]; if (!agent) return ; const v = agent[key]; if (v == null) return "—"; if (typeof v === "boolean") return v ? "✓" : "✗"; if (Array.isArray(v)) return `${v.length}`; return String(v); }; const skillsList = (agent: PaperclipAgent | null) => agent?.desiredSkills?.length ? agent.desiredSkills : []; return (

{pair.name}

{ROLE_LABEL[pair.role ?? ""] ?? pair.role ?? "—"} {pair.master && }
{pairMissing ? ( {pair.master ? "חסר ב-CMPA" : "חסר ב-CMP"} ) : driftCount > 0 ? ( {driftCount} פערים ) : ( מסונכרן )}
CMP (1xxx)
CMPA (8xxx)
{pair.master?.updated_at && ( עודכן: {new Date(pair.master.updated_at).toLocaleDateString("he-IL")} )}
{expanded && (
{pair.drift.length > 0 && !pairMissing && (
פערי סנכרון
    {pair.drift.map((d: DriftEntry) => (
  • {FIELD_LABEL[d.field] ?? d.field} CMP: {JSON.stringify(d.master)} CMPA: {JSON.stringify(d.mirror)}
  • ))}
)}
{(["master", "mirror"] as const).map((side) => { const agent = pair[side]; const skills = skillsList(agent); return (
{side === "master" ? "CMP" : "CMPA"}
{agent ? ( <>
id: {agent.id}
skills ({skills.length})
{skills.length === 0 ? ( ) : (
    {skills.map((s) => (
  • {s}
  • ))}
)}
{agent.instructionsFilePath && (
instructions path
{agent.instructionsFilePath}
)} {agent.pause_reason && (
סיבת השהיה: {agent.pause_reason}
)} ) : ( חסר )}
); })}
)}
); } export function AgentsTab() { const { data, isPending, error, refetch, isFetching } = usePaperclipAgents(); if (error) { return ( שגיאה: {error.message} ); } if (isPending) { return (
{[...Array(7)].map((_, i) => ( ))}
); } if (!data || data.pairs.length === 0) { return ( לא נמצאו סוכנים ); } const totalDrift = data.pairs.reduce( (sum, p) => sum + p.drift.filter((d) => d.field !== "_pair_missing").length, 0, ); const missingCount = data.pairs.filter((p) => !p.master || !p.mirror).length; return (
{data.pairs.length} סוכנים × 2 חברות (CMP master / CMPA mirror) {totalDrift > 0 && ( · {totalDrift} פערי סנכרון )} {missingCount > 0 && ( · {missingCount} זוגות לא שלמים )}
פערי skills מחושבים על paperclipai/* בלבד. local/* ו-company/* מסוננים — שם שונה בין החברות הוא צפוי.
{data.pairs.map((pair) => ( ))}
); }