import { buildFleetRows, fleetTiming, type DelegateInput, type FleetFilter, type FleetRow, type FleetSource, type PeerInput, type ResearchInput, type SpecialistInput } from "../../lib/fleet-read-model.ts"; export interface FleetSourceAgent { def: { name: string; description?: string }; status: FleetRow["status"]; task?: string; toolCount: number; elapsed: number; lastWork: string; contextPct: number; contextTokens: number; histEntry?: { startedAt: number; endedAt: number | null }; delegations?: ReadonlyMap; lastBackend?: "native" | "coms"; comsPeerModel?: string; runCount: number; dispatchId?: string; } export interface FleetSourceDelegate { id: string; parent: string; role: string; model: string; status: "running" | "done" | "error"; toolCount: number; tokens: number; lastWork: string; startedAt: number; elapsed: number; histEntry?: { startedAt: number; endedAt: number | null }; } export interface FleetSourceResearch { id: number; persona: boolean; def: { name: string }; status: FleetRow["status"]; model: string; toolCount: number; elapsed: number; lastWork: string; contextPct: number; histEntry?: { startedAt: number; endedAt: number | null }; } export interface PendingReplyLike { target_name?: string; created_at?: string; result?: unknown; } export interface PeerCardLike { name: string; model: string; purpose: string; color: string; staleCount?: number; status?: string; queue_depth?: number; } export interface FleetSourceDeps { getAgents(): ReadonlyMap; getResearch(): ReadonlyMap; getPeerInputs(formatModel?: (model: string) => string): PeerInput[]; getPeerCards(): ReadonlyMap; getPendingReplies(): Iterable; displayName(name: string): string; modelForAgent(state: TAgent): string; modelForResearch(state: TResearch): string; modelForPeer(model: string): string; } function delegateForest(children: readonly FleetSourceDelegate[], now: number, model: (value: string) => string): DelegateInput[] { const byId = new Map(children.map(child => [child.id, child])); const invalid = new Set(); for (const child of children) { if (child.parent === child.id) { invalid.add(child.id); continue; } let cursor: FleetSourceDelegate | undefined = child; const path = new Set(); while (cursor && cursor.parent !== "root" && byId.has(cursor.parent)) { if (path.has(cursor.id)) { for (const id of path) invalid.add(id); break; } path.add(cursor.id); cursor = byId.get(cursor.parent); } } const nested = new Map(); const roots: FleetSourceDelegate[] = []; for (const child of children) { if (!invalid.has(child.id) && child.parent !== "root" && byId.has(child.parent)) { const list = nested.get(child.parent) ?? []; list.push(child); nested.set(child.parent, list); } else roots.push(child); } const make = (child: FleetSourceDelegate, ancestry = new Set()): DelegateInput => { const safeChildren = ancestry.has(child.id) ? [] : (nested.get(child.id) ?? []); const next = new Set(ancestry); next.add(child.id); const timing = child.histEntry ? fleetTiming(child.histEntry, now) : { startedAt: child.startedAt, endedAt: child.status === "running" ? undefined : child.startedAt + Math.max(0, child.elapsed), elapsed: child.status === "running" ? Math.max(0, now - child.startedAt) : child.elapsed, timingKind: "run" as const, }; return { key: child.id, name: child.role || child.id, status: child.status, model: model(child.model), contextPct: null, contextTokens: null, ...timing, runToken: `${child.id}:${child.startedAt}`, toolCount: child.toolCount, lastWork: child.lastWork, children: safeChildren.map(item => make(item, next)) }; }; return roots.map(child => make(child)); } /** One production adapter for dashboard and below-editor widget data. */ export function createFleetSource(deps: FleetSourceDeps) { function snapshot(now: number): FleetSource { const specialists: SpecialistInput[] = Array.from(deps.getAgents().entries()).map(([key, state]) => ({ key, name: deps.displayName(state.def.name), status: state.status, model: deps.modelForAgent(state), backend: state.lastBackend ?? "native", contextPct: state.contextPct, contextTokens: state.contextTokens, ...fleetTiming(state.histEntry, now), runToken: `${key}:${state.dispatchId ?? state.runCount}`, toolCount: state.toolCount, lastWork: state.lastWork || state.task || state.def.description || "", hasTimeline: true, delegates: delegateForest(Array.from(state.delegations?.values() ?? []), now, deps.modelForPeer), })); const research: ResearchInput[] = Array.from(deps.getResearch().values()).map(state => ({ key: `r${state.id}`, name: `r${state.id} ${state.persona ? deps.displayName(state.def.name) : "research"}`, status: state.status, model: deps.modelForResearch(state), backend: "native", contextPct: state.contextPct, contextTokens: null, ...fleetTiming(state.histEntry, now), runToken: `r${state.id}:${state.histEntry?.startedAt ?? 0}`, toolCount: state.toolCount, lastWork: state.lastWork, hasTimeline: true, })); const peers = deps.getPeerInputs(deps.modelForPeer).map(peer => ({ ...peer })); const peerBySession = new Map(peers.map(peer => [peer.key, peer])); const peersByName = new Map(); for (const peer of peers) { const list = peersByName.get(peer.name) ?? []; list.push(peer); peersByName.set(peer.name, list); } for (const [sessionId, card] of deps.getPeerCards()) { const key = `peer:${sessionId}`; let peer = peerBySession.get(key); if (!peer) { peer = { key, name: card.name, model: deps.modelForPeer(card.model), lastWork: card.purpose, colorHex: card.color, staleCount: card.staleCount }; peers.push(peer); peerBySession.set(key, peer); const list = peersByName.get(peer.name) ?? []; list.push(peer); peersByName.set(peer.name, list); } if (card.status === "working" || (card.queue_depth ?? 0) > 0) peer.status = "running"; } const pendingByName = new Map(); for (const pending of deps.getPendingReplies()) { if (pending.result || !pending.target_name) continue; const parsed = pending.created_at ? Date.parse(pending.created_at) : Number.NaN; const previous = pendingByName.get(pending.target_name) ?? { count: 0 }; previous.count++; if (Number.isFinite(parsed)) previous.oldest = previous.oldest == null ? parsed : Math.min(previous.oldest, parsed); pendingByName.set(pending.target_name, previous); } for (const [name, pending] of pendingByName) { const candidates = peersByName.get(name) ?? []; if (candidates.length === 1) { const peer = candidates[0]; peer.status = "running"; peer.timingKind = pending.oldest == null ? "unknown" : "wait"; peer.startedAt = pending.oldest; peer.elapsed = pending.oldest == null ? 0 : Math.max(0, now - pending.oldest); peer.aliasKeys = [`peer-pending:${encodeURIComponent(name)}`]; } else { peers.push({ key: `peer-pending:${encodeURIComponent(name)}`, name, model: "", lastWork: `${pending.count} pending ${pending.count === 1 ? "reply" : "replies"}`, status: "running", timingKind: pending.oldest == null ? "unknown" : "wait", startedAt: pending.oldest, elapsed: pending.oldest == null ? 0 : Math.max(0, now - pending.oldest) }); } } return { specialists, research, peers }; } return { snapshot, rows(now: number, filter: FleetFilter): FleetRow[] { return buildFleetRows(snapshot(now), filter); }, }; }