1import { assert } from "@effect/vitest";
2import type { ProviderReplayTranscript } from "@t3tools/contracts";
3
4import type { OrchestratorV2ScenarioResult } from "../../OrchestratorScenario.ts";
5import {
6 assertAssistantTextIncludes,
7 assertBaseProjection,
8 assertExecutionNodeKinds,
9 assertNoExtraAppRunsForProviderChildren,
10 assertRunProviderTurnCardinality,
11 assertSemanticProjectionIntegrity,
12 assertTurnItemTypes,
13 assertUserMessagesInclude,
14 OPENCODE_SUBAGENT_PROMPT,
15 projectionFor,
16} from "../shared.ts";
17
18export function assertOpenCodeSubagentOutput(
19 result: OrchestratorV2ScenarioResult,
20 transcript: ProviderReplayTranscript,
21) {
22 assertBaseProjection({ result, transcript, runCount: 1, runStatuses: ["completed"] });
23
24 const projection = projectionFor(result, transcript.scenario);
25 assertSemanticProjectionIntegrity(projection);
26 assertExecutionNodeKinds(projection, ["root_turn", "subagent", "assistant_message"]);
27 assertTurnItemTypes(projection, ["user_message", "subagent", "assistant_message"]);
28 assertRunProviderTurnCardinality({ projection, rootRunCount: 1 });
29 assertNoExtraAppRunsForProviderChildren({ projection, expectedAppRuns: 1 });
30 assertUserMessagesInclude(projection, [OPENCODE_SUBAGENT_PROMPT]);
31 assertAssistantTextIncludes(projection, "PARENT_OK");
32
33 assert.lengthOf(projection.subagents, 1);
34 assert.lengthOf(result.shellSnapshot.threads, 2);
35 const subagent = projection.subagents[0];
36 assert.isDefined(subagent);
37 assert.equal(subagent.origin, "provider_native");
38 assert.equal(subagent.createdBy, "agent");
39 assert.equal(subagent.driver, "opencode");
40 assert.equal(subagent.status, "completed");
41 assert.equal(subagent.prompt, "Respond exactly CHILD_OK.");
42 assert.include(subagent.result ?? "", "CHILD_OK");
43 assert.isNotNull(subagent.childThreadId);
44 assert.isNotNull(subagent.providerThreadId);
45 assert.isNotNull(subagent.nativeTaskRef);
46 assert.isNotNull(subagent.completedAt);
47 if (subagent.childThreadId === null || subagent.providerThreadId === null) {
48 throw new Error("OpenCode subagent is missing its child thread identity");
49 }
50
51 const providerThread = projection.providerThreads.find(
52 (thread) => thread.id === subagent.providerThreadId,
53 );
54 assert.isDefined(providerThread);
55 assert.equal(providerThread.appThreadId, subagent.childThreadId);
56 assert.equal(providerThread.ownerNodeId, subagent.id);
57
58 const childProjection = result.projections.get(subagent.childThreadId);
59 assert.isDefined(childProjection);
60 assert.equal(childProjection.thread.lineage.parentThreadId, projection.thread.id);
61 assert.equal(childProjection.thread.lineage.relationshipToParent, "subagent");
62 assert.equal(childProjection.thread.activeProviderThreadId, providerThread.id);
63 assert.lengthOf(childProjection.runs, 0);
64 assert.lengthOf(childProjection.providerThreads, 1);
65 assert.lengthOf(childProjection.providerTurns, 1);
66 assert.equal(childProjection.providerTurns[0]?.status, "completed");
67 assertExecutionNodeKinds(childProjection, ["root_turn", "assistant_message"]);
68 assertTurnItemTypes(childProjection, ["user_message", "assistant_message"]);
69 assertUserMessagesInclude(childProjection, ["Respond exactly CHILD_OK."]);
70 assertAssistantTextIncludes(childProjection, "CHILD_OK");
71}