1import { describe, expect, it } from "vite-plus/test";
2import type { OrchestrationV2PendingBackgroundTask } from "@t3tools/contracts";
3import {
4 backgroundWorkHoldsCompletion,
5 derivePendingBackgroundWork,
6 turnItemUpdateCanEndBackgroundWork,
7} from "./orchestrationV2PendingBackgroundWork.ts";
8
9describe("turnItemUpdateCanEndBackgroundWork", () => {
10 it.each([
11 ["a finished subagent", true, { type: "subagent", status: "completed" }],
12 ["a failed background command", true, { type: "command_execution", status: "failed" }],
13 ["an idle dynamic tool", true, { type: "dynamic_tool", status: "idle" }],
14 ["a running command's output", false, { type: "command_execution", status: "running" }],
15 ["a waiting subagent", false, { type: "subagent", status: "waiting" }],
16 ["a finished assistant message", false, { type: "assistant_message", status: "completed" }],
17 ] as const)("%s: %s", (_case, canEnd, item) => {
18 expect(turnItemUpdateCanEndBackgroundWork(item)).toBe(canEnd);
19 });
20});
21
22describe("backgroundWorkHoldsCompletion", () => {
23 const task = (
24 taskId: string,
25 kind: OrchestrationV2PendingBackgroundTask["kind"],
26 ): OrchestrationV2PendingBackgroundTask => ({ taskId, kind });
27
28 it.each([
29 ["nothing pending", false, []],
30 ["a dev server", false, [task("dev", "command")]],
31 ["two long-lived shells", false, [task("web", "command"), task("api", "command")]],
32 ["a subagent", true, [task("review", "subagent")]],
33 ["a monitor", true, [task("watch", "monitor")]],
34 ["a command and a monitor", true, [task("dev", "command"), task("watch", "monitor")]],
35 ["a command and a subagent", true, [task("dev", "command"), task("review", "subagent")]],
36 // Also what an unknown or missing kind decodes to.
37 ["work the provider cannot name", true, [task("opaque", "background_task")]],
38 ] as const)("with %s pending, holds completion: %s", (_case, holds, tasks) => {
39 expect(backgroundWorkHoldsCompletion(tasks)).toBe(holds);
40 });
41});
42
43describe("derivePendingBackgroundWork", () => {
44 it("returns empty while the latest run is not settled", () => {
45 const tasks = derivePendingBackgroundWork({
46 latestRun: { id: "run-1" as never, ordinal: 1, status: "running" },
47 providerThreads: [
48 {
49 id: "pt-1" as never,
50 pendingBackgroundTasks: [{ taskId: "bg-1", description: "sleep 20", kind: "command" }],
51 },
52 ],
53 turnItems: [
54 {
55 id: "item-1" as never,
56 type: "command_execution",
57 status: "running",
58 title: "npm test",
59 nativeItemRef: null,
60 input: "npm test",
61 },
62 ],
63 });
64 expect(tasks).toEqual([]);
65 });
66
67 it("does not keep a settled parent waiting for an idle child", () => {
68 expect(
69 derivePendingBackgroundWork({
70 latestRun: { id: "run-1" as never, ordinal: 1, status: "completed" },
71 providerThreads: [],
72 turnItems: [{ id: "idle-child", type: "subagent", status: "idle", title: "Review" }],
73 }),
74 ).toEqual([]);
75 });
76
77 it("returns pending work when the latest run is waiting (post-success, pre-checkpoint)", () => {
78 const tasks = derivePendingBackgroundWork({
79 latestRun: { id: "run-1" as never, ordinal: 1, status: "waiting" },
80 providerThreads: [{ id: "pt-1" as never }],
81 turnItems: [
82 {
83 id: "item-1" as never,
84 type: "command_execution",
85 status: "running",
86 title: "npm test",
87 nativeItemRef: { nativeId: "cmd-1" },
88 input: "npm test",
89 },
90 ],
91 });
92 expect(tasks).toEqual([{ taskId: "cmd-1", description: "npm test", kind: "command" }]);
93 });
94
95 it("still returns empty when the latest run is running even with background items", () => {
96 const tasks = derivePendingBackgroundWork({
97 latestRun: { id: "run-1" as never, ordinal: 1, status: "running" },
98 providerThreads: [
99 {
100 id: "pt-1" as never,
101 pendingBackgroundTasks: [{ taskId: "bg-1", description: "sleep 20", kind: "command" }],
102 },
103 ],
104 turnItems: [
105 {
106 id: "item-1" as never,
107 type: "subagent",
108 status: "running",
109 title: "review",
110 nativeItemRef: { nativeId: "sub-1" },
111 prompt: "review",
112 },
113 ],
114 });
115 expect(tasks).toEqual([]);
116 });
117
118 it("excludes rolled_back items when the latest run is waiting", () => {
119 const tasks = derivePendingBackgroundWork({
120 latestRun: { id: "run-2" as never, ordinal: 2, status: "waiting" },
121 providerThreads: [{ id: "pt-1" as never }],
122 runs: [
123 { id: "run-1" as never, ordinal: 1, status: "rolled_back" },
124 { id: "run-2" as never, ordinal: 2, status: "waiting" },
125 ],
126 turnItems: [
127 {
128 id: "item-old" as never,
129 type: "command_execution",
130 status: "running",
131 title: "from rolled-back run",
132 runId: "run-1",
133 nativeItemRef: { nativeId: "cmd-old" },
134 input: "sleep 99",
135 },
136 {
137 id: "item-new" as never,
138 type: "command_execution",
139 status: "running",
140 title: "still pending",
141 runId: "run-2",
142 nativeItemRef: { nativeId: "cmd-new" },
143 input: "npm test",
144 },
145 ],
146 });
147 expect(tasks).toEqual([
148 {
149 taskId: "cmd-new",
150 description: "still pending",
151 kind: "command",
152 },
153 ]);
154 });
155
156 it("returns the provider-thread roster after settlement", () => {
157 const tasks = derivePendingBackgroundWork({
158 latestRun: { id: "run-1" as never, ordinal: 1, status: "completed" },
159 providerThreads: [
160 {
161 id: "pt-1" as never,
162 pendingBackgroundTasks: [
163 { taskId: "bg-1", description: "Run Codex review", kind: "command" },
164 ],
165 },
166 ],
167 turnItems: [],
168 activeProviderThreadId: "pt-1",
169 });
170 expect(tasks).toEqual([{ taskId: "bg-1", description: "Run Codex review", kind: "command" }]);
171 });
172
173 it("includes nonterminal turn items and excludes completed ones", () => {
174 const tasks = derivePendingBackgroundWork({
175 latestRun: { id: "run-1" as never, ordinal: 1, status: "completed" },
176 providerThreads: [{ id: "pt-1" as never }],
177 turnItems: [
178 {
179 id: "item-1" as never,
180 type: "command_execution",
181 status: "running",
182 title: "npm test",
183 nativeItemRef: { nativeId: "cmd-1" },
184 input: "npm test",
185 },
186 {
187 id: "item-2" as never,
188 type: "command_execution",
189 status: "completed",
190 title: "done",
191 nativeItemRef: { nativeId: "cmd-2" },
192 input: "echo done",
193 },
194 ],
195 });
196 expect(tasks).toEqual([{ taskId: "cmd-1", description: "npm test", kind: "command" }]);
197 });
198
199 it("trims normalized background-work descriptions", () => {
200 const base = {
201 latestRun: { id: "run-1" as never, ordinal: 1, status: "completed" as const },
202 providerThreads: [
203 {
204 id: "pt-1" as never,
205 pendingBackgroundTasks: [
206 {
207 taskId: "native-task",
208 description: " native background work ",
209 kind: "background_task" as const,
210 },
211 ],
212 },
213 ],
214 };
215 expect(
216 derivePendingBackgroundWork({
217 ...base,
218 turnItems: [
219 {
220 id: "item-command" as never,
221 type: "command_execution",
222 status: "running",
223 title: " npm test ",
224 nativeItemRef: null,
225 },
226 {
227 id: "item-tool" as never,
228 type: "dynamic_tool",
229 status: "running",
230 title: null,
231 nativeItemRef: null,
232 toolName: " browser.search ",
233 } as never,
234 ],
235 }),
236 ).toEqual([
237 {
238 taskId: "native-task",
239 description: "native background work",
240 kind: "background_task",
241 },
242 {
243 taskId: "item-command",
244 description: "npm test",
245 kind: "command",
246 },
247 {
248 taskId: "item-tool",
249 description: "browser.search",
250 kind: "background_task",
251 },
252 ]);
253 });
254
255 it("dedupes roster entries against turn items by native task id", () => {
256 const tasks = derivePendingBackgroundWork({
257 latestRun: { id: "run-1" as never, ordinal: 1, status: "completed" },
258 providerThreads: [
259 {
260 id: "pt-1" as never,
261 pendingBackgroundTasks: [
262 { taskId: "task-9", description: "Agent review", kind: "background_task" },
263 ],
264 },
265 ],
266 turnItems: [
267 {
268 id: "item-sub" as never,
269 type: "subagent",
270 status: "running",
271 title: "Agent review",
272 nativeItemRef: { nativeId: "task-9" },
273 prompt: "review the plan",
274 },
275 ],
276 });
277 expect(tasks).toEqual([
278 { taskId: "task-9", description: "Agent review", kind: "background_task" },
279 ]);
280 });
281
282 it("excludes Grok persistent monitors", () => {
283 const tasks = derivePendingBackgroundWork({
284 latestRun: { id: "run-1" as never, ordinal: 1, status: "completed" },
285 providerThreads: [{ id: "pt-1" as never }],
286 turnItems: [
287 {
288 id: "item-1" as never,
289 type: "dynamic_tool",
290 status: "running",
291 title: "monitor logs",
292 nativeItemRef: { nativeId: "mon-1" },
293 input: { persistent: true, command: "tail -f" },
294 },
295 {
296 id: "item-2" as never,
297 type: "dynamic_tool",
298 status: "running",
299 title: "finite monitor",
300 nativeItemRef: { nativeId: "mon-2" },
301 input: { persistent: false, command: "sleep 5" },
302 },
303 ],
304 });
305 expect(tasks).toEqual([
306 { taskId: "mon-2", description: "finite monitor", kind: "background_task" },
307 ]);
308 });
309
310 it("returns multiple tasks with stable ordering from insertion", () => {
311 const tasks = derivePendingBackgroundWork({
312 latestRun: { id: "run-1" as never, ordinal: 1, status: "completed" },
313 providerThreads: [
314 {
315 id: "pt-1" as never,
316 pendingBackgroundTasks: [
317 { taskId: "bg-1", description: "first", kind: "command" },
318 { taskId: "bg-2", description: "second", kind: "command" },
319 ],
320 },
321 ],
322 turnItems: [
323 {
324 id: "item-1" as never,
325 type: "command_execution",
326 status: "running",
327 title: "third",
328 nativeItemRef: { nativeId: "cmd-3" },
329 input: "third",
330 },
331 ],
332 });
333 expect(tasks.map((task) => task.taskId)).toEqual(["bg-1", "bg-2", "cmd-3"]);
334 });
335
336 it("excludes turn items owned by a rolled_back run", () => {
337 const tasks = derivePendingBackgroundWork({
338 latestRun: { id: "run-1" as never, ordinal: 1, status: "rolled_back" },
339 providerThreads: [{ id: "pt-1" as never }],
340 runs: [{ id: "run-1" as never, ordinal: 1, status: "rolled_back" }],
341 turnItems: [
342 {
343 id: "item-1" as never,
344 type: "command_execution",
345 status: "running",
346 title: "abandoned",
347 runId: "run-1",
348 nativeItemRef: { nativeId: "cmd-1" },
349 input: "sleep 99",
350 },
351 ],
352 });
353 expect(tasks).toEqual([]);
354 });
355
356 it("returns empty when the latest run is rolled_back even with a nonempty roster", () => {
357 const tasks = derivePendingBackgroundWork({
358 latestRun: { id: "run-1" as never, ordinal: 1, status: "rolled_back" },
359 providerThreads: [
360 {
361 id: "pt-1" as never,
362 pendingBackgroundTasks: [{ taskId: "bg-1", description: "sleep 20", kind: "command" }],
363 },
364 ],
365 turnItems: [
366 {
367 id: "item-1" as never,
368 type: "command_execution",
369 status: "running",
370 title: "abandoned",
371 runId: "run-1",
372 nativeItemRef: { nativeId: "cmd-1" },
373 input: "sleep 99",
374 },
375 ],
376 });
377 expect(tasks).toEqual([]);
378 });
379
380 it("excludes an older rolled_back nonterminal item when the latest run is completed", () => {
381 const tasks = derivePendingBackgroundWork({
382 latestRun: { id: "run-2" as never, ordinal: 2, status: "completed" },
383 providerThreads: [{ id: "pt-1" as never }],
384 runs: [
385 { id: "run-1" as never, ordinal: 1, status: "rolled_back" },
386 { id: "run-2" as never, ordinal: 2, status: "completed" },
387 ],
388 turnItems: [
389 {
390 id: "item-old" as never,
391 type: "command_execution",
392 status: "running",
393 title: "from rolled-back run",
394 runId: "run-1",
395 nativeItemRef: { nativeId: "cmd-old" },
396 input: "sleep 99",
397 },
398 {
399 id: "item-new" as never,
400 type: "command_execution",
401 status: "running",
402 title: "still pending",
403 runId: "run-2",
404 nativeItemRef: { nativeId: "cmd-new" },
405 input: "npm test",
406 },
407 ],
408 });
409 expect(tasks).toEqual([
410 {
411 taskId: "cmd-new",
412 description: "still pending",
413 kind: "command",
414 },
415 ]);
416 });
417
418 it("does not reclassify an older active run as background work", () => {
419 const tasks = derivePendingBackgroundWork({
420 latestRun: { id: "run-2" as never, ordinal: 2, status: "cancelled" },
421 providerThreads: [
422 {
423 id: "pt-1" as never,
424 pendingBackgroundTasks: [{ taskId: "provider-task", kind: "background_task" }],
425 },
426 ],
427 runs: [
428 { id: "run-1" as never, ordinal: 1, status: "running" },
429 { id: "run-2" as never, ordinal: 2, status: "cancelled" },
430 ],
431 turnItems: [
432 {
433 id: "item-active" as never,
434 type: "command_execution",
435 status: "running",
436 title: "foreground work",
437 runId: "run-1",
438 nativeItemRef: { nativeId: "cmd-active" },
439 input: "vp check",
440 },
441 ],
442 });
443
444 expect(tasks).toEqual([]);
445 });
446
447 it("includes items with a null run id even when runs list has rolled_back rows", () => {
448 const tasks = derivePendingBackgroundWork({
449 latestRun: { id: "run-1" as never, ordinal: 1, status: "completed" },
450 providerThreads: [{ id: "pt-1" as never }],
451 runs: [{ id: "run-1" as never, ordinal: 1, status: "rolled_back" }],
452 turnItems: [
453 {
454 id: "item-null-run" as never,
455 type: "command_execution",
456 status: "running",
457 title: "orphan item",
458 runId: null,
459 nativeItemRef: { nativeId: "cmd-null" },
460 input: "echo orphan",
461 },
462 ],
463 });
464 expect(tasks).toEqual([
465 {
466 taskId: "cmd-null",
467 description: "orphan item",
468 kind: "command",
469 },
470 ]);
471 });
472});
473
474describe("derivePendingBackgroundWork kinds", () => {
475 it("keeps the roster's kinds and names turn items by their type", () => {
476 const tasks = derivePendingBackgroundWork({
477 latestRun: { id: "run-1" as never, ordinal: 1, status: "completed" },
478 providerThreads: [
479 {
480 id: "pt-1" as never,
481 pendingBackgroundTasks: [
482 { taskId: "bash", kind: "command" },
483 { taskId: "watch", kind: "monitor" },
484 { taskId: "workflow", kind: "background_task" },
485 ],
486 },
487 ],
488 turnItems: [
489 {
490 id: "item-sub" as never,
491 type: "subagent",
492 status: "running",
493 title: "Review src/math.ts",
494 nativeItemRef: { nativeId: "sub" },
495 childThreadId: "thread:child" as never,
496 },
497 {
498 id: "item-cmd" as never,
499 type: "command_execution",
500 status: "running",
501 title: null,
502 nativeItemRef: { nativeId: "cmd" },
503 input: "npm test",
504 },
505 ],
506 });
507 expect(tasks).toEqual([
508 { taskId: "bash", kind: "command" },
509 { taskId: "watch", kind: "monitor" },
510 { taskId: "workflow", kind: "background_task" },
511 {
512 taskId: "sub",
513 description: "Review src/math.ts",
514 kind: "subagent",
515 childThreadId: "thread:child",
516 },
517 { taskId: "cmd", description: "npm test", kind: "command" },
518 ]);
519 });
520
521 describe("pull request watches", () => {
522 const watch = {
523 startedAt: "2026-10-05T00:00:00.000Z",
524 headSha: null,
525 failedChecks: [],
526 passed: false,
527 passedChecks: [],
528 remarksThrough: "2026-10-05T00:00:00.000Z",
529 remarkIds: [],
530 conflicting: false,
531 wakes: 0,
532 };
533 const link = (
534 number: number,
535 extra: { source?: "agent" | "stack-dismissed"; watched?: boolean },
536 ) => ({
537 host: "github.com",
538 repository: "acme/app",
539 number,
540 url: `https://github.com/acme/app/pull/${number}`,
541 source: extra.source ?? "agent",
542 ...(extra.watched === false ? {} : { watch }),
543 });
544 const pullRequests = [
545 link(1, {}),
546 link(2, { watched: false }),
547 link(3, { source: "stack-dismissed" }),
548 ];
549
550 it("keeps a settled run waiting on each visible watch, as a monitor", () => {
551 const tasks = derivePendingBackgroundWork({
552 latestRun: { id: "run-1" as never, ordinal: 1, status: "completed" },
553 providerThreads: [],
554 turnItems: [],
555 pullRequests,
556 });
557 expect(tasks).toEqual([
558 {
559 taskId: "pull-request-watch:github.com/acme/app#1",
560 description: "Watching pull request #1",
561 kind: "monitor",
562 },
563 ]);
564 expect(backgroundWorkHoldsCompletion(tasks)).toBe(true);
565 });
566
567 it("keeps a thread that never ran waiting on its watch", () => {
568 expect(
569 derivePendingBackgroundWork({
570 latestRun: null,
571 providerThreads: [],
572 turnItems: [],
573 pullRequests,
574 }).map((task) => task.taskId),
575 ).toEqual(["pull-request-watch:github.com/acme/app#1"]);
576 });
577
578 it("lists no watch while a run is active", () => {
579 expect(
580 derivePendingBackgroundWork({
581 latestRun: { id: "run-1" as never, ordinal: 1, status: "running" },
582 providerThreads: [],
583 turnItems: [],
584 pullRequests,
585 }),
586 ).toEqual([]);
587 });
588 });
589});