| 1 | import { assert } from "@effect/vitest"; |
| 2 | import type { ProviderReplayTranscript } from "@t3tools/contracts"; |
| 3 | |
| 4 | import type { OrchestratorV2ScenarioResult } from "../../OrchestratorScenario.ts"; |
| 5 | import { |
| 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 | */ |
| 18 | export 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 | } |