import type { CSSProperties, ReactNode } from 'react'; import { ToolCallCard } from './ToolCallCard.js'; import type { ToolInvocation } from './types.js'; export type AgentStepKind = | 'thought' | 'tool_call' | 'observation' | 'response' | 'custom'; export interface AgentStep { id: string; kind: AgentStepKind; /** Step body. For 'tool_call' supply a ToolInvocation, else freeform. */ content?: ReactNode; /** Required when kind === 'tool_call'. */ invocation?: ToolInvocation; /** Override the label rendered in the marker column. */ label?: string; /** Visually mark this step as still in-progress (e.g. streaming). */ isActive?: boolean; } export interface AgentTimelineProps { steps: AgentStep[]; className?: string; style?: CSSProperties; } /** * `` — vertical trace of agent steps in the * `think → act → observe → respond` loop. Used by JarvisJr and any * agentic flow that wants to expose the agent's reasoning chain. * * Each step renders with a colored marker dot, a kind label, and a * body slot. `kind === 'tool_call'` automatically uses `` * so the rendering stays consistent across the catalog. */ export function AgentTimeline({ steps, className, style }: AgentTimelineProps) { return (
    {steps.map((step, idx) => ( ))}
); } function Step({ step, isLast }: { step: AgentStep; isLast: boolean }) { const palette = STEP_PALETTE[step.kind]; return (
  • {/* Marker rail */}
    {!isLast && ( )}
    {/* Body */}
    {step.label ?? STEP_LABELS[step.kind]}
    {step.kind === 'tool_call' && step.invocation ? ( ) : (
    {step.content}
    )}
  • ); } const STEP_LABELS: Record = { thought: 'Thought', tool_call: 'Action', observation: 'Observation', response: 'Response', custom: 'Step', }; const STEP_PALETTE: Record = { thought: { dot: 'var(--bl-text-tertiary, #888)', glow: 'var(--bl-surface-muted, rgba(0,0,0,0.04))', label: 'var(--bl-text-tertiary, #888)', }, tool_call: { dot: 'var(--bl-accent, #6366f1)', glow: 'var(--bl-accent-muted, rgba(99,102,241,0.18))', label: 'var(--bl-accent, #6366f1)', }, observation: { dot: 'var(--bl-info, #38bdf8)', glow: 'var(--bl-info-muted, rgba(56,189,248,0.18))', label: 'var(--bl-info, #38bdf8)', }, response: { dot: 'var(--bl-success, #22c55e)', glow: 'var(--bl-success-muted, rgba(34,197,94,0.18))', label: 'var(--bl-success, #22c55e)', }, custom: { dot: 'var(--bl-text-secondary, #555)', glow: 'var(--bl-surface-muted, rgba(0,0,0,0.06))', label: 'var(--bl-text-secondary, #555)', }, };