1import { isActiveSubagentStatus } from "@t3tools/client-runtime/state/subagentRuntime";
2import type { OrchestrationV2Subagent } from "@t3tools/contracts";
3import { formatDuration } from "@t3tools/shared/orchestrationTiming";
4import * as DateTime from "effect/DateTime";
5
6export function subagentCardDetail(detail: string | null): string | null {
7 if (!detail || /^Child task ended with status\b/i.test(detail)) return null;
8 return (
9 detail
10 .replace(/\[([^\]]+)\]\([^)]*\)/g, "$1")
11 .replace(/`/g, "")
12 .replace(/^[ \t]*[-*][ \t]+/gm, "")
13 .replace(/\s+/g, " ")
14 .trim() || null
15 );
16}
17
18/** A group's wall time spans its first launch to its last completion. */
19export function subagentCardElapsed(
20 agents: ReadonlyArray<Pick<OrchestrationV2Subagent, "status" | "startedAt" | "completedAt">>,
21 nowMs: number,
22): string | null {
23 const starts = agents.flatMap((agent) =>
24 agent.startedAt ? [DateTime.toEpochMillis(agent.startedAt)] : [],
25 );
26 if (starts.length === 0) return null;
27 const live = agents.some((agent) => isActiveSubagentStatus(agent.status));
28 // Settled agents without a completion timestamp must not keep counting their age.
29 if (!live && agents.some((agent) => agent.completedAt === null)) return null;
30 const ends = agents.flatMap((agent) =>
31 agent.completedAt ? [DateTime.toEpochMillis(agent.completedAt)] : [],
32 );
33 const duration = (live ? nowMs : Math.max(...ends)) - Math.min(...starts);
34 return duration > 0 ? formatDuration(duration) : null;
35}