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 OPENCODE2_PERMISSION_PROMPT,
11 projectionFor,
12} from "../shared.ts";
13
14/**
15 * Supervised: each shell call asks. The approved one runs; the declined one and
16 * the model's two retries fail without running, and the turn still completes.
17 */
18export function assertOpenCode2PermissionOutput(
19 result: OrchestratorV2ScenarioResult,
20 transcript: ProviderReplayTranscript,
21) {
22 assertBaseProjection({ result, transcript, runCount: 1, runStatuses: ["completed"] });
23 const projection = projectionFor(result, transcript.scenario);
24 assertSemanticProjectionIntegrity(projection);
25 assertVisibleTurnItemsMirrorLocalTurnItems(projection);
26 assertUserMessagesInclude(projection, [OPENCODE2_PERMISSION_PROMPT]);
27
28 const requests = projection.runtimeRequests.toSorted((left, right) =>
29 left.createdAt < right.createdAt ? -1 : 1,
30 );
31 assert.deepEqual(
32 requests.map((request) => [request.kind, request.status, request.decision]),
33 [
34 ["command", "resolved", "accept"],
35 ["command", "resolved", "decline"],
36 ["command", "resolved", "decline"],
37 ["command", "resolved", "decline"],
38 ],
39 );
40 assert.deepEqual(
41 requests.map((request) => request.nativeRequestRef?.nativeId),
42 [
43 "per_0eb7c4d7e001Pyt8o50Vi4KrOO",
44 "per_0eb7c5376001gkpJzQEeWtaoRE",
45 "per_0eb7c5b1f001zGfBgDhzxz8z10",
46 "per_0eb7c6755001DdRDvkLLSYU4BD",
47 ],
48 );
49
50 const approvals = projection.turnItems.filter((item) => item.type === "approval_request");
51 assert.deepEqual(
52 approvals.map((item) => [item.status, item.type === "approval_request" ? item.prompt : null]),
53 [
54 ["completed", "echo FIRST"],
55 ["cancelled", "echo SECOND"],
56 ["cancelled", "echo SECOND"],
57 ["cancelled", "echo THIRD_PROBE"],
58 ],
59 );
60 // "Always" is this session's own rule, not OpenCode's project-wide grant.
61 const options = approvals[0]?.type === "approval_request" ? (approvals[0].options ?? []) : [];
62 assert.deepInclude(options, {
63 decision: "acceptForSession",
64 label: "Allow echo * this session",
65 });
66
67 const shells = projection.turnItems.filter((item) => item.type === "command_execution");
68 assert.deepEqual(
69 shells.map((item) => [
70 item.type === "command_execution" ? item.input : null,
71 item.status,
72 item.type === "command_execution" ? item.output : null,
73 ]),
74 [
75 ["echo FIRST", "completed", "FIRST\n"],
76 ["echo SECOND", "failed", "Unable to execute command: echo SECOND"],
77 ["echo SECOND", "failed", "Unable to execute command: echo SECOND"],
78 ["echo THIRD_PROBE", "failed", "Unable to execute command: echo THIRD_PROBE"],
79 ],
80 );
81}