1import { assert, describe, it } from "@effect/vitest";
2import {
3 MessageId,
4 NodeId,
5 type OrchestrationV2Subagent,
6 type OrchestrationV2TurnItem,
7 RunId,
8 ThreadId,
9 TurnItemId,
10} from "@t3tools/contracts";
11import * as DateTime from "effect/DateTime";
12
13import { backgroundWorkNotification, notificationTurnItem } from "./notification.ts";
14
15const childThreadId = ThreadId.make("thread:child");
16
17describe("backgroundWorkNotification", () => {
18 it("keeps the generic notification when nothing is known", () => {
19 assert.isNull(backgroundWorkNotification([]));
20 });
21
22 it("names one piece of work and the thread of a subagent", () => {
23 assert.deepEqual(
24 backgroundWorkNotification([
25 {
26 kind: "subagent",
27 label: "Review src/math.ts",
28 outcome: "completed",
29 childThreadId,
30 },
31 ]),
32 {
33 source: { kind: "subagent", childThreadId },
34 outcome: "completed",
35 summary: 'Subagent "Review src/math.ts" finished',
36 },
37 );
38 assert.equal(
39 backgroundWorkNotification([
40 { kind: "command", label: "npm test\nnpm run lint", outcome: "failed", exitCode: 1 },
41 ]).summary,
42 'Command "npm test" failed (exit 1)',
43 );
44 assert.equal(
45 backgroundWorkNotification([{ kind: "monitor", label: "Three ticks", outcome: "updated" }])
46 .summary,
47 'Monitor "Three ticks" reported new output',
48 );
49 });
50
51 it("names every piece of work that ended together, without opening one of them", () => {
52 const notification = backgroundWorkNotification([
53 { kind: "subagent", label: "Agent B", outcome: "cancelled", childThreadId },
54 { kind: "command", label: "Sleep 60 seconds", outcome: "cancelled" },
55 ]);
56 assert.deepEqual(notification, {
57 source: { kind: "background_task" },
58 outcome: "cancelled",
59 summary: 'Subagent "Agent B" and command "Sleep 60 seconds" were stopped',
60 });
61 });
62
63 it("counts work it cannot list", () => {
64 const reports = ["a", "b", "c", "d"].map((label) => ({
65 kind: "subagent" as const,
66 label,
67 outcome: "completed" as const,
68 }));
69 assert.deepEqual(backgroundWorkNotification(reports), {
70 source: { kind: "subagent" },
71 outcome: "completed",
72 summary: "4 subagents finished",
73 });
74 });
75});
76
77describe("notificationTurnItem", () => {
78 const now = DateTime.makeUnsafe("2026-09-27T00:00:00.000Z");
79 const threadId = ThreadId.make("thread:parent");
80 const parentRunId = RunId.make("run:parent");
81 const userMessage: OrchestrationV2TurnItem = {
82 id: TurnItemId.make("item:delivery"),
83 threadId,
84 runId: RunId.make("run:delivery"),
85 nodeId: null,
86 providerThreadId: null,
87 providerTurnId: null,
88 nativeItemRef: null,
89 parentItemId: null,
90 ordinal: 1,
91 status: "completed",
92 title: null,
93 startedAt: now,
94 completedAt: now,
95 updatedAt: now,
96 type: "user_message",
97 messageId: MessageId.make("message:delivery"),
98 inputIntent: "turn_start",
99 text: "Delegated tasks reached terminal states.",
100 attachments: [],
101 createdBy: "agent",
102 creationSource: "server",
103 };
104 const task = (id: string, title: string, status: OrchestrationV2Subagent["status"]) =>
105 ({
106 id: NodeId.make(id),
107 runId: parentRunId,
108 origin: "app_owned",
109 title,
110 prompt: `${title} prompt`,
111 status,
112 childThreadId: ThreadId.make(`thread:${id}`),
113 }) as OrchestrationV2Subagent;
114 const delivery = (taskIds: ReadonlyArray<string>) => ({
115 delegatedCompletion: {
116 parentRunId,
117 generation: 1,
118 taskIds: taskIds.map((taskId) => NodeId.make(taskId)),
119 },
120 });
121 const tasks = [
122 task("a", "Review src/math.ts", "completed"),
123 task("b", "Write tests", "completed"),
124 task("c", "Update docs", "running"),
125 ];
126
127 it("says how many of a run's delegated tasks a delivery reports", () => {
128 const item = notificationTurnItem(userMessage, delivery(["a", "b"]), tasks);
129 assert.equal(item.type, "notification");
130 if (item.type !== "notification") return;
131 assert.equal(item.summary, "2 of 3 delegated tasks finished: Review src/math.ts, Write tests");
132 assert.deepEqual(item.source, {
133 kind: "delegated_task",
134 taskIds: [NodeId.make("a"), NodeId.make("b")],
135 });
136 });
137
138 it("opens the child thread of a single delegated task", () => {
139 const item = notificationTurnItem(userMessage, delivery(["a"]), tasks);
140 assert.deepInclude(item, {
141 type: "notification",
142 summary: 'Delegated task "Review src/math.ts" finished',
143 source: {
144 kind: "delegated_task",
145 taskIds: [NodeId.make("a")],
146 childThreadId: ThreadId.make("thread:a"),
147 },
148 });
149 });
150});