| 1 | import { describe, expect, it } from "vite-plus/test"; |
| 2 | import type { OrchestrationV2Subagent, OrchestrationV2SubagentWorkflow } from "@t3tools/contracts"; |
| 3 | import * as DateTime from "effect/DateTime"; |
| 4 | |
| 5 | import { deriveWorkflowGroups, projectedSubagentsToRuntime } from "./subagentRuntime.ts"; |
| 6 | |
| 7 | const at = (iso: string) => DateTime.makeUnsafe(iso); |
| 8 | |
| 9 | type ProjectedSubagent = Parameters<typeof projectedSubagentsToRuntime>[0][number]; |
| 10 | |
| 11 | function subagent(overrides: Partial<ProjectedSubagent> = {}): ProjectedSubagent { |
| 12 | return { |
| 13 | id: "coordinator", |
| 14 | title: "probe", |
| 15 | prompt: "export const meta = …", |
| 16 | model: "claude-opus-5[1m]", |
| 17 | status: "running" satisfies OrchestrationV2Subagent["status"], |
| 18 | result: null, |
| 19 | startedAt: at("2026-08-01T10:00:00.000Z"), |
| 20 | completedAt: null, |
| 21 | updatedAt: at("2026-08-01T10:00:05.000Z"), |
| 22 | ...overrides, |
| 23 | }; |
| 24 | } |
| 25 | |
| 26 | const member = ( |
| 27 | overrides: Partial<OrchestrationV2SubagentWorkflow["agents"][number]> = {}, |
| 28 | ): OrchestrationV2SubagentWorkflow["agents"][number] => ({ |
| 29 | index: 1, |
| 30 | label: "alpha:one", |
| 31 | state: "running", |
| 32 | phaseIndex: 1, |
| 33 | phaseTitle: "Alpha", |
| 34 | ...overrides, |
| 35 | }); |
| 36 | |
| 37 | const groupsOf = (subagents: ReadonlyArray<ProjectedSubagent>) => |
| 38 | deriveWorkflowGroups(projectedSubagentsToRuntime(subagents)); |
| 39 | |
| 40 | describe("projectedSubagentsToRuntime workflow expansion", () => { |
| 41 | it("leaves a plain subagent flat", () => { |
| 42 | const [agent, ...rest] = projectedSubagentsToRuntime([subagent()]); |
| 43 | expect(rest).toEqual([]); |
| 44 | expect(agent?.kind).toBe("subagent"); |
| 45 | expect(agent?.phases).toEqual([]); |
| 46 | }); |
| 47 | |
| 48 | it("projects a coordinator plus one row per member", () => { |
| 49 | const rows = projectedSubagentsToRuntime([ |
| 50 | subagent({ |
| 51 | workflow: { |
| 52 | name: "probe-wf", |
| 53 | phases: [ |
| 54 | { index: 1, title: "Alpha" }, |
| 55 | { index: 2, title: "Beta" }, |
| 56 | ], |
| 57 | agents: [member(), member({ index: 2, label: "alpha:two", state: "queued" })], |
| 58 | totalTokens: 1000, |
| 59 | }, |
| 60 | }), |
| 61 | ]); |
| 62 | expect(rows.map((row) => row.kind)).toEqual(["workflow", "workflow_agent", "workflow_agent"]); |
| 63 | expect(rows[0]?.workflowName).toBe("probe-wf"); |
| 64 | expect(rows[0]?.usage?.totalTokens).toBe(1000); |
| 65 | expect(rows[1]).toMatchObject({ |
| 66 | id: "coordinator:agent:1", |
| 67 | title: "alpha:one", |
| 68 | parentAgentId: "coordinator", |
| 69 | phaseIndex: 1, |
| 70 | phaseTitle: "Alpha", |
| 71 | status: "running", |
| 72 | }); |
| 73 | // A queued member is not started work: it must not report an elapsed clock. |
| 74 | expect(rows[2]?.status).toBe("pending"); |
| 75 | expect(rows[2]?.startedAt).toBeNull(); |
| 76 | }); |
| 77 | |
| 78 | it("derives a settled member's end from its reported duration", () => { |
| 79 | const [, settled] = projectedSubagentsToRuntime([ |
| 80 | subagent({ |
| 81 | workflow: { |
| 82 | phases: [], |
| 83 | agents: [ |
| 84 | member({ |
| 85 | state: "completed", |
| 86 | startedAt: Date.UTC(2026, 7, 1, 10, 0, 0), |
| 87 | durationMs: 1500, |
| 88 | totalTokens: 71_141, |
| 89 | toolCalls: 3, |
| 90 | prompt: "Reply with exactly: A1", |
| 91 | result: "A1", |
| 92 | }), |
| 93 | ], |
| 94 | }, |
| 95 | }), |
| 96 | ]); |
| 97 | expect(settled?.status).toBe("completed"); |
| 98 | expect(settled?.completedAt).toBe("2026-08-01T10:00:01.500Z"); |
| 99 | expect(settled?.result).toBe("A1"); |
| 100 | // The prompt stays on progress so the detail view can show both halves. |
| 101 | expect(settled?.progress).toBe("Reply with exactly: A1"); |
| 102 | expect(settled?.usage).toEqual({ totalTokens: 71_141, toolUses: 3, durationMs: 1500 }); |
| 103 | }); |
| 104 | |
| 105 | it.each(["completed", "cancelled", "failed"] as const)( |
| 106 | "settles unfinished members when the coordinator is %s", |
| 107 | (status) => { |
| 108 | const groups = groupsOf([ |
| 109 | subagent({ |
| 110 | status, |
| 111 | completedAt: at("2026-08-01T10:00:09.000Z"), |
| 112 | workflow: { |
| 113 | phases: [], |
| 114 | agents: [ |
| 115 | member(), |
| 116 | member({ index: 2, state: "queued" }), |
| 117 | member({ index: 3, state: "completed" }), |
| 118 | ], |
| 119 | }, |
| 120 | }), |
| 121 | ]); |
| 122 | // The settled run keeps its members reachable under the coordinator. |
| 123 | expect(groups[0]?.workflow.status).toBe(status); |
| 124 | const members = groups[0]?.phases.flatMap((phase) => phase.members); |
| 125 | expect(members?.map((agent) => agent.status)).toEqual([status, status, "completed"]); |
| 126 | expect(members?.[0]?.completedAt).toBe("2026-08-01T10:00:09.000Z"); |
| 127 | }, |
| 128 | ); |
| 129 | |
| 130 | it("surfaces a retried member through the row's activation count", () => { |
| 131 | const [, retried] = projectedSubagentsToRuntime([ |
| 132 | subagent({ workflow: { phases: [], agents: [member({ attempt: 3 })] } }), |
| 133 | ]); |
| 134 | expect(retried?.activationCount).toBe(3); |
| 135 | expect(retried?.attempt).toBe(3); |
| 136 | }); |
| 137 | }); |
| 138 | |
| 139 | describe("deriveWorkflowGroups over a dynamic workflow", () => { |
| 140 | it("groups members under their phases and counts the run", () => { |
| 141 | const groups = groupsOf([ |
| 142 | subagent({ |
| 143 | workflow: { |
| 144 | name: "probe-wf", |
| 145 | phases: [ |
| 146 | { index: 1, title: "Alpha" }, |
| 147 | { index: 2, title: "Beta" }, |
| 148 | ], |
| 149 | agents: [ |
| 150 | member({ state: "completed" }), |
| 151 | member({ index: 2, label: "alpha:two", state: "failed" }), |
| 152 | member({ index: 3, label: "beta:one", phaseIndex: 2, phaseTitle: "Beta" }), |
| 153 | member({ |
| 154 | index: 4, |
| 155 | label: "beta:two", |
| 156 | phaseIndex: 2, |
| 157 | phaseTitle: "Beta", |
| 158 | state: "queued", |
| 159 | }), |
| 160 | ], |
| 161 | }, |
| 162 | }), |
| 163 | ]); |
| 164 | const group = groups[0]; |
| 165 | expect(group?.phases.map((phase) => [phase.title, phase.state])).toEqual([ |
| 166 | ["Alpha", "done"], |
| 167 | ["Beta", "running"], |
| 168 | ]); |
| 169 | expect(group?.phases.map((phase) => phase.settledCount)).toEqual([2, 0]); |
| 170 | expect(group?.phases.flatMap((phase) => phase.members)).toHaveLength(4); |
| 171 | expect(group?.unphasedMembers).toEqual([]); |
| 172 | }); |
| 173 | |
| 174 | it("shows a phase the script only reached at runtime", () => { |
| 175 | const groups = groupsOf([ |
| 176 | subagent({ |
| 177 | workflow: { |
| 178 | // The declared plan has not caught up with the member's phase yet. |
| 179 | phases: [{ index: 1, title: "Alpha" }], |
| 180 | agents: [ |
| 181 | member({ state: "completed" }), |
| 182 | member({ index: 2, label: "dynamic:late", phaseIndex: 3, phaseTitle: "Gamma" }), |
| 183 | ], |
| 184 | }, |
| 185 | }), |
| 186 | ]); |
| 187 | const group = groups[0]; |
| 188 | expect(group?.phases.map((phase) => phase.title)).toEqual(["Alpha", "Gamma"]); |
| 189 | expect(group?.unphasedMembers).toEqual([]); |
| 190 | }); |
| 191 | |
| 192 | it.each([false, true])("keeps a reported phase title, missing title first=%s", (missingFirst) => { |
| 193 | const agents = [member({ phaseTitle: "Review" }), member({ index: 2, phaseTitle: undefined })]; |
| 194 | const groups = groupsOf([ |
| 195 | subagent({ workflow: { phases: [], agents: missingFirst ? agents.toReversed() : agents } }), |
| 196 | ]); |
| 197 | expect(groups[0]?.phases[0]?.title).toBe("Review"); |
| 198 | }); |
| 199 | |
| 200 | it("keeps unphased members with their workflow and excludes unrelated agents", () => { |
| 201 | const runtime = projectedSubagentsToRuntime([ |
| 202 | subagent({ id: "ordinary" }), |
| 203 | subagent({ |
| 204 | id: "__proto__", |
| 205 | workflow: { |
| 206 | phases: [], |
| 207 | agents: [member({ phaseIndex: undefined, phaseTitle: undefined })], |
| 208 | }, |
| 209 | }), |
| 210 | subagent({ id: "constructor", workflow: { phases: [], agents: [] } }), |
| 211 | subagent({ id: "orphan", workflow: { phases: [], agents: [member()] } }), |
| 212 | ]); |
| 213 | const groups = deriveWorkflowGroups(runtime.filter((agent) => agent.id !== "orphan")); |
| 214 | expect(groups.map((group) => group.workflow.id)).toEqual(["__proto__", "constructor"]); |
| 215 | expect(groups[0]?.phases).toEqual([]); |
| 216 | expect(groups[0]?.unphasedMembers.map((agent) => agent.title)).toEqual(["alpha:one"]); |
| 217 | expect(groups[1]?.phases).toEqual([]); |
| 218 | expect(groups[1]?.unphasedMembers).toEqual([]); |
| 219 | }); |
| 220 | }); |