1import { ProjectId, ProviderDriverKind } from "@t3tools/contracts";
2import type { OrchestrationV2TurnItemStatus } from "@t3tools/contracts";
3import { describe, expect, it } from "vite-plus/test";
4import {
5 subagentGroupSummary,
6 resolveSubagentMetadata,
7 subagentDetailPreview,
8} from "./subagentDisplay.js";
9
10describe("subagentGroupSummary", () => {
11 it.each(["pending", "running", "waiting"] as const)(
12 "keeps a mixed group live while a member is %s",
13 (status) => {
14 expect(subagentGroupSummary([{ status: "completed" }, { status }])).toEqual({
15 label: "Kicked off 2 subagents",
16 active: true,
17 failed: false,
18 });
19 },
20 );
21
22 it("settles the label without disguising a failed member as success", () => {
23 const statuses: OrchestrationV2TurnItemStatus[] = [
24 "completed",
25 "failed",
26 "cancelled",
27 "interrupted",
28 ];
29 expect(subagentGroupSummary(statuses.map((status) => ({ status })))).toEqual({
30 label: "Ran 4 subagents",
31 active: false,
32 failed: true,
33 });
34 });
35
36 it("uses a singular label for one idle member", () => {
37 expect(subagentGroupSummary([{ status: "idle" }])).toEqual({
38 label: "Ran 1 subagent",
39 active: false,
40 failed: false,
41 });
42 });
43});
44
45describe("resolveSubagentMetadata", () => {
46 it("resolves provider aliases to catalog names, including custom models", () => {
47 expect(
48 resolveSubagentMetadata({
49 model: "claude-haiku-4-5-20251001",
50 provider: {
51 driver: ProviderDriverKind.make("claudeAgent"),
52 models: [
53 {
54 slug: "claude-haiku-4-5",
55 name: "Claude Haiku 4.5",
56 shortName: "Haiku 4.5",
57 aliases: ["claude-haiku-4-5-20251001"],
58 isCustom: false,
59 capabilities: null,
60 },
61 ],
62 },
63 }).modelLabel,
64 ).toBe("Haiku 4.5");
65 expect(
66 resolveSubagentMetadata({
67 model: "my-model",
68 provider: {
69 driver: ProviderDriverKind.make("acpRegistry"),
70 models: [
71 {
72 slug: "my-model",
73 name: "Cloud+ / My custom model",
74 subProvider: "Cloud+",
75 isCustom: true,
76 capabilities: null,
77 },
78 ],
79 },
80 }).modelLabel,
81 ).toBe("My custom model");
82 });
83
84 it("keeps unknown model identities and does not invent an unreported model", () => {
85 expect(resolveSubagentMetadata({ model: " custom/model " }).modelLabel).toBe("custom/model");
86 expect(
87 resolveSubagentMetadata({
88 model: null,
89 provider: { driver: ProviderDriverKind.make("codex"), models: [] },
90 }).modelLabel,
91 ).toBe("Not reported");
92 expect(resolveSubagentMetadata({ model: " " }).modelLabel).toBe("Not reported");
93 });
94
95 const parentThread = { projectId: ProjectId.make("parent"), worktreePath: null };
96 const parentProject = { workspaceRoot: "/repo" };
97
98 it("shows another project and its branch when the child has a different workspace", () => {
99 expect(
100 resolveSubagentMetadata({
101 model: null,
102 parentThread,
103 parentProject,
104 childThread: { branch: "fix/agents", worktreePath: "/worktrees/agents" },
105 childProject: {
106 id: ProjectId.make("child"),
107 title: "Other project",
108 workspaceRoot: "/other",
109 },
110 }).workspace,
111 ).toEqual([
112 { label: "Project", value: "Other project" },
113 { label: "Branch", value: "fix/agents" },
114 ]);
115 });
116
117 it("labels a detached worktree or another project workspace without a branch", () => {
118 expect(
119 resolveSubagentMetadata({
120 model: null,
121 parentThread,
122 parentProject,
123 childThread: { branch: null, worktreePath: "/worktrees/agents" },
124 }).workspace,
125 ).toEqual([{ label: "Worktree", value: "agents" }]);
126 expect(
127 resolveSubagentMetadata({
128 model: null,
129 parentThread,
130 parentProject,
131 childProject: {
132 id: parentThread.projectId,
133 title: "Same project",
134 workspaceRoot: "/other",
135 },
136 }).workspace,
137 ).toEqual([{ label: "Workspace", value: "other" }]);
138 });
139
140 it("hides redundant workspace metadata and tolerates unavailable child shells", () => {
141 expect(
142 resolveSubagentMetadata({
143 model: null,
144 parentThread,
145 parentProject,
146 childThread: { branch: "main", worktreePath: "/repo" },
147 childProject: { id: parentThread.projectId, title: "Same project", workspaceRoot: "/repo" },
148 }).workspace,
149 ).toEqual([]);
150 expect(resolveSubagentMetadata({ model: null, parentThread, parentProject }).workspace).toEqual(
151 [],
152 );
153 });
154});
155
156describe("subagentDetailPreview", () => {
157 it("prefers progress for live work and results for settled work", () => {
158 const details = { progress: "Reading files", result: "Found two\n problems" };
159 expect(subagentDetailPreview({ ...details, status: "running" })).toBe("Reading files");
160 expect(subagentDetailPreview({ ...details, status: "completed" })).toBe("Found two problems");
161 expect(
162 subagentDetailPreview({ status: "failed", progress: "Last progress", result: " " }),
163 ).toBe("Last progress");
164 expect(subagentDetailPreview({ status: "pending" })).toBeNull();
165 });
166});