1import { assert } from "@effect/vitest";
2import type { ProviderReplayTranscript } from "@t3tools/contracts";
3
4import type { OrchestratorV2ScenarioResult } from "../../OrchestratorScenario.ts";
5import {
6 assertBaseProjection,
7 assertSemanticProjectionIntegrity,
8 assertUserMessagesInclude,
9 assertVisibleTurnItemsMirrorLocalTurnItems,
10 projectionFor,
11 TURN_INTERRUPT_PROMPT,
12} from "../shared.ts";
13
14/**
15 * Stop while Muse works on a `sleep 30` request: T3 sends `turn/interrupt`, Muse
16 * ends the turn as cancelled, and the run, every row and the session settle.
17 */
18export function assertMuseTurnInterruptOutput(
19 result: OrchestratorV2ScenarioResult,
20 transcript: ProviderReplayTranscript,
21) {
22 assertBaseProjection({ result, transcript, runCount: 1, runStatuses: ["interrupted"] });
23 const projection = projectionFor(result, transcript.scenario);
24 assertSemanticProjectionIntegrity(projection);
25 assertVisibleTurnItemsMirrorLocalTurnItems(projection);
26 assertUserMessagesInclude(projection, [TURN_INTERRUPT_PROMPT]);
27 const outbound = transcript.entries.flatMap((entry) =>
28 entry.type === "expect_outbound" ? [entry.frame] : [],
29 );
30 assert.isTrue(
31 outbound.some(
32 (frame) =>
33 typeof frame === "object" &&
34 frame !== null &&
35 Reflect.get(frame, "method") === "turn/interrupt",
36 ),
37 "Stop must reach Muse as turn/interrupt",
38 );
39 assert.isFalse(projection.turnItems.some((item) => item.status === "running"));
40 assert.equal(projection.providerThreads[0]?.status, "idle");
41 assert.equal(projection.providerTurns[0]?.status, "interrupted");
42}