1import { assert } from "@effect/vitest";
2import type { ProviderReplayTranscript } from "@t3tools/contracts";
3
4import type { OrchestratorV2ScenarioResult } from "../../OrchestratorScenario.ts";
5import {
6 assertBaseProjection,
7 assertExecutionNodeKinds,
8 assertNoExtraAppRunsForProviderChildren,
9 assertTurnItemTypes,
10 projectionFor,
11} from "../shared.ts";
12
13const EXPECTED_CHILDREN = [
14 {
15 title: "Explore server architecture",
16 sessionId: "019f0220-e192-7c41-9e9d-b406bc3459c8",
17 first: "I'll audit `apps/server` by mapping its layout first",
18 second: "# `apps/server` Audit Summary",
19 result: "Server audit complete.",
20 },
21 {
22 title: "Explore web client architecture",
23 sessionId: "019f0220-e197-7833-a8e4-ad38f2bd5b4c",
24 first: "Auditing `apps/web`: mapping package structure",
25 second: "# `apps/web` Audit Summary",
26 result: "Web audit complete.",
27 },
28] as const;
29
30export function assertGrokSubagentLineageOutput(
31 result: OrchestratorV2ScenarioResult,
32 transcript: ProviderReplayTranscript,
33) {
34 assertBaseProjection({ result, transcript, runCount: 1, runStatuses: ["completed"] });
35 const parent = projectionFor(result, transcript.scenario);
36 assertTurnItemTypes(parent, ["user_message", "subagent", "subagent", "assistant_message"]);
37 assertExecutionNodeKinds(parent, ["root_turn", "subagent", "subagent", "assistant_message"]);
38 assertNoExtraAppRunsForProviderChildren({ projection: parent, expectedAppRuns: 1 });
39 assert.lengthOf(parent.subagents, 2);
40 assert.lengthOf(result.shellSnapshot.threads, 3);
41 assert.notInclude(
42 parent.messages.map((message) => message.text).join("\n"),
43 "Audit Summary",
44 "interleaved child output must not leak into the parent timeline",
45 );
46
47 for (const expected of EXPECTED_CHILDREN) {
48 const subagent = parent.subagents.find((candidate) => candidate.title === expected.title);
49 assert.isDefined(subagent);
50 assert.equal(subagent.status, "completed");
51 assert.equal(subagent.origin, "provider_native");
52 assert.equal(subagent.driver, "grok");
53 assert.isNotNull(subagent.childThreadId);
54 assert.isNotNull(subagent.providerThreadId);
55 assert.equal(subagent.result, expected.result);
56 if (subagent.childThreadId === null || subagent.providerThreadId === null) {
57 throw new Error(`Grok subagent ${expected.title} is missing lineage`);
58 }
59
60 const child = result.projections.get(subagent.childThreadId);
61 assert.isDefined(child);
62 assert.equal(child.thread.lineage.parentThreadId, parent.thread.id);
63 assert.equal(child.thread.lineage.relationshipToParent, "subagent");
64 assert.equal(child.thread.lineage.rootThreadId, parent.thread.lineage.rootThreadId);
65 assert.equal(child.thread.activeProviderThreadId, subagent.providerThreadId);
66 assert.lengthOf(child.runs, 0);
67 assert.lengthOf(child.providerThreads, 1);
68 assert.equal(child.providerThreads[0]?.nativeThreadRef?.nativeId, expected.sessionId);
69 const forkedFrom = child.providerThreads[0]?.forkedFrom;
70 if (forkedFrom == null) {
71 throw new Error(`Grok subagent ${expected.title} is missing provider lineage`);
72 }
73 assert.notEqual(forkedFrom.providerThreadId, subagent.providerThreadId);
74 assert.isTrue(
75 parent.providerTurns.some(
76 (turn) =>
77 turn.id === forkedFrom.providerTurnId &&
78 turn.providerThreadId === forkedFrom.providerThreadId,
79 ),
80 "the native child session must fork from the parent provider turn",
81 );
82 assertTurnItemTypes(child, ["user_message", "assistant_message"]);
83 const assistant = child.messages.find((message) => message.role === "assistant");
84 assert.isDefined(assistant);
85 assert.include(assistant.text, expected.first);
86 assert.include(assistant.text, expected.second);
87 assert.isBelow(assistant.text.indexOf(expected.first), assistant.text.indexOf(expected.second));
88 for (const other of EXPECTED_CHILDREN) {
89 if (other.sessionId !== expected.sessionId) {
90 assert.notInclude(
91 assistant.text,
92 other.second,
93 "interleaved chunks must remain isolated to their native child session",
94 );
95 }
96 }
97 }
98}