"use client"; import { CheckIcon, Clock3Icon, ShieldAlertIcon, ShieldCheckIcon, ShieldIcon, Undo2Icon, XIcon } from "lucide-react"; import { AnimatePresence, motion, useReducedMotion } from "motion/react"; import { type KeyboardEvent, useEffect, useLayoutEffect, useRef, useState } from "react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Kbd } from "@/components/ui/kbd"; import { cn } from "@/lib/utils"; export type GoNoGoRisk = "low" | "medium" | "high"; export type GoNoGoDecision = "approved" | "denied" | "expired"; export type GoNoGoPreview = | { type: "command"; command: string; cwd?: string } | { type: "diff"; file: string; lines: { kind: "+" | "-" | " "; text: string }[] }; export interface GoNoGoRequest { agent: string; action: string; risk: GoNoGoRisk; preview: GoNoGoPreview; reason?: string; } export interface GoNoGoProps { request: GoNoGoRequest; timeout?: number; onDecision?: (decision: GoNoGoDecision) => void; onUndo?: () => void; className?: string; } const risks: Record = { low: { label: "Low risk", variant: "success", Icon: ShieldCheckIcon }, medium: { label: "Medium risk", variant: "warning", Icon: ShieldIcon }, high: { label: "High risk", variant: "error", Icon: ShieldAlertIcon }, }; const outcomes: Record = { approved: { label: "Approved", tone: "bg-success text-white", Icon: CheckIcon }, denied: { label: "Denied", tone: "bg-destructive text-white", Icon: XIcon }, expired: { label: "Timed out, denied", tone: "bg-muted-foreground/60 text-background", Icon: Clock3Icon }, }; const press = "transition-[scale,box-shadow,background-color] duration-150 ease-out active:scale-[0.97]"; function previewText(p: GoNoGoPreview) { return p.type === "command" ? p.command : p.file; } function useThemeRadius(scale: number) { const probe = useRef(null); const [radius, setRadius] = useState(scale * 10); useLayoutEffect(() => { const el = probe.current; if (!el) return; const read = () => setRadius(el.offsetWidth); read(); const observer = new ResizeObserver(read); observer.observe(el); return () => observer.disconnect(); }, []); const marker = ( ); return [radius, marker] as const; } export function GoNoGo({ request, timeout = 30, onDecision, onUndo, className }: GoNoGoProps) { const reduce = useReducedMotion(); const [radius, radiusProbe] = useThemeRadius(1.8); const [decision, setDecision] = useState(null); const [left, setLeft] = useState(timeout * 1000); const [hovered, setHovered] = useState(false); const [focused, setFocused] = useState(false); const rootRef = useRef(null); const paused = hovered || focused; const decide = (d: GoNoGoDecision) => { setDecision(d); onDecision?.(d); }; useEffect(() => { if (decision || paused) return; let last = performance.now(); const id = setInterval(() => { const now = performance.now(); const dt = now - last; last = now; setLeft((l) => Math.max(0, l - dt)); }, 100); return () => clearInterval(id); }, [decision, paused]); useEffect(() => { if (left <= 0 && !decision) decide("expired"); }, [left, decision]); const onKeyDown = (e: KeyboardEvent) => { if (decision || e.metaKey || e.ctrlKey || e.altKey) return; const k = e.key.toLowerCase(); if (k === "y") { e.preventDefault(); decide("approved"); } else if (k === "n" || k === "escape") { e.preventDefault(); decide("denied"); } }; const undo = () => { setDecision(null); setLeft(timeout * 1000); onUndo?.(); requestAnimationFrame(() => rootRef.current?.focus()); }; const risk = risks[request.risk]; const spring = reduce ? { duration: 0 } : { type: "spring" as const, stiffness: 380, damping: 32 }; const fade = reduce ? { duration: 0 } : { duration: 0.18 }; return ( { if (!e.currentTarget.contains(e.relatedTarget as Node)) setFocused(false); }} onFocus={() => setFocused(true)} onKeyDown={onKeyDown} onPointerEnter={() => setHovered(true)} onPointerLeave={() => setHovered(false)} ref={rootRef} role="group" style={{ borderRadius: radius }} tabIndex={decision ? -1 : 0} transition={spring} > {radiusProbe} {decision ? ( {(() => { const Icon = outcomes[decision].Icon; return ; })()}

{outcomes[decision].label} {" "} · {request.agent} · {previewText(request.preview)}

) : (
{request.agent.slice(0, 1)}

{request.agent} wants to {request.action}

{risk.label}
{request.preview.type === "command" ? (
{request.preview.cwd &&

{request.preview.cwd}

}

$ {request.preview.command}

) : (

{request.preview.file}

{request.preview.lines.map((line, i) => (

{line.kind} {line.text}

))}
)} {request.reason &&

{request.reason}

}

{focused ? "Press Y or N" : paused ? "Paused while you look" : "Denies itself when time runs out"}

)}
); } function Countdown({ left, total, paused, reduce }: { left: number; total: number; paused: boolean; reduce: boolean }) { const frac = total ? left / total : 0; const urgent = left < 10000; return (
{paused ? ( ) : ( Math.ceil(left / 1000) )}
); }