/** * End-to-end smoke test for the bundled executor binary. * * Catches "works in dev, breaks in --compile" regressions: bunfs asset * loading (embedded web UI, QuickJS WASM), native * .node loaders (keychain), and the MCP → engine → QuickJS → tool path. * * Flow: * 1. Spin up a tiny local OpenAPI server (one operation, returns 42). * 2. Spawn the compiled `executor daemon run --foreground --port 0` * and parse the ready URL. * 3. Connect via MCP streamable HTTP, call the `execute` tool with code * that registers and invokes the OpenAPI tool, assert the answer * round-trips as 42. * * Run after `bun ./scripts/build-sidecar.ts`. Exits non-zero on any * deviation so it can gate CI. */ import { chmod, cp, mkdtemp, rename, rm } from "node:fs/promises"; import { createHash } from "node:crypto"; import { createRequire } from "node:module"; import { tmpdir } from "node:os"; import { basename, dirname, join, resolve } from "node:path"; import { spawn, type Subprocess } from "bun"; import { Database } from "bun:sqlite"; import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; const ROOT = resolve(import.meta.dir, ".."); const REPO_ROOT = resolve(ROOT, "../.."); const APPS_LOCAL_DRIZZLE = resolve(ROOT, "../local/drizzle-legacy-v1"); const BINARY = resolve( ROOT, "resources/executor", process.platform === "win32" ? "executor.exe" : "executor", ); const AUTH_TOKEN = "smoke-test-token"; const AUTH_HEADER = `Bearer ${AUTH_TOKEN}`; const READY_TIMEOUT_MS = 30_000; const EXECUTOR_BINARY_NAME = process.platform === "win32" ? "executor.exe" : "executor"; const requireFromOnePasswordPlugin = createRequire( join(REPO_ROOT, "packages/plugins/onepassword/package.json"), ); const requireFromOnePasswordSdk = createRequire( requireFromOnePasswordPlugin.resolve("@1password/sdk/package.json"), ); const ONEPASSWORD_SDK_CORE_WASM = join( dirname(requireFromOnePasswordSdk.resolve("@1password/sdk-core/package.json")), "nodejs/core_bg.wasm", ); // Throw instead of process.exit so main()'s finally still tears down the // spawned daemon + temp dirs — exiting here leaks a running process. const fail = (msg: string): never => { // oxlint-disable-next-line executor/no-try-catch-or-throw, executor/no-error-constructor -- boundary: standalone smoke harness surfaces failures as a thrown error throw new Error(`[smoke-sidecar] FAIL: ${msg}`); }; type ToolCallResult = Awaited>; const isRecord = (value: unknown): value is Record => typeof value === "object" && value !== null && !Array.isArray(value); const makeScopeId = (cwd: string): string => { const folder = basename(cwd) || cwd; const hash = createHash("sha256").update(cwd).digest("hex").slice(0, 8); return `${folder}-${hash}`; }; const readLegacyMigrations = async (): Promise => { const journal = (await Bun.file(join(APPS_LOCAL_DRIZZLE, "meta/_journal.json")).json()) as { readonly entries: readonly { readonly idx: number; readonly tag: string }[]; }; const migrations: { sql: string; hash: string }[] = []; for (const entry of [...journal.entries].sort((left, right) => left.idx - right.idx)) { const query = await Bun.file(join(APPS_LOCAL_DRIZZLE, `${entry.tag}.sql`)).text(); migrations.push({ sql: query, hash: createHash("sha256").update(query).digest("hex"), }); } return migrations; }; const seedLegacyScopedSqlite = async (dataDir: string, scopeId: string): Promise => { const db = new Database(join(dataDir, "data.db")); // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: standalone smoke harness closes the SQLite handle before spawning the sidecar try { // Replay the real legacy v1 chain so the fixture matches the migration // history the sidecar's embedded copy expects. A hand-rolled partial // schema that claims the full history is applied makes the v1→v2 data // migration read tables that don't exist. const migrations = await readLegacyMigrations(); for (const migration of migrations) { // `--> statement-breakpoint` markers are SQL line comments, so the // whole file executes as one multi-statement batch. db.exec(migration.sql); } db.exec(` CREATE TABLE "__drizzle_migrations" ( id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, hash text NOT NULL, created_at numeric ); `); const insertMigration = db.prepare( `INSERT INTO "__drizzle_migrations" (hash, created_at) VALUES (?, ?)`, ); for (const migration of migrations) { insertMigration.run(migration.hash, Date.now()); } db.prepare( `INSERT INTO source ( scope_id, id, plugin_id, kind, name, url, can_remove, can_refresh, can_edit, created_at, updated_at ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, ).run( scopeId, "legacy-smoke", "smoke", "remote", "Legacy Smoke Source", null, 1, 0, 1, 1_700_000_000_000, 1_700_000_001_000, ); db.prepare("INSERT INTO blob (namespace, key, value) VALUES (?, ?, ?)").run( `${scopeId}/smoke`, "legacy", "{}", ); db.exec("PRAGMA wal_checkpoint(TRUNCATE); PRAGMA journal_mode = DELETE;"); } finally { db.close(); } }; // The v1→v2 migration moves the legacy SQLite file set aside as // `data.db.v1-v2--` before writing the new v2 database. Assert the // backup exists and still holds the seeded legacy row — that proves the // migration path ran (rather than the sidecar treating the DB as fresh) and // preserved the original data. const assertV1MigrationCompleted = async (dataDir: string): Promise => { const entries = await Array.fromAsync(new Bun.Glob("data.db.v1-v2-*").scan({ cwd: dataDir })); const backups = entries.filter((name) => !name.endsWith("-wal") && !name.endsWith("-shm")); if (backups.length !== 1) { fail(`expected exactly one v1→v2 backup in ${dataDir}, found: ${JSON.stringify(entries)}`); } const backup = new Database(join(dataDir, backups[0]!), { readonly: true }); // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: standalone smoke harness closes the SQLite handle it opened try { const row = backup.prepare("SELECT name FROM source WHERE id = 'legacy-smoke'").get() as { name?: string; } | null; if (row?.name !== "Legacy Smoke Source") { fail(`v1→v2 backup is missing the seeded legacy source row: ${JSON.stringify(row)}`); } } finally { backup.close(); } }; // Petstore-style spec: GET list + GET by id. Exercises path params, // multi-step orchestration, and array/object response shapes against a real // running HTTP server, all the way through the compiled binary → // MCP → QuickJS → openapi-invoker → HttpClient chain. const startOpenApiServer = () => { const Pet = { type: "object", properties: { id: { type: "integer" }, name: { type: "string" }, tag: { type: "string" }, }, required: ["id", "name"], }; const spec = { openapi: "3.0.0", info: { title: "Petstore Smoke API", version: "0.0.1" }, paths: { "/pets": { get: { operationId: "listPets", responses: { "200": { description: "ok", content: { "application/json": { schema: { type: "array", items: Pet } }, }, }, }, }, }, "/pets/{petId}": { get: { operationId: "getPet", parameters: [ { name: "petId", in: "path", required: true, schema: { type: "integer" }, }, ], responses: { "200": { description: "ok", content: { "application/json": { schema: Pet } }, }, "404": { description: "not found" }, }, }, }, }, }; // Seed the in-memory store so the GET-driven smoke can verify list + // path-param round-trips. Body-bearing POST/PUT is gated by the // executor's approval flow and is covered by separate non-compiled tests. const pets: Array<{ id: number; name: string; tag?: string }> = [ { id: 1, name: "Fido", tag: "dog" }, { id: 2, name: "Whiskers", tag: "cat" }, ]; const server = Bun.serve({ port: 0, hostname: "127.0.0.1", fetch(req) { const url = new URL(req.url); if (url.pathname === "/openapi.json") return Response.json(spec); if (url.pathname === "/pets" && req.method === "GET") { return Response.json(pets); } const match = /^\/pets\/(\d+)$/.exec(url.pathname); if (match && req.method === "GET") { const pet = pets.find((p) => p.id === Number(match[1])); if (!pet) return new Response("not found", { status: 404 }); return Response.json(pet); } return new Response("not found", { status: 404 }); }, }); return { server, origin: `http://127.0.0.1:${server.port}` }; }; const waitForReadyPort = (proc: Subprocess<"ignore", "pipe", "pipe">): Promise => // oxlint-disable-next-line executor/no-promise-reject -- boundary: standalone build-time smoke harness, no Effect runtime new Promise((resolveReady, rejectReady) => { const deadline = setTimeout(() => { // oxlint-disable-next-line executor/no-promise-reject, executor/no-error-constructor -- boundary: standalone smoke harness reporting a build-time timeout rejectReady(new Error(`daemon did not announce ready within ${READY_TIMEOUT_MS}ms`)); }, READY_TIMEOUT_MS); let stdoutBuf = ""; const decoder = new TextDecoder(); const reader = proc.stdout.getReader(); const stderrReader = proc.stderr.getReader(); void (async () => { while (true) { const { value, done } = await stderrReader.read(); if (done) return; process.stderr.write(`[executor-stderr] ${decoder.decode(value)}`); } })(); void (async () => { while (true) { const { value, done } = await reader.read(); if (done) { clearTimeout(deadline); // oxlint-disable-next-line executor/no-promise-reject, executor/no-error-constructor -- boundary: standalone smoke harness, stdout-closed surfaced as rejection rejectReady(new Error("daemon stdout closed before ready")); return; } const chunk = decoder.decode(value); process.stdout.write(`[executor-stdout] ${chunk}`); stdoutBuf += chunk; const match = /Daemon ready on http:\/\/(?:\[[^\]]+\]|[^:\s]+):(\d+)/.exec(stdoutBuf); if (match) { clearTimeout(deadline); resolveReady(parseInt(match[1]!, 10)); return; } } })(); }); const completePausedResult = async ( client: Client, initial: ToolCallResult, ): Promise> => { let result = initial; for (let attempt = 0; attempt < 5; attempt++) { if (result.isError) { fail(`tool returned isError: ${JSON.stringify(result.content)}`); } const structured = result.structuredContent; if (!isRecord(structured)) { fail(`tool returned no structured content: ${JSON.stringify(result.content)}`); } const structuredRecord = structured as Record; if (structuredRecord.status !== "waiting_for_interaction") { return structuredRecord; } const executionId = structuredRecord.executionId; if (typeof executionId !== "string" || executionId.length === 0) { fail(`paused result missing executionId: ${JSON.stringify(structuredRecord)}`); } console.log(`[smoke-sidecar] auto-accepting paused execution ${executionId}`); result = await client.callTool({ name: "resume", arguments: { executionId, action: "accept", content: "{}" }, }); } return fail("execute still paused after 5 resume attempts"); }; const stageSidecarWithUnavailableOp = async ( sidecarRoot: string, ): Promise<{ readonly binary: string }> => { const stagedDir = join(sidecarRoot, "executor"); await cp(dirname(BINARY), stagedDir, { recursive: true }); const fakeOpPath = join(stagedDir, process.platform === "win32" ? "op.cmd" : "op"); const fakeOp = process.platform === "win32" ? "@echo off\r\necho [ERROR] 2026/01/01 00:00:00 op: command not found 1>&2\r\nexit /b 127\r\n" : "#!/bin/sh\necho '[ERROR] 2026/01/01 00:00:00 op: command not found' >&2\nexit 127\n"; await Bun.write(fakeOpPath, fakeOp); if (process.platform !== "win32") { await chmod(fakeOpPath, 0o755); } return { binary: join(stagedDir, EXECUTOR_BINARY_NAME) }; }; interface HiddenWorkspaceWasm { readonly original: string; readonly hidden: string; } const hideWorkspaceOnePasswordWasm = async (): Promise => { const hidden = `${ONEPASSWORD_SDK_CORE_WASM}.hidden`; if (!(await Bun.file(ONEPASSWORD_SDK_CORE_WASM).exists())) { fail(`workspace 1Password SDK WASM not found at ${ONEPASSWORD_SDK_CORE_WASM}`); } await rm(hidden, { force: true }); await rename(ONEPASSWORD_SDK_CORE_WASM, hidden); console.log(`[smoke-sidecar] hid workspace 1Password SDK WASM: ${ONEPASSWORD_SDK_CORE_WASM}`); return { original: ONEPASSWORD_SDK_CORE_WASM, hidden }; }; const restoreWorkspaceOnePasswordWasm = async ( hiddenWorkspaceWasm: HiddenWorkspaceWasm | null, ): Promise => { if (!hiddenWorkspaceWasm) return; if (await Bun.file(hiddenWorkspaceWasm.original).exists()) { await rm(hiddenWorkspaceWasm.hidden, { force: true }); return; } if (!(await Bun.file(hiddenWorkspaceWasm.hidden).exists())) { fail(`workspace 1Password SDK WASM restore source missing at ${hiddenWorkspaceWasm.hidden}`); } await rename(hiddenWorkspaceWasm.hidden, hiddenWorkspaceWasm.original); console.log(`[smoke-sidecar] restored workspace 1Password SDK WASM`); }; const assertOnePasswordSdkLoads = async (origin: string): Promise => { const url = new URL("/api/onepassword/vaults", origin); url.searchParams.set("authKind", "service-account"); url.searchParams.set("account", "ops_fake_smoke_token"); const response = await fetch(url, { headers: { Authorization: AUTH_HEADER } }); const body = await response.text(); if (response.status !== 502) { fail(`expected 1Password invalid-token 502, got ${response.status}: ${body}`); } if ( body.includes("createClient is not a function") || body.includes("undefined is not a constructor") || body.includes("did not expose createClient and DesktopAuth") ) { fail(`1Password SDK namespace did not load in compiled binary: ${body}`); } if (!body.includes("invalid service account token")) { fail(`1Password SDK did not reach token validation: ${body}`); } console.log("[smoke-sidecar] OK - 1Password SDK reached service-account token validation"); }; const main = async () => { if (!(await Bun.file(BINARY).exists())) { fail( `binary not found at ${BINARY}. Run \`bun ./scripts/build-sidecar.ts\` from apps/desktop first.`, ); } const scopeDir = await mkdtemp(join(tmpdir(), "executor-smoke-scope-")); const dataDir = await mkdtemp(join(tmpdir(), "executor-smoke-data-")); const sidecarRoot = await mkdtemp(join(tmpdir(), "executor-smoke-sidecar-")); const sidecar = await stageSidecarWithUnavailableOp(sidecarRoot); let hiddenWorkspaceWasm: HiddenWorkspaceWasm | null = null; await seedLegacyScopedSqlite(dataDir, makeScopeId(scopeDir)); // v2 connections reference credentials by provider item instead of carrying // raw values, so seed the file-secrets provider (auth.json under // XDG_DATA_HOME) with the token the sandbox's connections.create points at. const xdgDir = await mkdtemp(join(tmpdir(), "executor-smoke-xdg-")); await Bun.write( join(xdgDir, "executor", "auth.json"), `${JSON.stringify({ "petstore-token": "smoke-token" })}\n`, ); const openapi = startOpenApiServer(); console.log(`[smoke-sidecar] scope: ${scopeDir}`); console.log(`[smoke-sidecar] data: ${dataDir}`); console.log(`[smoke-sidecar] openapi: ${openapi.origin}`); console.log(`[smoke-sidecar] sidecar: ${sidecar.binary}`); const proc = spawn({ cmd: [ sidecar.binary, "daemon", "run", "--foreground", "--port", "0", "--hostname", "127.0.0.1", "--auth-token", AUTH_TOKEN, ], env: { ...process.env, EXECUTOR_SCOPE_DIR: scopeDir, EXECUTOR_DATA_DIR: dataDir, EXECUTOR_CLIENT: "desktop", XDG_DATA_HOME: xdgDir, }, stdin: "ignore", stdout: "pipe", stderr: "pipe", }); let exitCode: number | null = null; void proc.exited.then((code) => { exitCode = code; }); const cleanup = async () => { if (exitCode === null) { proc.kill("SIGTERM"); await Promise.race([proc.exited, Bun.sleep(3000)]); if (exitCode === null) proc.kill("SIGKILL"); } openapi.server.stop(true); await restoreWorkspaceOnePasswordWasm(hiddenWorkspaceWasm); // oxlint-disable-next-line executor/no-promise-catch -- boundary: best-effort tempdir cleanup in a standalone smoke harness await rm(scopeDir, { recursive: true, force: true }).catch(() => {}); // oxlint-disable-next-line executor/no-promise-catch -- boundary: best-effort tempdir cleanup in a standalone smoke harness await rm(dataDir, { recursive: true, force: true }).catch(() => {}); // oxlint-disable-next-line executor/no-promise-catch -- boundary: best-effort tempdir cleanup in a standalone smoke harness await rm(xdgDir, { recursive: true, force: true }).catch(() => {}); // oxlint-disable-next-line executor/no-promise-catch -- boundary: best-effort tempdir cleanup in a standalone smoke harness await rm(sidecarRoot, { recursive: true, force: true }).catch(() => {}); }; // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: standalone smoke harness needs a finally to tear down the spawned binary + http server try { const port = await waitForReadyPort(proc); await assertV1MigrationCompleted(dataDir); const mcpUrl = new URL(`http://127.0.0.1:${port}/mcp`); console.log(`[smoke-sidecar] ready on ${mcpUrl.origin}`); hiddenWorkspaceWasm = await hideWorkspaceOnePasswordWasm(); await assertOnePasswordSdkLoads(mcpUrl.origin); const transport = new StreamableHTTPClientTransport(mcpUrl, { requestInit: { headers: { Authorization: AUTH_HEADER } }, }); const client = new Client({ name: "smoke-test", version: "0.0.1" }); await client.connect(transport); const tools = await client.listTools(); const hasExecute = tools.tools.some((t) => t.name === "execute"); if (!hasExecute) fail(`MCP tools/list missing "execute": ${JSON.stringify(tools.tools)}`); const hasResume = tools.tools.some((t) => t.name === "resume"); if (!hasResume) fail(`MCP tools/list missing "resume": ${JSON.stringify(tools.tools)}`); // Shared sandbox helper, prepended to both execute calls. const unwrapHelper = ` const unwrapToolData = (value) => { if (value && typeof value === "object" && "ok" in value) { if (!value.ok) throw new Error(value.error?.message ?? "Tool failed"); value = value.data; } if (value && typeof value === "object" && "data" in value) return value.data; return value; }; `; // Execute #1 — register the integration and create its connection. v2 // produces tools per connection, and the sandbox snapshots the tool tree // when an execution starts, so the invocation has to happen in a second // execute call. const setupCode = `${unwrapHelper} unwrapToolData(await tools.executor.openapi.addSpec({ spec: { kind: "url", url: ${JSON.stringify(`${openapi.origin}/openapi.json`)} }, baseUrl: ${JSON.stringify(openapi.origin)}, slug: "petstore", })); unwrapToolData(await tools.executor.coreTools.connections.create({ owner: "org", name: "main", integration: "petstore", template: "apiKey", from: { provider: "file", id: "petstore-token" }, })); return "setup-ok"; `; const setupResult = await completePausedResult( client, await client.callTool({ name: "execute", arguments: { code: setupCode }, }), ); if (setupResult.result !== "setup-ok") { fail(`integration setup failed: ${JSON.stringify(setupResult)}`); } // Execute #2 — drive the running OpenAPI server. Covers per-connection // tool registration, array list response, path param dispatch, and object // responses — all going out over real HTTP from inside QuickJS, via the // v2 `tools....` address. const invokeCode = `${unwrapHelper} const list = unwrapToolData(await tools.petstore.org.main.pets.listPets({})); const fetchedData = unwrapToolData(await tools.petstore.org.main.pets.getPet({ petId: list[1].id })); return { count: list.length, names: list.map((p) => p.name), fetched: { id: fetchedData.id, name: fetchedData.name }, }; `; const result = await client.callTool({ name: "execute", arguments: { code: invokeCode }, }); const structured = await completePausedResult(client, result); const expected = { count: 2, names: ["Fido", "Whiskers"], fetched: { id: 2, name: "Whiskers" }, }; if (JSON.stringify(structured.result) !== JSON.stringify(expected)) { fail( `expected ${JSON.stringify(expected)}, got ${JSON.stringify(structured.result)} (content: ${JSON.stringify(result.content)})`, ); } await client.close(); console.log( `[smoke-sidecar] OK — listPets + getPet({petId:2}) round-tripped through the running OpenAPI server`, ); } finally { await cleanup(); } }; await main();