From 20ef2e837d1abc55ef91656786e266a86d4a9474 Mon Sep 17 00:00:00 2001 From: Lex Date: Wed, 26 Aug 2026 09:49:40 +0800 Subject: [PATCH 1/9] chore: record delivery binding for expose-negotiated-protocol --- .specgit.yaml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.specgit.yaml b/.specgit.yaml index 1a76cc0ea..76dea78f0 100644 --- a/.specgit.yaml +++ b/.specgit.yaml @@ -1,9 +1,7 @@ version: 1 -delivery: upgrade-mcp-client +delivery: expose-negotiated-protocol context: kind: branch - branch: refactor/447-upgrade-mcp-client + branch: feat/450-expose-negotiated-protocol issues: - - 447 - - 448 -pr: 449 + - 450 From 38b7294d97c16f808118b4028f20f688ed260cbe Mon Sep 17 00:00:00 2001 From: Lex Date: Wed, 26 Aug 2026 09:50:03 +0800 Subject: [PATCH 2/9] chore: record delivery binding for expose-negotiated-protocol --- .specgit.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.specgit.yaml b/.specgit.yaml index 76dea78f0..deb427936 100644 --- a/.specgit.yaml +++ b/.specgit.yaml @@ -5,3 +5,4 @@ context: branch: feat/450-expose-negotiated-protocol issues: - 450 +pr: 451 From 4a53e15d38e989a884e5f41bd8019bd893d249e9 Mon Sep 17 00:00:00 2001 From: Lex Date: Wed, 26 Aug 2026 09:50:39 +0800 Subject: [PATCH 3/9] chore: record delivery binding for add-mcp-dual --- .specgit.yaml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.specgit.yaml b/.specgit.yaml index 1a76cc0ea..0ac2cefae 100644 --- a/.specgit.yaml +++ b/.specgit.yaml @@ -1,9 +1,7 @@ version: 1 -delivery: upgrade-mcp-client +delivery: add-mcp-dual context: kind: branch - branch: refactor/447-upgrade-mcp-client + branch: test/452-add-mcp-dual issues: - - 447 - - 448 -pr: 449 + - 452 From a0042d9a807bb36802761bd1cb64b04009ff5ab6 Mon Sep 17 00:00:00 2001 From: Lex Date: Wed, 26 Aug 2026 09:51:01 +0800 Subject: [PATCH 4/9] chore: record delivery binding for add-mcp-dual --- .specgit.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.specgit.yaml b/.specgit.yaml index 0ac2cefae..080a1b4d5 100644 --- a/.specgit.yaml +++ b/.specgit.yaml @@ -5,3 +5,4 @@ context: branch: test/452-add-mcp-dual issues: - 452 +pr: 453 From 49578eea9c616e9405128f9cf849e1db258c4da3 Mon Sep 17 00:00:00 2001 From: Lex Date: Wed, 26 Aug 2026 09:51:26 +0800 Subject: [PATCH 5/9] chore: record delivery binding for ad-hoc-sign --- .specgit.yaml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.specgit.yaml b/.specgit.yaml index 1a76cc0ea..be5d5d7db 100644 --- a/.specgit.yaml +++ b/.specgit.yaml @@ -1,9 +1,7 @@ version: 1 -delivery: upgrade-mcp-client +delivery: ad-hoc-sign context: kind: branch - branch: refactor/447-upgrade-mcp-client + branch: fix/454-ad-hoc-sign issues: - - 447 - - 448 -pr: 449 + - 454 From 63521c1121364aaef9b75b7b99033d866f645e0d Mon Sep 17 00:00:00 2001 From: Lex Date: Wed, 26 Aug 2026 09:51:49 +0800 Subject: [PATCH 6/9] chore: record delivery binding for ad-hoc-sign --- .specgit.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.specgit.yaml b/.specgit.yaml index be5d5d7db..fb943af14 100644 --- a/.specgit.yaml +++ b/.specgit.yaml @@ -5,3 +5,4 @@ context: branch: fix/454-ad-hoc-sign issues: - 454 +pr: 455 From 21f4754d4baba18128cc84dd85df1ad489383705 Mon Sep 17 00:00:00 2001 From: Lex Date: Wed, 26 Aug 2026 10:10:18 +0800 Subject: [PATCH 7/9] feat(mcp): expose negotiated protocol era and version in server status --- packages/opencode/src/cli/cmd/mcp.ts | 2 +- packages/opencode/src/mcp/index.ts | 27 +++- .../opencode/test/mcp/protocol-era.test.ts | 138 ++++++++++++++++++ .../test/server/httpapi-exercise/index.ts | 24 ++- packages/sdk/js/src/v2/gen/types.gen.ts | 2 + 5 files changed, 187 insertions(+), 6 deletions(-) create mode 100644 packages/opencode/test/mcp/protocol-era.test.ts diff --git a/packages/opencode/src/cli/cmd/mcp.ts b/packages/opencode/src/cli/cmd/mcp.ts index 26090e105..eb84652e0 100644 --- a/packages/opencode/src/cli/cmd/mcp.ts +++ b/packages/opencode/src/cli/cmd/mcp.ts @@ -139,7 +139,7 @@ export const McpListCommand = effectCmd({ statusText = "not initialized" } else if (status.status === "connected") { statusIcon = "โœ“" - statusText = "connected" + statusText = `connected${status.era ? ` ยท ${status.era}` : ""}${status.protocolVersion ? ` (${status.protocolVersion})` : ""}` if (hasOAuth && hasStoredTokens) { hint = " (OAuth)" } diff --git a/packages/opencode/src/mcp/index.ts b/packages/opencode/src/mcp/index.ts index 91d253a83..2bbdcf95c 100644 --- a/packages/opencode/src/mcp/index.ts +++ b/packages/opencode/src/mcp/index.ts @@ -80,7 +80,26 @@ function createClient(directory: string, protocol?: "auto" | "legacy" | "modern" return client } -const StatusConnected = Schema.Struct({ status: Schema.Literal("connected") }).annotate({ +// Protocol versions are ISO dates and sort lexicographically, so >= the +// 2026-07-28 baseline (server/discover era) means modern. +export function protocolEra(version: string): "modern" | "legacy" { + return version >= "2026-07-28" ? "modern" : "legacy" +} + +// Connected status carries the negotiated protocol era/version when the client +// reports one; test doubles without the getter get the plain shape. +function connectedStatus(client: MCPClient): Status { + const protocolVersion = + typeof client.getNegotiatedProtocolVersion === "function" ? client.getNegotiatedProtocolVersion() : undefined + if (protocolVersion === undefined) return { status: "connected" } + return { status: "connected", era: protocolEra(protocolVersion), protocolVersion } +} + +const StatusConnected = Schema.Struct({ + status: Schema.Literal("connected"), + era: Schema.optional(Schema.Literals(["modern", "legacy"])), + protocolVersion: Schema.optional(Schema.String), +}).annotate({ identifier: "MCPStatusConnected", }) const StatusDisabled = Schema.Struct({ status: Schema.Literal("disabled") }).annotate({ @@ -308,7 +327,7 @@ export const layer = Layer.effect( return Effect.void }), ) - if (result) return { client: result, status: { status: "connected" } as Status } + if (result) return { client: result, status: connectedStatus(result) } return { client: undefined as MCPClient | undefined, @@ -339,7 +358,7 @@ export const layer = Layer.effect( return yield* connectTransport(transport, connectTimeout, mcp.protocol).pipe( Effect.map((client): { client: MCPClient | undefined; status: Status } => ({ client, - status: { status: "connected" }, + status: connectedStatus(client), })), Effect.catch((error): Effect.Effect<{ client: MCPClient | undefined; status: Status }> => { const msg = error instanceof Error ? error.message : String(error) @@ -561,7 +580,7 @@ export const layer = Layer.effect( ) { const bridge = yield* EffectBridge.make() const previous = s.clients[name] - s.status[name] = { status: "connected" } + s.status[name] = connectedStatus(client) s.clients[name] = client s.defs[name] = listed if (instructions) s.instructions[name] = instructions diff --git a/packages/opencode/test/mcp/protocol-era.test.ts b/packages/opencode/test/mcp/protocol-era.test.ts new file mode 100644 index 000000000..e6c56dc4b --- /dev/null +++ b/packages/opencode/test/mcp/protocol-era.test.ts @@ -0,0 +1,138 @@ +import { describe, expect, mock, test, beforeEach } from "bun:test" +import { Effect } from "effect" +import { testEffect } from "../lib/effect" + +// Negotiated protocol version reported by every MockClient; controlled per test. +let negotiatedVersion: string | undefined + +class MockUnauthorizedError extends Error { + constructor() { + super("Unauthorized") + this.name = "UnauthorizedError" + } +} + +// Successful stdio transport: start() resolves, so connect() completes and a +// connected status is built โ€” the seam this file exercises. +class MockStdioTransport { + async start() {} + async close() {} +} + +// v2 packs Client, StreamableHTTPClientTransport, and UnauthorizedError into +// the single package root, so one mock.module covers the whole surface. The +// Client mock bridges connect() into the (succeeding) mock transport and +// reports the per-test negotiated version. +void mock.module("@modelcontextprotocol/client/stdio", () => ({ + StdioClientTransport: MockStdioTransport, +})) + +void mock.module("@modelcontextprotocol/client", () => ({ + StreamableHTTPClientTransport: class MockStreamableHTTP { + async start() {} + async close() {} + }, + UnauthorizedError: MockUnauthorizedError, + Client: class MockClient { + onclose: (() => void) | undefined + setRequestHandler() {} + setNotificationHandler() {} + getServerCapabilities() { + return undefined + } + getInstructions() { + return undefined + } + getNegotiatedProtocolVersion() { + return negotiatedVersion + } + async connect(transport: { start: () => Promise }) { + await transport.start() + } + async close() {} + }, +})) + +beforeEach(() => { + negotiatedVersion = undefined +}) + +// Import MCP after mocking +const { MCP } = await import("../../src/mcp/index") +const it = testEffect(MCP.defaultLayer) + +describe("mcp.protocolEra derivation", () => { + test("versions at or above the 2026-07-28 baseline are modern", () => { + expect(MCP.protocolEra("2026-07-28")).toBe("modern") + expect(MCP.protocolEra("2026-08-15")).toBe("modern") + expect(MCP.protocolEra("2027-01-05")).toBe("modern") + }) + + test("2025-era versions are legacy", () => { + expect(MCP.protocolEra("2025-06-18")).toBe("legacy") + expect(MCP.protocolEra("2025-11-25")).toBe("legacy") + expect(MCP.protocolEra("2026-07-27")).toBe("legacy") + }) +}) + +describe("mcp connected status era population", () => { + it.instance( + "modern negotiated version surfaces era and protocolVersion", + () => + Effect.gen(function* () { + const mcp = yield* MCP.Service + negotiatedVersion = "2026-07-28" + yield* mcp.add("era-modern", { type: "local", command: ["echo", "test"] }) + const statuses = yield* mcp.status() + expect(statuses["era-modern"]).toEqual({ + status: "connected", + era: "modern", + protocolVersion: "2026-07-28", + }) + }), + { config: { mcp: {} } }, + ) + + it.instance( + "legacy negotiated version surfaces legacy era", + () => + Effect.gen(function* () { + const mcp = yield* MCP.Service + negotiatedVersion = "2025-06-18" + yield* mcp.add("era-legacy", { type: "local", command: ["echo", "test"] }) + const statuses = yield* mcp.status() + expect(statuses["era-legacy"]).toEqual({ + status: "connected", + era: "legacy", + protocolVersion: "2025-06-18", + }) + }), + { config: { mcp: {} } }, + ) + + it.instance( + "undefined negotiated version omits both fields", + () => + Effect.gen(function* () { + const mcp = yield* MCP.Service + yield* mcp.add("era-unreported", { type: "local", command: ["echo", "test"] }) + const statuses = yield* mcp.status() + expect(statuses["era-unreported"]).toEqual({ status: "connected" }) + }), + { config: { mcp: {} } }, + ) + + it.instance( + "status endpoint reflects the stored era fields", + () => + Effect.gen(function* () { + const mcp = yield* MCP.Service + negotiatedVersion = "2026-07-28" + yield* mcp.add("era-status", { type: "local", command: ["echo", "test"] }) + const statuses = yield* mcp.status() + expect(statuses["era-status"]?.status).toBe("connected") + expect(statuses["era-status"]).toMatchObject({ era: "modern", protocolVersion: "2026-07-28" }) + }), + { config: { mcp: {} } }, + ) +}) diff --git a/packages/opencode/test/server/httpapi-exercise/index.ts b/packages/opencode/test/server/httpapi-exercise/index.ts index 842399295..5b9ee14d9 100644 --- a/packages/opencode/test/server/httpapi-exercise/index.ts +++ b/packages/opencode/test/server/httpapi-exercise/index.ts @@ -372,7 +372,29 @@ const scenarios: Scenario[] = [ }), "status", ), - http.protected.get("/mcp", "mcp.status").json(), + http.protected.get("/mcp", "mcp.status").json( + 200, + (body) => { + object(body) + for (const entry of Object.values(body)) { + object(entry) + if (entry.status === "connected") { + check( + entry.era === undefined || entry.era === "modern" || entry.era === "legacy", + "connected MCP status era should be modern or legacy when present", + ) + check( + entry.protocolVersion === undefined || typeof entry.protocolVersion === "string", + "connected MCP status protocolVersion should be a string when present", + ) + } else { + check(entry.era === undefined, "MCP era field should only appear on connected statuses") + check(entry.protocolVersion === undefined, "MCP protocolVersion field should only appear on connected statuses") + } + } + }, + "status", + ), http.protected .post("/mcp", "mcp.add") .mutating() diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index ff7c1db68..c74b13d39 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -2466,6 +2466,8 @@ export type FormatterStatus = { export type McpStatusConnected = { status: "connected" + era?: "modern" | "legacy" + protocolVersion?: string } export type McpStatusDisabled = { From 2aa92590e0cc339eb92dd2a5745914e817bc4b7b Mon Sep 17 00:00:00 2001 From: Lex Date: Wed, 26 Aug 2026 10:21:23 +0800 Subject: [PATCH 8/9] test(mcp): add dual-era interop regression tests --- .../test/mcp/fixtures/interop-probe.ts | 89 +++++++++++++++++++ .../opencode/test/mcp/fixtures/server-http.ts | 29 ++++++ .../test/mcp/fixtures/server-stdio.ts | 23 +++++ packages/opencode/test/mcp/interop.test.ts | 71 +++++++++++++++ 4 files changed, 212 insertions(+) create mode 100644 packages/opencode/test/mcp/fixtures/interop-probe.ts create mode 100644 packages/opencode/test/mcp/fixtures/server-http.ts create mode 100644 packages/opencode/test/mcp/fixtures/server-stdio.ts create mode 100644 packages/opencode/test/mcp/interop.test.ts diff --git a/packages/opencode/test/mcp/fixtures/interop-probe.ts b/packages/opencode/test/mcp/fixtures/interop-probe.ts new file mode 100644 index 000000000..f460e3948 --- /dev/null +++ b/packages/opencode/test/mcp/fixtures/interop-probe.ts @@ -0,0 +1,89 @@ +// Runs the real v2 client against the fixture servers in a fresh process. +// Sibling test files mock.module("@modelcontextprotocol/client") in the shared +// bun test process, so real-transport coverage must not import the SDK there. +import path from "node:path" +import { Client, StreamableHTTPClientTransport } from "@modelcontextprotocol/client" +import { StdioClientTransport } from "@modelcontextprotocol/client/stdio" + +const mode = process.argv[2] ?? "stdio-auto" +const TIMEOUT_MS = 10_000 +let httpServer: Bun.Subprocess<"ignore", "pipe", "inherit"> | undefined + +process.on("SIGTERM", () => { + httpServer?.kill() + process.exit(1) +}) + +function withTimeout(promise: Promise, message: string): Promise { + return new Promise((resolve, reject) => { + const timer = setTimeout(() => reject(new Error(message)), TIMEOUT_MS) + promise.then( + (value) => { + clearTimeout(timer) + resolve(value) + }, + (error) => { + clearTimeout(timer) + reject(error) + }, + ) + }) +} + +function firstText(result: { content: Array<{ type: string; text?: string }> }) { + return result.content[0]?.text +} + +async function runStdio(legacy: boolean) { + const client = new Client({ name: "interop-probe", version: "0.0.0" }, { versionNegotiation: { mode: legacy ? "legacy" : "auto" } }) + const transport = new StdioClientTransport({ + command: process.execPath, + args: [path.join(import.meta.dir, "server-stdio.ts")], + }) + await withTimeout(client.connect(transport), "stdio connect timed out") + try { + const result = await client.callTool({ name: "echo", arguments: { text: legacy ? "legacy" : "hello" } }) + return { era: client.getProtocolEra(), version: client.getNegotiatedProtocolVersion(), echo: firstText(result as { content: Array<{ type: string; text?: string }> }) } + } finally { + await client.close() + } +} + +async function readListeningPort(stream: ReadableStream): Promise { + const reader = stream.getReader() + const decoder = new TextDecoder() + let buffer = "" + while (true) { + const { done, value } = await reader.read() + if (value) buffer += decoder.decode(value) + const port = Number(buffer.match(/listening (\d+)/)?.[1]) + if (port) { + reader.releaseLock() + return port + } + if (done) throw new Error(`http fixture exited before reporting a port: ${buffer}`) + } +} + +async function runHttp() { + httpServer = Bun.spawn([process.execPath, path.join(import.meta.dir, "server-http.ts")], { + cwd: path.join(import.meta.dir, "../../.."), + stdout: "pipe", + stderr: "inherit", + }) + const port = await withTimeout(readListeningPort(httpServer.stdout), "http fixture did not report a port in time") + const client = new Client({ name: "interop-probe", version: "0.0.0" }, { versionNegotiation: { mode: "auto" } }) + await withTimeout(client.connect(new StreamableHTTPClientTransport(new URL(`http://127.0.0.1:${port}/mcp`))), "http connect timed out") + try { + const result = await client.callTool({ name: "echo", arguments: { text: "http" } }) + return { era: client.getProtocolEra(), version: client.getNegotiatedProtocolVersion(), echo: firstText(result as { content: Array<{ type: string; text?: string }> }) } + } finally { + await client.close() + httpServer.kill() + await httpServer.exited + } +} + +const outcome = mode === "http-auto" ? await runHttp() : await runStdio(mode === "stdio-legacy") +console.log(JSON.stringify({ mode, ...outcome })) +process.exit(0) diff --git a/packages/opencode/test/mcp/fixtures/server-http.ts b/packages/opencode/test/mcp/fixtures/server-http.ts new file mode 100644 index 000000000..09aeb752d --- /dev/null +++ b/packages/opencode/test/mcp/fixtures/server-http.ts @@ -0,0 +1,29 @@ +import { z } from "zod" +import { McpServer, createMcpHandler } from "@modelcontextprotocol/server" + +const factory = () => { + const server = new McpServer({ name: "v2-fixture-http", version: "1.0.0" }, { capabilities: { tools: {} } }) + server.registerTool( + "echo", + { + description: "echo the input back", + inputSchema: { text: z.string() }, + }, + async ({ text }) => ({ content: [{ type: "text" as const, text: `echo-http:${text}` }] }), + ) + return server +} + +const handler = createMcpHandler(factory) +const server = Bun.serve({ + port: 0, + fetch: async (req) => { + try { + return await handler.fetch(req) + } catch (e) { + console.error("[fixture]", e) + return new Response(JSON.stringify({ error: String(e) }), { status: 500 }) + } + }, +}) +console.log(`listening ${server.port}`) diff --git a/packages/opencode/test/mcp/fixtures/server-stdio.ts b/packages/opencode/test/mcp/fixtures/server-stdio.ts new file mode 100644 index 000000000..b54938541 --- /dev/null +++ b/packages/opencode/test/mcp/fixtures/server-stdio.ts @@ -0,0 +1,23 @@ +// Dual-era fixture: the SAME factory serves 2026-07-28 (server/discover) and +// legacy (initialize) clients โ€” serveStdio owns the era decision per connection. +import { z } from "zod" +import { McpServer } from "@modelcontextprotocol/server" +import { serveStdio } from "@modelcontextprotocol/server/stdio" + +serveStdio( + () => { + const server = new McpServer({ name: "v2-fixture", version: "1.0.0" }, { capabilities: { tools: {} } }) + server.registerTool( + "echo", + { + description: "echo the input back", + inputSchema: { text: z.string() }, + }, + async ({ text }) => ({ content: [{ type: "text", text: `echo:${text}` }] }), + ) + return server + }, + { + onerror: (e) => console.error("[fixture]", e.message), + }, +) diff --git a/packages/opencode/test/mcp/interop.test.ts b/packages/opencode/test/mcp/interop.test.ts new file mode 100644 index 000000000..85fa527fe --- /dev/null +++ b/packages/opencode/test/mcp/interop.test.ts @@ -0,0 +1,71 @@ +import path from "node:path" +import { afterEach, describe, expect, test } from "bun:test" + +// Local to this branch: the era helper from #450 is not available here. +const MODERN_VERSION = "2026-07-28" +const LEGACY_VERSION = "2025-11-25" +const PROBE_TIMEOUT_MS = 10_000 + +const children: Array> = [] + +afterEach(() => { + for (const child of children.splice(0)) child.kill() +}) + +function withTimeout(promise: Promise, message: string, ms = PROBE_TIMEOUT_MS): Promise { + return new Promise((resolve, reject) => { + const timer = setTimeout(() => reject(new Error(message)), ms) + promise.then( + (value) => { + clearTimeout(timer) + resolve(value) + }, + (error) => { + clearTimeout(timer) + reject(error) + }, + ) + }) +} + +// The probe drives the real v2 client in a fresh process: sibling files in +// test/mcp mock.module the client SDK in the shared bun test process. +async function runProbe(mode: "stdio-auto" | "stdio-legacy" | "http-auto") { + const child = Bun.spawn([process.execPath, path.join(import.meta.dir, "fixtures", "interop-probe.ts"), mode], { + cwd: path.join(import.meta.dir, "../.."), + stdout: "pipe", + stderr: "inherit", + }) + children.push(child) + const [code, stdout] = await withTimeout( + Promise.all([child.exited, Bun.readableStreamToText(child.stdout)]), + `interop probe ${mode} timed out`, + ) + return { code, result: JSON.parse(stdout) as { era?: string; version?: string; echo?: string } } +} + +describe("mcp dual-era interop", () => { + test("stdio auto negotiates 2026-07-28 and round-trips a tool call", async () => { + const { code, result } = await runProbe("stdio-auto") + expect(code, JSON.stringify(result)).toBe(0) + expect(result.era).toBe("modern") + expect(result.version).toBe(MODERN_VERSION) + expect(result.echo).toBe("echo:hello") + }) + + test("stdio legacy pin negotiates 2025-11-25 against the same fixture", async () => { + const { code, result } = await runProbe("stdio-legacy") + expect(code, JSON.stringify(result)).toBe(0) + expect(result.era).toBe("legacy") + expect(result.version).toBe(LEGACY_VERSION) + expect(result.echo).toBe("echo:legacy") + }) + + test("streamable http auto negotiates 2026-07-28 and round-trips a tool call", async () => { + const { code, result } = await runProbe("http-auto") + expect(code, JSON.stringify(result)).toBe(0) + expect(result.era).toBe("modern") + expect(result.version).toBe(MODERN_VERSION) + expect(result.echo).toBe("echo-http:http") + }) +}) From e463690e71030e935700d26894d355ecb0a98d8f Mon Sep 17 00:00:00 2001 From: Lex Date: Wed, 26 Aug 2026 10:28:43 +0800 Subject: [PATCH 9/9] fix(build): ad-hoc sign darwin binaries before smoke test --- packages/opencode/script/build.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/opencode/script/build.ts b/packages/opencode/script/build.ts index 8b2e9cf02..e26402584 100755 --- a/packages/opencode/script/build.ts +++ b/packages/opencode/script/build.ts @@ -209,6 +209,11 @@ for (const item of targets) { }, }) + // macOS kills freshly compiled unsigned darwin binaries (SIGKILL 137) before they can run + if (process.platform === "darwin" && item.os === "darwin" && fs.existsSync("/usr/bin/codesign")) { + await $`codesign --force --sign - dist/${name}/bin/opencode` + } + // Smoke test: only run if binary is for current platform if (item.os === process.platform && item.arch === process.arch && !item.abi) { const binaryPath = `dist/${name}/bin/opencode`