mirror of
https://github.com/shareAI-lab/analysis_claude_code.git
synced 2026-09-21 12:53:37 +08:00
Consolidate agent harness course into 19 lessons
This commit is contained in:
289
web/src/components/visualizations/s15-team-runtime.tsx
Normal file
289
web/src/components/visualizations/s15-team-runtime.tsx
Normal file
@@ -0,0 +1,289 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import {
|
||||
CheckCircle2,
|
||||
ClipboardList,
|
||||
GitBranch,
|
||||
Inbox,
|
||||
LockKeyhole,
|
||||
Search,
|
||||
Terminal,
|
||||
UsersRound,
|
||||
} from "lucide-react";
|
||||
import { StepControls } from "@/components/visualizations/shared/step-controls";
|
||||
import { useSteppedVisualization } from "@/hooks/useSteppedVisualization";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const STEPS = [
|
||||
{
|
||||
title: "Confirm a Small Team",
|
||||
desc: "The Lead proposes focused roles and waits for the user before starting persistent teammates.",
|
||||
event: "user confirmed: backend + tests",
|
||||
},
|
||||
{
|
||||
title: "Deliver a Typed Assignment",
|
||||
desc: "The runtime writes the assignment to a mailbox and correlates plan approval with a request id.",
|
||||
event: "plan_response(req_7, approved=true)",
|
||||
},
|
||||
{
|
||||
title: "Idle Teammates Scan the Board",
|
||||
desc: "A teammate with no direct message looks only for pending, unowned work whose dependencies are complete.",
|
||||
event: "scan_ready_tasks(backend) -> task_auth",
|
||||
},
|
||||
{
|
||||
title: "Claim Under One Lock",
|
||||
desc: "Ownership and status change atomically, so another teammate cannot take the same task.",
|
||||
event: "task_lock: task_auth -> backend",
|
||||
},
|
||||
{
|
||||
title: "Route Tools to the Task Directory",
|
||||
desc: "The claimed task carries its worktree binding; bash, read, and write derive their cwd from that record.",
|
||||
event: "cwd -> .worktrees/auth-refactor",
|
||||
},
|
||||
{
|
||||
title: "Return the Result, Keep the Teammate",
|
||||
desc: "The runtime delivers the result to the Lead and moves the teammate back to IDLE for newly ready work.",
|
||||
event: "result(auth complete) -> idle_notification",
|
||||
},
|
||||
] as const;
|
||||
|
||||
const EVENTS = STEPS.map((step) => step.event);
|
||||
|
||||
function StateBadge({
|
||||
label,
|
||||
tone,
|
||||
}: {
|
||||
label: string;
|
||||
tone: "zinc" | "blue" | "amber" | "emerald";
|
||||
}) {
|
||||
const classes = {
|
||||
zinc: "bg-zinc-100 text-zinc-600 dark:bg-zinc-800 dark:text-zinc-300",
|
||||
blue: "bg-blue-100 text-blue-700 dark:bg-blue-950/50 dark:text-blue-200",
|
||||
amber: "bg-amber-100 text-amber-700 dark:bg-amber-950/50 dark:text-amber-200",
|
||||
emerald:
|
||||
"bg-emerald-100 text-emerald-700 dark:bg-emerald-950/50 dark:text-emerald-200",
|
||||
}[tone];
|
||||
|
||||
return (
|
||||
<span className={cn("shrink-0 whitespace-nowrap rounded px-2 py-1 text-[11px] font-semibold", classes)}>
|
||||
{label}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function RuntimePanel({ step }: { step: number }) {
|
||||
const state =
|
||||
step === 0 ? "awaiting confirmation" : step === 1 ? "working" : step === 5 ? "idle" : "active";
|
||||
const tone = step === 0 ? "zinc" : step === 5 ? "emerald" : "blue";
|
||||
|
||||
return (
|
||||
<div className="min-h-[250px] rounded-lg border border-zinc-200 bg-white p-3 dark:border-zinc-700 dark:bg-zinc-900">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="flex min-w-0 items-center gap-2 text-sm font-semibold text-zinc-900 dark:text-zinc-100">
|
||||
<UsersRound size={16} className="shrink-0 text-blue-500" />
|
||||
<span className="break-words">Team runtime</span>
|
||||
</div>
|
||||
<StateBadge label={state} tone={tone} />
|
||||
</div>
|
||||
|
||||
<div className="mt-4 grid grid-cols-[88px_minmax(0,1fr)] gap-x-2 gap-y-3 text-xs">
|
||||
<span className="text-zinc-500 dark:text-zinc-400">Lead</span>
|
||||
<span className="break-words font-medium text-zinc-800 dark:text-zinc-200">
|
||||
{step === 0 ? "proposes roles" : step === 5 ? "receives result" : "coordinates"}
|
||||
</span>
|
||||
<span className="text-zinc-500 dark:text-zinc-400">Teammate</span>
|
||||
<span className="break-words font-medium text-zinc-800 dark:text-zinc-200">
|
||||
backend
|
||||
</span>
|
||||
<span className="text-zinc-500 dark:text-zinc-400">Protocol</span>
|
||||
<span className="break-all font-mono text-zinc-700 dark:text-zinc-300">
|
||||
{step < 1 ? "-" : "request_id=req_7"}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 flex min-h-[72px] items-start gap-2 rounded-md border border-blue-200 bg-blue-50 p-3 text-xs text-blue-800 dark:border-blue-900 dark:bg-blue-950/30 dark:text-blue-200">
|
||||
<Inbox size={15} className="mt-0.5 shrink-0" />
|
||||
<span className="break-words leading-relaxed">
|
||||
{step === 0
|
||||
? "No mailbox is created before confirmation."
|
||||
: step === 1
|
||||
? "Assignment and plan approval travel through typed messages."
|
||||
: step === 5
|
||||
? "The result wakes the Lead; IDLE is a reusable state."
|
||||
: "The runtime owns delivery while the teammate works."}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function TaskPanel({ step }: { step: number }) {
|
||||
const status = step < 3 ? "pending" : step < 5 ? "in_progress" : "completed";
|
||||
const owner = step < 3 ? "-" : "backend";
|
||||
const tone = status === "pending" ? "zinc" : status === "in_progress" ? "amber" : "emerald";
|
||||
|
||||
return (
|
||||
<div className="min-h-[250px] rounded-lg border border-zinc-200 bg-white p-3 dark:border-zinc-700 dark:bg-zinc-900">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="flex min-w-0 items-center gap-2 text-sm font-semibold text-zinc-900 dark:text-zinc-100">
|
||||
<ClipboardList size={16} className="shrink-0 text-amber-500" />
|
||||
<span>Task board</span>
|
||||
</div>
|
||||
<StateBadge label={status} tone={tone} />
|
||||
</div>
|
||||
|
||||
<div className="mt-4 rounded-md border border-zinc-200 p-3 dark:border-zinc-700">
|
||||
<div className="font-mono text-[11px] text-zinc-500 dark:text-zinc-400">
|
||||
task_auth
|
||||
</div>
|
||||
<div className="mt-1 text-sm font-semibold text-zinc-900 dark:text-zinc-100">
|
||||
Refactor authentication
|
||||
</div>
|
||||
<div className="mt-3 grid grid-cols-[76px_minmax(0,1fr)] gap-2 text-xs">
|
||||
<span className="text-zinc-500 dark:text-zinc-400">owner</span>
|
||||
<span className="font-mono text-zinc-700 dark:text-zinc-300">{owner}</span>
|
||||
<span className="text-zinc-500 dark:text-zinc-400">blockedBy</span>
|
||||
<span className="font-mono text-zinc-700 dark:text-zinc-300">[]</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
"mt-3 flex min-h-[56px] items-center gap-2 rounded-md border px-3 py-2 text-xs",
|
||||
step === 2 || step === 3
|
||||
? "border-amber-300 bg-amber-50 text-amber-800 dark:border-amber-800 dark:bg-amber-950/30 dark:text-amber-200"
|
||||
: "border-zinc-200 bg-zinc-50 text-zinc-600 dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-300"
|
||||
)}
|
||||
>
|
||||
{step < 3 ? <Search size={15} className="shrink-0" /> : <LockKeyhole size={15} className="shrink-0" />}
|
||||
<span className="break-words">
|
||||
{step < 2
|
||||
? "Waiting for the teammate loop."
|
||||
: step === 2
|
||||
? "Ready filter: pending + unowned + dependencies complete."
|
||||
: "The claim check and update share one lock."}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function WorkspacePanel({ step }: { step: number }) {
|
||||
const bound = step >= 4;
|
||||
|
||||
return (
|
||||
<div className="min-h-[250px] rounded-lg border border-zinc-200 bg-white p-3 dark:border-zinc-700 dark:bg-zinc-900">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="flex min-w-0 items-center gap-2 text-sm font-semibold text-zinc-900 dark:text-zinc-100">
|
||||
<GitBranch size={16} className="shrink-0 text-emerald-500" />
|
||||
<span>Task directory</span>
|
||||
</div>
|
||||
<StateBadge label={bound ? "bound" : "reserved"} tone={bound ? "emerald" : "zinc"} />
|
||||
</div>
|
||||
|
||||
<div className="mt-4 space-y-2">
|
||||
<div
|
||||
className={cn(
|
||||
"rounded-md border p-3 transition-colors",
|
||||
!bound
|
||||
? "border-blue-300 bg-blue-50 dark:border-blue-800 dark:bg-blue-950/30"
|
||||
: "border-zinc-200 bg-zinc-50 dark:border-zinc-700 dark:bg-zinc-800"
|
||||
)}
|
||||
>
|
||||
<div className="font-mono text-xs font-semibold text-zinc-800 dark:text-zinc-200">
|
||||
repository root
|
||||
</div>
|
||||
<div className="mt-1 text-[11px] text-zinc-500 dark:text-zinc-400">
|
||||
coordination state
|
||||
</div>
|
||||
</div>
|
||||
<motion.div
|
||||
animate={bound ? { y: [0, -2, 0] } : { y: 0 }}
|
||||
transition={{ duration: 0.8, repeat: bound ? Infinity : 0 }}
|
||||
className={cn(
|
||||
"rounded-md border p-3 transition-colors",
|
||||
bound
|
||||
? "border-emerald-300 bg-emerald-50 dark:border-emerald-800 dark:bg-emerald-950/30"
|
||||
: "border-dashed border-zinc-300 bg-white dark:border-zinc-700 dark:bg-zinc-900"
|
||||
)}
|
||||
>
|
||||
<div className="break-all font-mono text-xs font-semibold text-zinc-800 dark:text-zinc-200">
|
||||
.worktrees/auth-refactor
|
||||
</div>
|
||||
<div className="mt-1 text-[11px] text-zinc-500 dark:text-zinc-400">
|
||||
{bound ? "bash / read / write cwd" : "task.worktree binding"}
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
<div className="mt-3 flex items-center gap-2 text-xs text-zinc-600 dark:text-zinc-300">
|
||||
{bound ? <Terminal size={15} /> : <GitBranch size={15} />}
|
||||
<span>{bound ? "Tools follow the claimed task." : "No implicit directory switching."}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function TeamRuntime({ title }: { title?: string }) {
|
||||
const vis = useSteppedVisualization({ totalSteps: STEPS.length, autoPlayInterval: 2800 });
|
||||
const step = vis.currentStep;
|
||||
const current = STEPS[step];
|
||||
|
||||
return (
|
||||
<section className="min-h-[500px] space-y-4">
|
||||
<h2 className="text-xl font-semibold text-zinc-900 dark:text-zinc-100">
|
||||
{title || "Agent Team Runtime"}
|
||||
</h2>
|
||||
|
||||
<div className="rounded-lg border border-zinc-200 bg-zinc-50 p-4 dark:border-zinc-700 dark:bg-zinc-950">
|
||||
<div className="grid gap-3 lg:grid-cols-3">
|
||||
<RuntimePanel step={step} />
|
||||
<TaskPanel step={step} />
|
||||
<WorkspacePanel step={step} />
|
||||
</div>
|
||||
|
||||
<div className="mt-3 rounded-lg border border-zinc-200 bg-white p-3 dark:border-zinc-700 dark:bg-zinc-900">
|
||||
<div className="mb-2 flex items-center gap-2 text-xs font-semibold uppercase tracking-wide text-zinc-500 dark:text-zinc-400">
|
||||
<CheckCircle2 size={14} />
|
||||
Runtime events
|
||||
</div>
|
||||
<div className="grid gap-2 md:grid-cols-2 xl:grid-cols-3">
|
||||
{EVENTS.map((event, index) => {
|
||||
const visible = index <= step;
|
||||
return (
|
||||
<motion.div
|
||||
key={event}
|
||||
initial={false}
|
||||
animate={{ opacity: visible ? 1 : 0.18, y: 0 }}
|
||||
aria-hidden={!visible}
|
||||
className={cn(
|
||||
"break-all rounded-md border px-2 py-1.5 font-mono text-[10px]",
|
||||
index === step
|
||||
? "border-blue-300 bg-blue-50 text-blue-700 dark:border-blue-800 dark:bg-blue-950/30 dark:text-blue-200"
|
||||
: "border-zinc-200 bg-zinc-50 text-zinc-600 dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-300"
|
||||
)}
|
||||
>
|
||||
{event}
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<StepControls
|
||||
className="mt-4"
|
||||
currentStep={vis.currentStep}
|
||||
totalSteps={vis.totalSteps}
|
||||
onPrev={vis.prev}
|
||||
onNext={vis.next}
|
||||
onReset={vis.reset}
|
||||
isPlaying={vis.isPlaying}
|
||||
onToggleAutoPlay={vis.toggleAutoPlay}
|
||||
stepTitle={current.title}
|
||||
stepDescription={current.desc}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user