(runDir: string)
| 112 | ); |
| 113 | |
| 114 | const runApproval = async (runDir: string) => { |
| 115 | const home = mkdtempSync(join(tmpdir(), "executor-desktop-mcp-")); |
| 116 | const app = await launchDesktop(home, runDir); |
| 117 | let stepIndex = 0; |
| 118 | try { |
| 119 | const page = await app.firstWindow({ timeout: 120_000 }); |
| 120 | const step = async (label: string, body: () => Promise<void>) => { |
| 121 | await body(); |
| 122 | stepIndex += 1; |
| 123 | await page.screenshot({ |
| 124 | path: join( |
| 125 | runDir, |
| 126 | `${String(stepIndex).padStart(2, "0")}-${label.toLowerCase().replace(/[^a-z0-9]+/g, "-")}.png`, |
| 127 | ), |
| 128 | }); |
| 129 | }; |
| 130 | |
| 131 | await step("app boots into the web console", async () => { |
| 132 | await page.getByText("Settings").first().waitFor({ timeout: 120_000 }); |
| 133 | }); |
| 134 | |
| 135 | const { origin, token } = readSidecar(home); |
| 136 | |
| 137 | // Plant a require_approval policy so the gated tool elicits (bearer-authed — |
| 138 | // the test process is not the app, so it carries the token explicitly). |
| 139 | const policyRes = await fetch(`${origin}/api/policies`, { |
| 140 | method: "POST", |
| 141 | headers: { "content-type": "application/json", authorization: `Bearer ${token}` }, |
| 142 | body: JSON.stringify({ |
| 143 | owner: "org", |
| 144 | pattern: APPROVAL_TARGET_TOOL, |
| 145 | action: "require_approval", |
| 146 | }), |
| 147 | }); |
| 148 | expect(policyRes.ok, `policy create ok (${policyRes.status})`).toBe(true); |
| 149 | |
| 150 | const mcp = new Client({ name: "e2e-desktop-approve", version: "1.0.0" }, { capabilities: {} }); |
| 151 | const transport = new StreamableHTTPClientTransport( |
| 152 | new URL(`${origin}/mcp?elicitation_mode=browser`), |
| 153 | { requestInit: { headers: { authorization: `Bearer ${token}` } } }, |
| 154 | ); |
| 155 | await mcp.connect(transport); |
| 156 | |
| 157 | // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: the test owns the MCP transport lifecycle |
| 158 | try { |
| 159 | const executed = await mcp.callTool({ name: "execute", arguments: { code: EXECUTE_CODE } }); |
| 160 | const paused = executed.structuredContent as { |
| 161 | status: string; |
| 162 | executionId: string; |
| 163 | approvalUrl: string; |
| 164 | }; |
| 165 | expect(paused.status, "execute paused for browser approval").toBe("user_approval_required"); |
| 166 | |
| 167 | await step("approve the gated tool in the desktop renderer", async () => { |
| 168 | // The renderer carries no token; the main process injects the bearer for |
| 169 | // THIS window. No ?_token (bootstrap no-ops on desktop). |
| 170 | await page.goto(paused.approvalUrl, { waitUntil: "domcontentloaded" }); |
| 171 | await page.getByRole("button", { name: "Approve" }).waitFor({ timeout: 30_000 }); |
no test coverage detected