1import {
2 TurnItemId,
3 NodeId,
4 MessageId,
5 ProviderInstanceId,
6 ProviderThreadId,
7 ProviderSessionId,
8 ProviderDriverKind,
9 ProviderTurnId,
10 RunAttemptId,
11 RunId,
12 RuntimeRequestId,
13 ThreadId,
14 type OrchestrationV2ExecutionNode,
15 type OrchestrationV2RunStatus,
16 type OrchestrationV2RuntimeRequest,
17} from "@t3tools/contracts";
18import * as DateTime from "effect/DateTime";
19import { createModelCapabilities, createModelSelection } from "@t3tools/shared/model";
20import { usageLimitBlockedRun } from "@t3tools/shared/orchestrationV2ThreadError";
21import { describe, expect, it } from "vite-plus/test";
22
23import { v2Projection } from "./orchestrationV2TestFixtures.ts";
24import {
25 canResumeThread,
26 deriveReportedModelSelection,
27 deriveLatestThreadRun,
28 deriveProviderSubagentStatus,
29 deriveProviderExtensionStatuses,
30 deriveResumableRunId,
31 deriveRunlessWorkStartedAt,
32 deriveThreadActivityRun,
33 deriveThreadRuntime,
34 formatModelSelectionEffort,
35 isBackgroundWorkStopPending,
36 presentPendingBackgroundWork,
37 presentProviderGoal,
38 presentRunStop,
39 threadRuntimeHasInterruptibleRun,
40} from "./threadExecution.ts";
41
42const now = DateTime.makeUnsafe("2026-07-28T10:00:00.000Z");
43
44function request(
45 id: string,
46 status: OrchestrationV2RuntimeRequest["status"],
47): OrchestrationV2RuntimeRequest {
48 return {
49 id: RuntimeRequestId.make(id),
50 nodeId: NodeId.make(`node-${id}`),
51 providerTurnId: null,
52 nativeRequestRef: null,
53 kind: "command",
54 status,
55 responseCapability: { type: "not_resumable", reason: "Session ended." },
56 createdAt: now,
57 resolvedAt: status === "pending" ? null : now,
58 };
59}
60
61function run(id: string, ordinal: number, status: OrchestrationV2RunStatus) {
62 return {
63 id: RunId.make(id),
64 threadId: v2Projection.thread.id,
65 ordinal,
66 providerInstanceId: v2Projection.thread.providerInstanceId,
67 modelSelection: v2Projection.thread.modelSelection,
68 providerThreadId: null,
69 userMessageId: MessageId.make(`message-${id}`),
70 rootNodeId: null,
71 activeAttemptId: null,
72 status,
73 requestedAt: now,
74 startedAt: status === "queued" ? null : now,
75 completedAt: null,
76 checkpointId: null,
77 contextHandoffId: null,
78 };
79}
80
81describe("thread execution presentation", () => {
82 it("shows extension statuses only from the selected provider's current live session", () => {
83 const instanceId = v2Projection.thread.modelSelection.instanceId;
84 const providerThreadId = ProviderThreadId.make("native-status-thread");
85 const sessionId = ProviderSessionId.make("native-status-session");
86 const statuses = [{ key: "watch", text: "Watching" }];
87 const projection = {
88 thread: { ...v2Projection.thread, activeProviderThreadId: providerThreadId },
89 providerThreads: [
90 { id: providerThreadId, providerSessionId: sessionId, providerInstanceId: instanceId },
91 ],
92 providerSessions: [
93 {
94 id: sessionId,
95 providerInstanceId: instanceId,
96 status: "ready" as const,
97 extensionStatuses: statuses,
98 },
99 ],
100 };
101 expect(deriveProviderExtensionStatuses(projection)).toEqual(statuses);
102 expect(
103 deriveProviderExtensionStatuses({
104 ...projection,
105 providerSessions: [
106 ...projection.providerSessions,
107 {
108 ...projection.providerSessions[0]!,
109 id: ProviderSessionId.make("unrelated"),
110 extensionStatuses: [{ key: "wrong", text: "Wrong session" }],
111 },
112 ],
113 }),
114 ).toEqual(statuses);
115 expect(
116 deriveProviderExtensionStatuses({
117 ...projection,
118 providerSessions: [{ ...projection.providerSessions[0]!, status: "stopped" }],
119 }),
120 ).toEqual([]);
121 expect(
122 deriveProviderExtensionStatuses({
123 ...projection,
124 providerSessions: [{ ...projection.providerSessions[0]!, status: "error" }],
125 }),
126 ).toEqual(statuses);
127 expect(
128 deriveProviderExtensionStatuses({
129 ...projection,
130 providerSessions: [
131 { ...projection.providerSessions[0]!, status: "error", extensionStatuses: [] },
132 ],
133 }),
134 ).toEqual([]);
135 expect(
136 deriveProviderExtensionStatuses({
137 ...projection,
138 providerSessions: [{ ...projection.providerSessions[0]!, extensionStatuses: undefined }],
139 }),
140 ).toEqual([]);
141 expect(
142 deriveProviderExtensionStatuses({
143 ...projection,
144 thread: {
145 ...projection.thread,
146 modelSelection: { instanceId: ProviderInstanceId.make("other"), model: "default" },
147 },
148 }),
149 ).toEqual([]);
150 expect(deriveProviderExtensionStatuses(null)).toEqual([]);
151 });
152
153 it("derives the current root failure without inheriting errors from children or previous runs", () => {
154 const failed = { ...run("limited", 1, "failed"), rootNodeId: NodeId.make("root") };
155 const item = {
156 id: TurnItemId.make("limit-error"),
157 threadId: v2Projection.thread.id,
158 runId: failed.id,
159 nodeId: failed.rootNodeId,
160 providerThreadId: null,
161 providerTurnId: null,
162 nativeItemRef: null,
163 parentItemId: null,
164 ordinal: 1,
165 type: "error" as const,
166 status: "failed" as const,
167 title: "Usage limit reached",
168 startedAt: now,
169 completedAt: now,
170 updatedAt: now,
171 failure: {
172 class: "usage_limit" as const,
173 message: "Plan limit reached",
174 code: "usageLimitExceeded",
175 retryable: null,
176 },
177 };
178 const projection = { ...v2Projection, runs: [failed], turnItems: [item] };
179 expect(deriveThreadRuntime(projection)).toMatchObject({
180 lastError: "Plan limit reached",
181 lastErrorClass: "usage_limit",
182 });
183 expect(deriveResumableRunId(projection, deriveThreadRuntime(projection))).toBe(failed.id);
184 expect(
185 deriveThreadRuntime({
186 ...projection,
187 turnItems: [{ ...item, nodeId: NodeId.make("child") }],
188 }),
189 ).toMatchObject({ lastError: null, lastErrorClass: null });
190 expect(
191 deriveThreadRuntime({
192 ...projection,
193 runs: [{ ...failed, rootNodeId: NodeId.make("new-root") }],
194 }),
195 ).toMatchObject({ lastError: null, lastErrorClass: null });
196 expect(
197 deriveThreadRuntime({ ...projection, runs: [failed, run("new", 2, "running")] }),
198 ).toMatchObject({ status: "running", lastError: null, lastErrorClass: null });
199 });
200
201 // regression: #15029 working timers no longer reset on every background wake
202 it("counts a wake run's activity from the start of the work it continues", () => {
203 const workStartedAt = DateTime.makeUnsafe("2026-07-28T09:20:00.000Z");
204 const wake = { ...run("wake", 2, "running"), workStartedAt };
205 expect(
206 deriveThreadRuntime({ ...v2Projection, runs: [run("prompt", 1, "completed"), wake] })
207 ?.activityStartedAt,
208 ).toBe("2026-07-28T09:20:00.000Z");
209 expect(
210 deriveThreadRuntime({ ...v2Projection, runs: [run("prompt", 1, "running")] })
211 ?.activityStartedAt,
212 ).toBe("2026-07-28T10:00:00.000Z");
213 });
214
215 it("keeps a subscription limit visible while later messages stay queued", () => {
216 const failed = {
217 ...run("limited", 1, "failed"),
218 rootNodeId: NodeId.make("root"),
219 completedAt: now,
220 };
221 const queued = run("queued", 2, "queued");
222 const item = {
223 id: TurnItemId.make("limit-error"),
224 threadId: v2Projection.thread.id,
225 runId: failed.id,
226 nodeId: failed.rootNodeId,
227 providerThreadId: null,
228 providerTurnId: null,
229 nativeItemRef: null,
230 parentItemId: null,
231 ordinal: 1,
232 type: "error" as const,
233 status: "failed" as const,
234 title: "Usage limit reached",
235 startedAt: now,
236 completedAt: now,
237 updatedAt: now,
238 failure: {
239 class: "usage_limit" as const,
240 message: "Plan limit reached",
241 code: "usageLimitExceeded",
242 retryable: null,
243 },
244 };
245 const projection = { ...v2Projection, runs: [failed, queued], turnItems: [item] };
246
247 expect(deriveLatestThreadRun(projection)?.runId).toBe(failed.id);
248 expect(deriveThreadActivityRun(projection)?.runId).toBe(failed.id);
249 expect(deriveThreadRuntime(projection)).toMatchObject({
250 status: "failed",
251 lastError: "Plan limit reached",
252 lastErrorClass: "usage_limit",
253 });
254 expect(deriveResumableRunId(projection, deriveThreadRuntime(projection))).toBe(failed.id);
255 const cancelledQueued = {
256 ...run("cancelled-queued", 3, "cancelled"),
257 startedAt: null,
258 completedAt: now,
259 };
260 expect(
261 usageLimitBlockedRun([failed, queued, cancelledQueued], projection.turnItems, null)?.id,
262 ).toBe(failed.id);
263 expect(
264 deriveThreadRuntime({ ...projection, runs: [failed, queued, cancelledQueued] }),
265 ).toMatchObject({ status: "failed", lastErrorClass: "usage_limit" });
266 const providerError = {
267 ...projection,
268 turnItems: [{ ...item, failure: { ...item.failure, class: "provider_error" as const } }],
269 };
270 expect(deriveThreadRuntime(providerError)).toMatchObject({
271 status: "queued",
272 lastErrorClass: null,
273 });
274 expect(deriveResumableRunId(providerError, deriveThreadRuntime(providerError))).toBeNull();
275 });
276
277 it("keeps live activity attached to an executing run when a newer run is queued", () => {
278 const runningRun = run("run-running", 1, "running");
279 const queuedRun = run("run-queued", 2, "queued");
280 const projection = { ...v2Projection, runs: [queuedRun, runningRun], updatedAt: now };
281
282 expect(deriveLatestThreadRun(projection)?.runId).toBe(queuedRun.id);
283 expect(deriveThreadActivityRun(projection)).toMatchObject({
284 runId: runningRun.id,
285 status: "running",
286 });
287
288 const runtime = deriveThreadRuntime(projection);
289 expect(runtime).toMatchObject({
290 status: "running",
291 activeRunId: runningRun.id,
292 });
293 expect(threadRuntimeHasInterruptibleRun(runtime)).toBe(true);
294 });
295
296 it("marks an open run whose provider turn already ended", () => {
297 const attemptId = RunAttemptId.make("attempt-open");
298 const openRun = { ...run("run-open", 1, "running"), activeAttemptId: attemptId };
299 const providerTurn = (status: "running" | "failed") => ({
300 id: ProviderTurnId.make("provider-turn-open"),
301 providerThreadId: ProviderThreadId.make("provider-thread-open"),
302 nodeId: NodeId.make("node-open"),
303 runAttemptId: attemptId,
304 nativeTurnRef: null,
305 ordinal: 1,
306 status,
307 startedAt: now,
308 completedAt: status === "running" ? null : now,
309 });
310 const projection = (status: "running" | "failed") => ({
311 ...v2Projection,
312 runs: [openRun],
313 providerTurns: [providerTurn(status)],
314 updatedAt: now,
315 });
316
317 expect(deriveThreadActivityRun(projection("running"))?.turnEnded).toBeUndefined();
318 expect(deriveThreadActivityRun(projection("failed"))).toMatchObject({
319 status: "running",
320 turnEnded: true,
321 });
322 });
323
324 // The server marks a run running before the provider opens, which can take minutes.
325 it("labels an open run whose provider has not started a turn", () => {
326 const attemptId = RunAttemptId.make("attempt-starting");
327 const openRun = { ...run("run-starting", 1, "running"), activeAttemptId: attemptId };
328 const session = {
329 id: ProviderSessionId.make("session-starting"),
330 driver: ProviderDriverKind.make("claudeAgent"),
331 providerInstanceId: openRun.providerInstanceId,
332 status: "starting" as const,
333 cwd: "/repo",
334 model: null,
335 // The label reads only the driver.
336 capabilities: {} as never,
337 createdAt: now,
338 updatedAt: now,
339 lastError: null,
340 };
341 const starting = { ...v2Projection, runs: [openRun], providerSessions: [session] };
342
343 expect(deriveThreadActivityRun(starting)?.providerStartingLabel).toBe("Starting Claude");
344 expect(
345 deriveThreadActivityRun({ ...starting, providerSessions: [] })?.providerStartingLabel,
346 ).toBe("Starting");
347 expect(
348 deriveThreadActivityRun({
349 ...starting,
350 providerTurns: [
351 {
352 id: ProviderTurnId.make("provider-turn-starting"),
353 providerThreadId: ProviderThreadId.make("provider-thread-starting"),
354 nodeId: NodeId.make("node-starting"),
355 runAttemptId: attemptId,
356 nativeTurnRef: null,
357 ordinal: 1,
358 status: "running",
359 startedAt: now,
360 completedAt: null,
361 },
362 ],
363 })?.providerStartingLabel,
364 ).toBeUndefined();
365 });
366
367 it("presents a held queue as the stopped run instead of queued work", () => {
368 const interrupted = { ...run("run-interrupted", 1, "interrupted"), completedAt: now };
369 const held = { ...run("run-held", 2, "queued"), queueHeld: true };
370 const projection = { ...v2Projection, runs: [interrupted, held], updatedAt: now };
371
372 expect(deriveLatestThreadRun(projection)?.runId).toBe(interrupted.id);
373 expect(deriveThreadActivityRun(projection)?.runId).toBe(interrupted.id);
374 expect(deriveThreadRuntime(projection)).toMatchObject({
375 status: "interrupted",
376 activeRunId: null,
377 });
378 expect(deriveResumableRunId(projection, deriveThreadRuntime(projection))).toBe(interrupted.id);
379
380 const resumed = { ...projection, runs: [interrupted, { ...held, queueHeld: false }] };
381 expect(deriveThreadRuntime(resumed)).toMatchObject({ status: "queued" });
382
383 const onlyHeld = {
384 ...projection,
385 runs: [held],
386 thread: { ...projection.thread, activeProviderThreadId: null },
387 };
388 expect(deriveLatestThreadRun(onlyHeld)).toBeNull();
389 expect(deriveThreadRuntime(onlyHeld)).toBeNull();
390 });
391
392 it.each([
393 { name: "no requests", requests: [], runId: "run-stopped", held: false, expected: true },
394 {
395 name: "a pending request",
396 requests: [request("approval", "pending")],
397 runId: "run-stopped",
398 held: false,
399 expected: false,
400 },
401 {
402 name: "only a resolved request",
403 requests: [request("approval", "resolved")],
404 runId: "run-stopped",
405 held: false,
406 expected: true,
407 },
408 {
409 name: "a held queue behind a blocked continuation",
410 requests: [request("approval", "pending")],
411 runId: "run-stopped",
412 held: true,
413 expected: false,
414 },
415 {
416 name: "a held queue with no run to continue",
417 requests: [request("approval", "pending")],
418 runId: null,
419 held: true,
420 expected: true,
421 },
422 { name: "nothing to resume", requests: [], runId: null, held: false, expected: false },
423 ])("decides whether Resume can start with $name", ({ requests, runId, held, expected }) => {
424 expect(
425 canResumeThread(
426 { runtimeRequests: requests },
427 runId === null ? null : RunId.make(runId),
428 held,
429 ),
430 ).toBe(expected);
431 });
432
433 it("does not expose a queued-only run as interruptible", () => {
434 const queuedRun = run("run-queued", 1, "queued");
435 const projection = { ...v2Projection, runs: [queuedRun], updatedAt: now };
436
437 expect(deriveThreadActivityRun(projection)).toMatchObject({
438 runId: queuedRun.id,
439 status: "queued",
440 });
441
442 const runtime = deriveThreadRuntime(projection);
443 expect(runtime).toMatchObject({
444 status: "queued",
445 activeRunId: null,
446 });
447 expect(threadRuntimeHasInterruptibleRun(runtime)).toBe(false);
448 });
449});
450
451describe("presentRunStop", () => {
452 const signal = (type: "run_interrupt_request" | "run_interrupt_result", runId: RunId) => ({
453 id: TurnItemId.make(`${type}:${runId}`),
454 threadId: v2Projection.thread.id,
455 runId,
456 nodeId: null,
457 providerThreadId: null,
458 providerTurnId: null,
459 nativeItemRef: null,
460 parentItemId: null,
461 ordinal: 1,
462 status: type === "run_interrupt_result" ? ("interrupted" as const) : ("completed" as const),
463 title: "Interrupt",
464 startedAt: now,
465 completedAt: now,
466 updatedAt: now,
467 type,
468 message: "Stopped by user",
469 });
470 const running = run("run-stopping", 1, "running");
471 const request = signal("run_interrupt_request", running.id);
472
473 it.each([
474 { name: "no Stop", runs: [running], turnItems: [], state: null },
475 {
476 name: "a Stop the agent is winding down",
477 runs: [running],
478 turnItems: [request],
479 state: "stopping",
480 },
481 {
482 name: "a Stop the provider never confirmed",
483 runs: [running],
484 turnItems: [{ ...request, unresponsiveAt: now }],
485 state: "unresponsive",
486 },
487 {
488 name: "a Stop that ended the run",
489 runs: [running],
490 turnItems: [request, signal("run_interrupt_result", running.id)],
491 state: null,
492 },
493 {
494 name: "a settled run's background work still stopping",
495 runs: [{ ...running, status: "completed" as const, completedAt: now }],
496 turnItems: [request],
497 state: null,
498 },
499 {
500 name: "a settled run whose background work ignored Stop",
501 runs: [{ ...running, status: "completed" as const, completedAt: now }],
502 turnItems: [{ ...request, unresponsiveAt: now }],
503 state: "unresponsive",
504 },
505 {
506 name: "a settled run whose background work ignored Stop, with a queued follow-up",
507 runs: [
508 { ...running, status: "completed" as const, completedAt: now },
509 run("run-queued", 2, "queued"),
510 ],
511 turnItems: [{ ...request, unresponsiveAt: now }],
512 state: "unresponsive",
513 },
514 ])("presents $name", ({ runs, turnItems, state }) => {
515 expect(presentRunStop({ runs, turnItems })?.state ?? null).toBe(state);
516 });
517});
518
519describe("deriveRunlessWorkStartedAt", () => {
520 const later = DateTime.makeUnsafe("2026-07-28T10:05:00.000Z");
521 const rootTurn = (
522 status: OrchestrationV2ExecutionNode["status"],
523 startedAt = now,
524 ): OrchestrationV2ExecutionNode => ({
525 id: NodeId.make("child-root"),
526 threadId: v2Projection.thread.id,
527 runId: null,
528 parentNodeId: null,
529 rootNodeId: NodeId.make("child-root"),
530 kind: "root_turn",
531 status,
532 countsForRun: false,
533 providerThreadId: null,
534 providerTurnId: null,
535 nativeItemRef: null,
536 runtimeRequestId: null,
537 checkpointScopeId: null,
538 startedAt,
539 completedAt: null,
540 });
541
542 const nativeChild = {
543 ...v2Projection,
544 thread: {
545 ...v2Projection.thread,
546 creationSource: "provider" as const,
547 lineage: {
548 parentThreadId: ThreadId.make("parent"),
549 relationshipToParent: "subagent" as const,
550 rootThreadId: ThreadId.make("parent"),
551 },
552 },
553 };
554
555 it("times a provider-native subagent from its runless root turn while it works", () => {
556 const projection = {
557 ...nativeChild,
558 thread: {
559 ...nativeChild.thread,
560 activeProviderThreadId: ProviderThreadId.make("native-child"),
561 },
562 nodes: [rootTurn("running", later)],
563 };
564 expect(deriveRunlessWorkStartedAt(projection)).toBe("2026-07-28T10:05:00.000Z");
565 expect(deriveThreadRuntime(projection)).toBeNull();
566 });
567
568 it("ignores root turns that belong to a run, and threads the provider does not run", () => {
569 const owned = { ...rootTurn("running"), runId: RunId.make("run-1") };
570 expect(deriveRunlessWorkStartedAt({ ...nativeChild, nodes: [owned] })).toBeNull();
571 expect(
572 deriveRunlessWorkStartedAt({ ...v2Projection, nodes: [rootTurn("running")] }),
573 ).toBeNull();
574 });
575});
576
577describe("deriveProviderSubagentStatus", () => {
578 const root = {
579 id: NodeId.make("child-root"),
580 threadId: v2Projection.thread.id,
581 runId: null,
582 parentNodeId: null,
583 rootNodeId: NodeId.make("child-root"),
584 kind: "root_turn" as const,
585 status: "completed" as const,
586 countsForRun: false,
587 providerThreadId: null,
588 providerTurnId: null,
589 nativeItemRef: null,
590 runtimeRequestId: null,
591 checkpointScopeId: null,
592 startedAt: now,
593 completedAt: now,
594 };
595 const child = (creationSource: "provider" | "mcp") => ({
596 ...v2Projection,
597 thread: {
598 ...v2Projection.thread,
599 creationSource,
600 lineage: {
601 parentThreadId: ThreadId.make("parent"),
602 relationshipToParent: "subagent" as const,
603 rootThreadId: ThreadId.make("parent"),
604 },
605 },
606 nodes: [root],
607 });
608
609 it("leaves T3 delegated tasks and ordinary threads alone", () => {
610 expect(deriveProviderSubagentStatus(child("mcp"))).toBeNull();
611 expect(deriveProviderSubagentStatus({ ...v2Projection, nodes: [root] })).toBeNull();
612 });
613});
614
615describe("provider-reported model selection", () => {
616 const selected = v2Projection.thread.modelSelection;
617 const reported = { ...selected, options: [{ id: "reasoningEffort", value: "default" }] };
618 const providerThread = {
619 id: ProviderThreadId.make("active"),
620 driver: ProviderDriverKind.make("codex"),
621 providerInstanceId: selected.instanceId,
622 providerSessionId: ProviderSessionId.make("session"),
623 appThreadId: v2Projection.thread.id,
624 ownerNodeId: null,
625 nativeThreadRef: null,
626 nativeConversationHeadRef: null,
627 status: "idle" as const,
628 firstRunOrdinal: null,
629 lastRunOrdinal: null,
630 handoffIds: [],
631 forkedFrom: null,
632 createdAt: now,
633 updatedAt: now,
634 nativeMetadata: { modelSelection: reported },
635 };
636 const projection = {
637 ...v2Projection,
638 thread: { ...v2Projection.thread, activeProviderThreadId: providerThread.id },
639 providerThreads: [providerThread],
640 };
641
642 it("reads only the active provider thread's reported selection", () => {
643 expect(deriveReportedModelSelection(projection)).toBe(reported);
644 expect(
645 deriveReportedModelSelection({
646 ...projection,
647 thread: { ...projection.thread, activeProviderThreadId: null },
648 }),
649 ).toBeNull();
650 expect(
651 deriveReportedModelSelection({
652 ...projection,
653 providerThreads: [
654 { ...providerThread, providerInstanceId: ProviderInstanceId.make("other") },
655 ],
656 }),
657 ).toBeNull();
658 });
659});
660
661describe("formatModelSelectionEffort", () => {
662 it("labels an old Ultracode effort by the effort it runs at", () => {
663 const models = [
664 {
665 slug: "claude-opus-5-5",
666 name: "Claude Opus 5.5",
667 isCustom: false,
668 capabilities: createModelCapabilities({
669 optionDescriptors: [
670 {
671 id: "effort",
672 label: "Reasoning",
673 type: "select",
674 options: [
675 { id: "high", label: "High", isDefault: true },
676 { id: "xhigh", label: "Extra High" },
677 ],
678 },
679 { id: "ultracode", label: "Ultracode", type: "boolean" },
680 ],
681 }),
682 },
683 ];
684 const selection = createModelSelection(
685 ProviderInstanceId.make("claudeAgent"),
686 "claude-opus-5-5",
687 [{ id: "effort", value: "ultracode" }],
688 );
689
690 expect(formatModelSelectionEffort(selection, models)).toBe("Extra High");
691 });
692});
693
694describe("isBackgroundWorkStopPending", () => {
695 const tasks = [{ taskId: "dev-server" }];
696 const sent = { threadKey: "env:thread", taskIds: ["dev-server"], retryAt: null };
697 const accepted = { ...sent, retryAt: 10_000 };
698
699 it("stays pending after acceptance while a stopped task is still listed", () => {
700 expect(isBackgroundWorkStopPending(sent, "env:thread", tasks, false)).toBe(true);
701 expect(isBackgroundWorkStopPending(accepted, "env:thread", tasks, false)).toBe(true);
702 });
703
704 it("ends once the stopped tasks leave, even if new work starts", () => {
705 expect(isBackgroundWorkStopPending(accepted, "env:thread", [], false)).toBe(false);
706 expect(isBackgroundWorkStopPending(accepted, "env:thread", [{ taskId: "next" }], false)).toBe(
707 false,
708 );
709 });
710
711 it("allows retry at the deadline even if a stopped task remains", () => {
712 const request = { ...accepted, retryAt: 10_000 };
713 expect(isBackgroundWorkStopPending(request, "env:thread", tasks, false)).toBe(true);
714 expect(isBackgroundWorkStopPending(request, "env:thread", tasks, true)).toBe(false);
715 });
716
717 it("only applies to the thread that sent the Stop", () => {
718 expect(isBackgroundWorkStopPending(null, "env:thread", tasks, false)).toBe(false);
719 expect(isBackgroundWorkStopPending(sent, "env:other", tasks, false)).toBe(false);
720 });
721});
722
723describe("presentProviderGoal", () => {
724 it("rolls token counts that round to 1000k over to 1m", () => {
725 const usage = (tokensUsed: number) =>
726 presentProviderGoal(
727 { objective: "Ship the feature", status: "active", tokensUsed, tokenBudget: 2_000_000 },
728 true,
729 ).usage;
730 expect(usage(999_499)).toBe("999k / 2m tokens");
731 expect(usage(999_500)).toBe("1m / 2m tokens");
732 expect(usage(999_999)).toBe("1m / 2m tokens");
733 expect(usage(1_000_000)).toBe("1m / 2m tokens");
734 expect(
735 presentProviderGoal(
736 {
737 objective: "Ship the feature",
738 status: "paused",
739 tokensUsed: 999_500,
740 tokenBudget: 999_500,
741 },
742 false,
743 ).usage,
744 ).toBe("1m / 1m tokens");
745 });
746});
747
748describe("presentPendingBackgroundWork", () => {
749 it("names a command by its script, not the shell that wraps it", () => {
750 const presentation = presentPendingBackgroundWork([
751 {
752 taskId: "call_1",
753 kind: "command",
754 description: `/bin/zsh -lc "mv a.ts b.ts\ncat > b.ts <<'EOF'\nexport {};\nEOF"`,
755 },
756 ]);
757 expect(presentation?.items[0]?.label).toBe("mv a.ts b.ts\ncat > b.ts <<'EOF'\nexport {};\nEOF");
758 });
759});