"use client" import * as React from 'react' import Link from 'next/link' import { Button } from '@open-mercato/ui/primitives/button' import { Avatar } from '@open-mercato/ui/primitives/avatar' import { StatusBadge } from '@open-mercato/ui/primitives/status-badge' import { LoadingMessage, ErrorMessage } from '@open-mercato/ui/backend/detail' import { SectionHeader } from '@open-mercato/ui/backend/SectionHeader' import { apiCall } from '@open-mercato/ui/backend/utils/apiCall' import { useT } from '@open-mercato/shared/lib/i18n/context' import { AgentIoDrawer } from './AgentIoDrawer' import { agentAvatarIcon } from './agentChips' import { useAgentIconMap } from './useAgentIcons' import { mapProposal, mapRun, formatConfidence, type ProposalView, type RunView } from './types' import { dispositionLabelKey, dispositionVariant, ConfidenceFaceValue } from './cockpitStatus' type ProposalsResponse = { items?: Array> } type RunsResponse = { items?: Array> } export type AgentTimelineProps = { /** The workflow instance id used to scope proposals (`?workflowInstanceId=`). */ workflowInstanceId: string } /** * Three-lane (agent / system / human) timeline of agent activity for a workflow * process. Designed to be injected under the workflows instance-detail header; * uses `StatusBadge`/status tokens only (never the legacy `bg-blue-100` classes * the current monitor uses — Boy-Scout rule). */ export function AgentTimeline({ workflowInstanceId }: AgentTimelineProps) { const t = useT() const agentIcons = useAgentIconMap() const [proposals, setProposals] = React.useState([]) const [runs, setRuns] = React.useState>({}) const [isLoading, setIsLoading] = React.useState(true) const [error, setError] = React.useState(null) const [drawerRun, setDrawerRun] = React.useState(null) const [drawerOpen, setDrawerOpen] = React.useState(false) React.useEffect(() => { let cancelled = false async function load() { setIsLoading(true) setError(null) try { const call = await apiCall( `/api/agent_orchestrator/proposals?workflowInstanceId=${encodeURIComponent(workflowInstanceId)}&pageSize=100`, undefined, { fallback: { items: [] } }, ) if (cancelled) return if (!call.ok) { setError(t('agent_orchestrator.timeline.error')) return } const items = Array.isArray(call.result?.items) ? call.result!.items : [] const mapped = items .map((item) => mapProposal(item as Record)) .filter((row): row is ProposalView => !!row) setProposals(mapped) const runEntries = await Promise.all( Array.from(new Set(mapped.map((proposal) => proposal.runId))).map(async (runId) => { try { const runCall = await apiCall( `/api/agent_orchestrator/runs?id=${encodeURIComponent(runId)}`, undefined, { fallback: { items: [] } }, ) const runItems = Array.isArray(runCall.result?.items) ? runCall.result!.items : [] const run = runItems[0] ? mapRun(runItems[0] as Record) : null return run ? ([runId, run] as const) : null } catch { return null } }), ) if (!cancelled) { const map: Record = {} for (const entry of runEntries) { if (entry) map[entry[0]] = entry[1] } setRuns(map) } } catch (err) { if (!cancelled) setError(err instanceof Error ? err.message : t('agent_orchestrator.timeline.error')) } finally { if (!cancelled) setIsLoading(false) } } if (workflowInstanceId) load() return () => { cancelled = true } }, [workflowInstanceId, t]) const openDrawer = React.useCallback((run: RunView | null) => { setDrawerRun(run) setDrawerOpen(true) }, []) return (
{isLoading ? ( ) : error ? ( ) : proposals.length === 0 ? (

{t('agent_orchestrator.timeline.empty')}

) : (
    {proposals.map((proposal) => { const run = runs[proposal.runId] ?? null const confidence = formatConfidence(proposal.confidence) const isParked = proposal.disposition === 'pending' return (
  • {t('agent_orchestrator.timeline.lane.agent')} {isParked ? ( {t('agent_orchestrator.timeline.parked')} ) : ( {t(dispositionLabelKey(proposal.disposition))} )}

    {proposal.agentId}

    {proposal.confidence != null ? ( ) : null}
    {isParked ? ( ) : null}
  • ) })}
)}
) } export default AgentTimeline