| 1 | import { ProjectId, ProviderDriverKind } from "@t3tools/contracts"; |
| 2 | import type { OrchestrationV2TurnItemStatus } from "@t3tools/contracts"; |
| 3 | import { describe, expect, it } from "vite-plus/test"; |
| 4 | import { |
| 5 | subagentGroupSummary, |
| 6 | resolveSubagentMetadata, |
| 7 | subagentDetailPreview, |
| 8 | } from "./subagentDisplay.js"; |
| 9 | |
| 10 | describe("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 | |
| 45 | describe("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 | |
| 156 | describe("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 | }); |