| 1 | import { |
| 2 | formatSubagentDisplayTitle, |
| 3 | subagentDetailPreview, |
| 4 | } from "@t3tools/client-runtime/state/subagent-display"; |
| 5 | import { isActiveSubagentStatus } from "@t3tools/client-runtime/state/subagentRuntime"; |
| 6 | import type { OrchestrationV2Subagent } from "@t3tools/contracts"; |
| 7 | |
| 8 | const PROMPT_TITLE_LIMIT = 80; |
| 9 | |
| 10 | export type SubagentRowTone = "working" | "completed" | "failed" | "stopped"; |
| 11 | |
| 12 | export interface SubagentRowPresentation { |
| 13 | readonly title: string; |
| 14 | /** Live agents lead with progress; settled ones lead with what came out. */ |
| 15 | readonly detail: string | null; |
| 16 | readonly statusLabel: string; |
| 17 | readonly tone: SubagentRowTone; |
| 18 | readonly live: boolean; |
| 19 | /** Provider-native tasks have no thread of their own to open. */ |
| 20 | readonly canOpenThread: boolean; |
| 21 | } |
| 22 | |
| 23 | function rowTitle(subagent: Pick<OrchestrationV2Subagent, "title" | "prompt">): string { |
| 24 | const title = subagent.title?.trim(); |
| 25 | if (title) return formatSubagentDisplayTitle(title); |
| 26 | const prompt = subagent.prompt.trim(); |
| 27 | if (prompt.length === 0) return "Subagent"; |
| 28 | return prompt.length > PROMPT_TITLE_LIMIT |
| 29 | ? `${prompt.slice(0, PROMPT_TITLE_LIMIT - 3)}...` |
| 30 | : prompt; |
| 31 | } |
| 32 | |
| 33 | function rowTone(status: OrchestrationV2Subagent["status"]): SubagentRowTone { |
| 34 | if (isActiveSubagentStatus(status)) return "working"; |
| 35 | if (status === "completed") return "completed"; |
| 36 | if (status === "failed") return "failed"; |
| 37 | return "stopped"; |
| 38 | } |
| 39 | |
| 40 | function rowStatusLabel(status: OrchestrationV2Subagent["status"]): string { |
| 41 | switch (status) { |
| 42 | case "pending": |
| 43 | case "running": |
| 44 | return "Working"; |
| 45 | case "waiting": |
| 46 | return "Waiting"; |
| 47 | case "idle": |
| 48 | return "Idle"; |
| 49 | case "completed": |
| 50 | return "Completed"; |
| 51 | case "failed": |
| 52 | return "Failed"; |
| 53 | case "cancelled": |
| 54 | return "Cancelled"; |
| 55 | case "interrupted": |
| 56 | return "Interrupted"; |
| 57 | } |
| 58 | } |
| 59 | |
| 60 | export function resolveSubagentRowPresentation( |
| 61 | subagent: Pick< |
| 62 | OrchestrationV2Subagent, |
| 63 | "title" | "prompt" | "status" | "progress" | "result" | "childThreadId" |
| 64 | >, |
| 65 | ): SubagentRowPresentation { |
| 66 | const live = isActiveSubagentStatus(subagent.status); |
| 67 | return { |
| 68 | title: rowTitle(subagent), |
| 69 | detail: subagentDetailPreview(subagent), |
| 70 | statusLabel: rowStatusLabel(subagent.status), |
| 71 | tone: rowTone(subagent.status), |
| 72 | live, |
| 73 | canOpenThread: subagent.childThreadId !== null, |
| 74 | }; |
| 75 | } |