Skip to content
Source
Sign inSign up
Open History to see the last commit

orchestrationV2PendingBackgroundWork.test.ts

487 lines15.3 KB

1import { describe, expect, it } from "vite-plus/test";
2import { ProviderInstanceId, ProviderThreadId } from "@t3tools/contracts";
3import {
4 backgroundWorkHoldsCompletion,
5 derivePendingBackgroundWork,
6 monitorHoldsWakeCompletion,
7} from "./orchestrationV2PendingBackgroundWork.ts";
8​
9describe("backgroundWorkHoldsCompletion", () => {
10 it.each([
11 ["a dev server", false, [{ taskId: "dev", kind: "command" }]],
12 ["a dev server's watchdog monitor", false, [{ taskId: "watch", kind: "monitor" }]],
13 ["a command the agent waits on", true, [{ taskId: "tests", kind: "command", awaited: true }]],
14 ["a subagent", true, [{ taskId: "review", kind: "subagent" }]],
15 ] as const)("with %s pending, holds completion: %s", (_case, holds, tasks) => {
16 expect(backgroundWorkHoldsCompletion(tasks)).toBe(holds);
17 });
18});
19​
20describe("derivePendingBackgroundWork", () => {
21 it("takes each subagent item's instance from its joined owner", () => {
22 const owner = ProviderInstanceId.make("claude-owner");
23 const tasks = derivePendingBackgroundWork({
24 latestRun: { id: "run-1" as never, ordinal: 1, status: "completed" },
25 providerThreads: [],
26 turnItems: [
27 {
28 id: "owned-item",
29 owningProviderInstanceId: owner,
30 type: "subagent",
31 status: "running",
32 title: null,
33 },
34 { id: "unknown-item", type: "subagent", status: "running", title: null },
35 ],
36 });
37 expect(tasks).toEqual([
38 { taskId: "owned-item", kind: "subagent", providerInstanceId: owner },
39 { taskId: "unknown-item", kind: "subagent" },
40 ]);
41 });
42​
43 it("takes a roster subagent's instance from its owning provider thread", () => {
44 const owner = ProviderInstanceId.make("claude-owner");
45 const tasks = derivePendingBackgroundWork({
46 latestRun: { id: "run-1" as never, ordinal: 1, status: "completed" },
47 providerThreads: [
48 {
49 id: ProviderThreadId.make("pt-owner"),
50 providerInstanceId: owner,
51 pendingBackgroundTasks: [
52 {
53 taskId: "roster-item",
54 kind: "subagent",
55 providerInstanceId: ProviderInstanceId.make("other"),
56 },
57 ],
58 },
59 ],
60 turnItems: [],
61 });
62 expect(tasks).toEqual([{ taskId: "roster-item", kind: "subagent", providerInstanceId: owner }]);
63 });
64​
65 it("returns empty while the latest run is not settled", () => {
66 const tasks = derivePendingBackgroundWork({
67 latestRun: { id: "run-1" as never, ordinal: 1, status: "running" },
68 providerThreads: [
69 {
70 id: "pt-1" as never,
71 pendingBackgroundTasks: [{ taskId: "bg-1", description: "sleep 20", kind: "command" }],
72 },
73 ],
74 turnItems: [
75 {
76 id: "item-1" as never,
77 type: "command_execution",
78 status: "running",
79 title: "npm test",
80 nativeItemRef: null,
81 input: "npm test",
82 },
83 ],
84 });
85 expect(tasks).toEqual([]);
86 });
87​
88 it("does not keep a settled parent waiting for an idle child", () => {
89 expect(
90 derivePendingBackgroundWork({
91 latestRun: { id: "run-1" as never, ordinal: 1, status: "completed" },
92 providerThreads: [],
93 turnItems: [{ id: "idle-child", type: "subagent", status: "idle", title: "Review" }],
94 }),
95 ).toEqual([]);
96 });
97​
98 it("returns pending work when the latest run is waiting (post-success, pre-checkpoint)", () => {
99 const tasks = derivePendingBackgroundWork({
100 latestRun: { id: "run-1" as never, ordinal: 1, status: "waiting" },
101 providerThreads: [{ id: "pt-1" as never }],
102 turnItems: [
103 {
104 id: "item-1" as never,
105 type: "command_execution",
106 status: "running",
107 title: "npm test",
108 nativeItemRef: { nativeId: "cmd-1" },
109 input: "npm test",
110 },
111 ],
112 });
113 expect(tasks).toEqual([{ taskId: "cmd-1", description: "npm test", kind: "command" }]);
114 });
115​
116 it("excludes rolled_back items when the latest run is waiting", () => {
117 const tasks = derivePendingBackgroundWork({
118 latestRun: { id: "run-2" as never, ordinal: 2, status: "waiting" },
119 providerThreads: [{ id: "pt-1" as never }],
120 runs: [
121 { id: "run-1" as never, ordinal: 1, status: "rolled_back" },
122 { id: "run-2" as never, ordinal: 2, status: "waiting" },
123 ],
124 turnItems: [
125 {
126 id: "item-old" as never,
127 type: "command_execution",
128 status: "running",
129 title: "from rolled-back run",
130 runId: "run-1",
131 nativeItemRef: { nativeId: "cmd-old" },
132 input: "sleep 99",
133 },
134 {
135 id: "item-new" as never,
136 type: "command_execution",
137 status: "running",
138 title: "still pending",
139 runId: "run-2",
140 nativeItemRef: { nativeId: "cmd-new" },
141 input: "npm test",
142 },
143 ],
144 });
145 expect(tasks).toEqual([
146 {
147 taskId: "cmd-new",
148 description: "still pending",
149 kind: "command",
150 },
151 ]);
152 });
153​
154 it("includes nonterminal turn items and excludes completed ones", () => {
155 const tasks = derivePendingBackgroundWork({
156 latestRun: { id: "run-1" as never, ordinal: 1, status: "completed" },
157 providerThreads: [{ id: "pt-1" as never }],
158 turnItems: [
159 {
160 id: "item-1" as never,
161 type: "command_execution",
162 status: "running",
163 title: "npm test",
164 nativeItemRef: { nativeId: "cmd-1" },
165 input: "npm test",
166 },
167 {
168 id: "item-2" as never,
169 type: "command_execution",
170 status: "completed",
171 title: "done",
172 nativeItemRef: { nativeId: "cmd-2" },
173 input: "echo done",
174 },
175 ],
176 });
177 expect(tasks).toEqual([{ taskId: "cmd-1", description: "npm test", kind: "command" }]);
178 });
179​
180 it("dedupes roster entries against turn items by native task id", () => {
181 const tasks = derivePendingBackgroundWork({
182 latestRun: { id: "run-1" as never, ordinal: 1, status: "completed" },
183 providerThreads: [
184 {
185 id: "pt-1" as never,
186 pendingBackgroundTasks: [
187 { taskId: "task-9", description: "Agent review", kind: "background_task" },
188 ],
189 },
190 ],
191 turnItems: [
192 {
193 id: "item-sub" as never,
194 type: "subagent",
195 status: "running",
196 title: "Agent review",
197 nativeItemRef: { nativeId: "task-9" },
198 prompt: "review the plan",
199 },
200 ],
201 });
202 expect(tasks).toEqual([
203 { taskId: "task-9", description: "Agent review", kind: "background_task" },
204 ]);
205 });
206​
207 it("excludes Grok persistent monitors", () => {
208 const tasks = derivePendingBackgroundWork({
209 latestRun: { id: "run-1" as never, ordinal: 1, status: "completed" },
210 providerThreads: [{ id: "pt-1" as never }],
211 turnItems: [
212 {
213 id: "item-1" as never,
214 type: "dynamic_tool",
215 status: "running",
216 title: "monitor logs",
217 nativeItemRef: { nativeId: "mon-1" },
218 input: { persistent: true, command: "tail -f" },
219 },
220 {
221 id: "item-2" as never,
222 type: "dynamic_tool",
223 status: "running",
224 title: "finite monitor",
225 nativeItemRef: { nativeId: "mon-2" },
226 input: { persistent: false, command: "sleep 5" },
227 },
228 ],
229 });
230 expect(tasks).toEqual([
231 { taskId: "mon-2", description: "finite monitor", kind: "background_task" },
232 ]);
233 });
234​
235 it("returns empty when the latest run is rolled_back even with a nonempty roster", () => {
236 const tasks = derivePendingBackgroundWork({
237 latestRun: { id: "run-1" as never, ordinal: 1, status: "rolled_back" },
238 providerThreads: [
239 {
240 id: "pt-1" as never,
241 pendingBackgroundTasks: [{ taskId: "bg-1", description: "sleep 20", kind: "command" }],
242 },
243 ],
244 turnItems: [
245 {
246 id: "item-1" as never,
247 type: "command_execution",
248 status: "running",
249 title: "abandoned",
250 runId: "run-1",
251 nativeItemRef: { nativeId: "cmd-1" },
252 input: "sleep 99",
253 },
254 ],
255 });
256 expect(tasks).toEqual([]);
257 });
258​
259 it("does not reclassify an older active run as background work", () => {
260 const tasks = derivePendingBackgroundWork({
261 latestRun: { id: "run-2" as never, ordinal: 2, status: "cancelled" },
262 providerThreads: [
263 {
264 id: "pt-1" as never,
265 pendingBackgroundTasks: [{ taskId: "provider-task", kind: "background_task" }],
266 },
267 ],
268 runs: [
269 { id: "run-1" as never, ordinal: 1, status: "running" },
270 { id: "run-2" as never, ordinal: 2, status: "cancelled" },
271 ],
272 turnItems: [
273 {
274 id: "item-active" as never,
275 type: "command_execution",
276 status: "running",
277 title: "foreground work",
278 runId: "run-1",
279 nativeItemRef: { nativeId: "cmd-active" },
280 input: "vp check",
281 },
282 ],
283 });
284​
285 expect(tasks).toEqual([]);
286 });
287​
288 it("includes items with a null run id even when runs list has rolled_back rows", () => {
289 const tasks = derivePendingBackgroundWork({
290 latestRun: { id: "run-1" as never, ordinal: 1, status: "completed" },
291 providerThreads: [{ id: "pt-1" as never }],
292 runs: [{ id: "run-1" as never, ordinal: 1, status: "rolled_back" }],
293 turnItems: [
294 {
295 id: "item-null-run" as never,
296 type: "command_execution",
297 status: "running",
298 title: "orphan item",
299 runId: null,
300 nativeItemRef: { nativeId: "cmd-null" },
301 input: "echo orphan",
302 },
303 ],
304 });
305 expect(tasks).toEqual([
306 {
307 taskId: "cmd-null",
308 description: "orphan item",
309 kind: "command",
310 },
311 ]);
312 });
313});
314​
315describe("derivePendingBackgroundWork kinds", () => {
316 it("keeps the roster's kinds and names turn items by their type", () => {
317 const tasks = derivePendingBackgroundWork({
318 latestRun: { id: "run-1" as never, ordinal: 1, status: "completed" },
319 providerThreads: [
320 {
321 id: "pt-1" as never,
322 pendingBackgroundTasks: [
323 { taskId: "bash", kind: "command" },
324 { taskId: "tests", kind: "command", awaited: true },
325 { taskId: "watch", kind: "monitor" },
326 { taskId: "workflow", kind: "background_task" },
327 ],
328 },
329 ],
330 turnItems: [
331 {
332 id: "item-sub" as never,
333 type: "subagent",
334 status: "running",
335 title: "Review src/math.ts",
336 nativeItemRef: { nativeId: "sub" },
337 childThreadId: "thread:child" as never,
338 },
339 {
340 id: "item-cmd" as never,
341 type: "command_execution",
342 status: "running",
343 title: null,
344 nativeItemRef: { nativeId: "cmd" },
345 input: "npm test",
346 },
347 ],
348 });
349 expect(tasks).toEqual([
350 { taskId: "bash", kind: "command" },
351 { taskId: "tests", kind: "command", awaited: true },
352 { taskId: "watch", kind: "monitor" },
353 { taskId: "workflow", kind: "background_task" },
354 {
355 taskId: "sub",
356 description: "Review src/math.ts",
357 kind: "subagent",
358 childThreadId: "thread:child",
359 },
360 { taskId: "cmd", description: "npm test", kind: "command" },
361 ]);
362 });
363​
364 describe("pull request watches", () => {
365 const watch = {
366 startedAt: "2026-10-05T00:00:00.000Z",
367 headSha: null,
368 failedChecks: [],
369 passed: false,
370 passedChecks: [],
371 remarksThrough: "2026-10-05T00:00:00.000Z",
372 remarkIds: [],
373 conflicting: false,
374 wakes: 0,
375 };
376 const link = (
377 number: number,
378 extra: {
379 source?: "agent" | "stack-dismissed";
380 watched?: boolean;
381 state?: "open" | "closed" | "merged";
382 },
383 ) => ({
384 host: "github.com",
385 repository: "acme/app",
386 number,
387 url: `https://github.com/acme/app/pull/${number}`,
388 source: extra.source ?? "agent",
389 snapshot: extra.state === undefined ? null : { state: extra.state },
390 ...(extra.watched === false ? {} : { watch }),
391 });
392 const pullRequests = [
393 link(1, {}),
394 link(2, { watched: false }),
395 link(3, { source: "stack-dismissed" }),
396 link(4, { state: "merged" }),
397 link(5, { state: "closed" }),
398 ];
399​
400 it("keeps a settled run waiting on each visible watch, as an awaited monitor", () => {
401 const tasks = derivePendingBackgroundWork({
402 latestRun: { id: "run-1" as never, ordinal: 1, status: "completed" },
403 providerThreads: [],
404 turnItems: [],
405 pullRequests,
406 });
407 expect(tasks).toEqual([
408 {
409 taskId: "pull-request-watch:github.com/acme/app#1",
410 description: "Watching pull request #1",
411 kind: "monitor",
412 awaited: true,
413 },
414 {
415 taskId: "pull-request-watch:github.com/acme/app#5",
416 description: "Watching pull request #5",
417 kind: "monitor",
418 awaited: true,
419 },
420 ]);
421 expect(backgroundWorkHoldsCompletion(tasks)).toBe(true);
422 });
423​
424 it("keeps a thread that never ran waiting on its watch", () => {
425 expect(
426 derivePendingBackgroundWork({
427 latestRun: null,
428 providerThreads: [],
429 turnItems: [],
430 pullRequests,
431 }).map((task) => task.taskId),
432 ).toEqual([
433 "pull-request-watch:github.com/acme/app#1",
434 "pull-request-watch:github.com/acme/app#5",
435 ]);
436 });
437​
438 it("stops a watch whose checks all finished from holding the thread, while still listing it", () => {
439 const tasks = derivePendingBackgroundWork({
440 latestRun: { id: "run-1" as never, ordinal: 1, status: "completed" },
441 providerThreads: [],
442 turnItems: [],
443 pullRequests: [{ ...link(1, {}), watch: { ...watch, passed: true, checksPending: false } }],
444 });
445 expect(tasks).toEqual([
446 {
447 taskId: "pull-request-watch:github.com/acme/app#1",
448 description: "Watching pull request #1",
449 kind: "monitor",
450 },
451 ]);
452 expect(backgroundWorkHoldsCompletion(tasks)).toBe(false);
453 // Unlike a monitor that wakes per line, it lets a comment wake's run complete too.
454 expect(
455 monitorHoldsWakeCompletion({
456 latestRunBackgroundWake: true,
457 pendingBackgroundTasks: tasks,
458 }),
459 ).toBe(false);
460 expect(
461 monitorHoldsWakeCompletion({
462 latestRunBackgroundWake: true,
463 pendingBackgroundTasks: [...tasks, { taskId: "tail", kind: "monitor" }],
464 }),
465 ).toBe(true);
466​
467 const running = derivePendingBackgroundWork({
468 latestRun: { id: "run-1" as never, ordinal: 1, status: "completed" },
469 providerThreads: [],
470 turnItems: [],
471 pullRequests: [{ ...link(1, {}), watch: { ...watch, checksPending: true } }],
472 });
473 expect(backgroundWorkHoldsCompletion(running)).toBe(true);
474 });
475​
476 it("lists no watch while a run is active", () => {
477 expect(
478 derivePendingBackgroundWork({
479 latestRun: { id: "run-1" as never, ordinal: 1, status: "running" },
480 providerThreads: [],
481 turnItems: [],
482 pullRequests,
483 }),
484 ).toEqual([]);
485 });
486 });
487});