1import { describe, expect, it } from "@effect/vitest";
2
3import type {
4 EnvironmentId,
5 OrchestrationV2ThreadShell,
6 Project,
7 ThreadId,
8} from "@t3tools/contracts";
9import { ProviderInstanceId, RuntimeRequestId } from "@t3tools/contracts";
10import * as DateTime from "effect/DateTime";
11
12import { projectThreadAwarenessV2 } from "./agentAwareness.ts";
13
14const NOW = "2026-05-22T12:00:00.000Z";
15
16const project = {
17 title: "t3code",
18} satisfies Pick<Project, "title">;
19
20describe("projectThreadAwarenessV2", () => {
21 const updatedAt = DateTime.makeUnsafe(NOW);
22 const v2Thread = (
23 overrides: Partial<
24 Pick<
25 OrchestrationV2ThreadShell,
26 | "activityRunStatus"
27 | "status"
28 | "pendingBackgroundTasks"
29 | "pendingRuntimeRequest"
30 | "lineage"
31 >
32 > = {},
33 ) => ({
34 id: "thread-2" as ThreadId,
35 lineage: {
36 rootThreadId: "thread-2" as ThreadId,
37 parentThreadId: null,
38 relationshipToParent: null,
39 },
40 title: "Integrate orchestration",
41 modelSelection: { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5.4" },
42 status: "running" as const,
43 pendingRuntimeRequest: null,
44 updatedAt,
45 ...overrides,
46 });
47
48 it("projects V2 run state", () => {
49 expect(
50 projectThreadAwarenessV2({
51 environmentId: "env-1" as EnvironmentId,
52 project,
53 thread: v2Thread(),
54 }),
55 ).toMatchObject({ phase: "running", headline: "Agent is working" });
56 });
57
58 it.each(["running", "completed", "failed"] as const)(
59 "does not publish %s subagent activity",
60 (status) => {
61 expect(
62 projectThreadAwarenessV2({
63 environmentId: "env-1" as EnvironmentId,
64 project,
65 thread: v2Thread({
66 status,
67 lineage: {
68 rootThreadId: "parent" as ThreadId,
69 parentThreadId: "parent" as ThreadId,
70 relationshipToParent: "subagent",
71 },
72 }),
73 }),
74 ).toBeNull();
75 },
76 );
77
78 it("keeps an older activity run visible over a newer cancelled run", () => {
79 expect(
80 projectThreadAwarenessV2({
81 environmentId: "env-1" as EnvironmentId,
82 project,
83 thread: v2Thread({ status: "cancelled", activityRunStatus: "running" }),
84 }),
85 ).toMatchObject({ phase: "running", headline: "Agent is working" });
86 });
87
88 it.each([
89 ["only a dev server", "completed", [{ taskId: "dev", kind: "command" }]],
90 ["a monitor", "running", [{ taskId: "watch", kind: "monitor" }]],
91 [
92 "a dev server and a subagent",
93 "running",
94 [
95 { taskId: "dev", kind: "command" },
96 { taskId: "review", kind: "subagent" },
97 ],
98 ],
99 ] as const)("reports a completed run waiting on %s as %s", (_case, phase, tasks) => {
100 expect(
101 projectThreadAwarenessV2({
102 environmentId: "env-1" as EnvironmentId,
103 project,
104 thread: v2Thread({ status: "completed", pendingBackgroundTasks: tasks }),
105 }),
106 ).toMatchObject({ phase });
107 });
108
109 it("prioritizes V2 user-input requests", () => {
110 expect(
111 projectThreadAwarenessV2({
112 environmentId: "env-1" as EnvironmentId,
113 project,
114 thread: v2Thread({
115 pendingRuntimeRequest: {
116 id: RuntimeRequestId.make("request-1"),
117 kind: "user_input",
118 createdAt: updatedAt,
119 },
120 }),
121 }),
122 ).toMatchObject({ phase: "waiting_for_input", headline: "Waiting for input" });
123 });
124
125 it("does not present authentication refreshes as user approvals", () => {
126 expect(
127 projectThreadAwarenessV2({
128 environmentId: "env-1" as EnvironmentId,
129 project,
130 thread: v2Thread({
131 pendingRuntimeRequest: {
132 id: RuntimeRequestId.make("request-auth-refresh"),
133 kind: "auth_refresh",
134 createdAt: updatedAt,
135 },
136 }),
137 }),
138 ).toMatchObject({ phase: "running", headline: "Agent is working" });
139 });
140});