diff --git a/mcp-server/src/index.ts b/mcp-server/src/index.ts index 59abcf0b..5445e709 100644 --- a/mcp-server/src/index.ts +++ b/mcp-server/src/index.ts @@ -1,5 +1,5 @@ #!/usr/bin/env node -import { pathToFileURL } from 'node:url'; +import { pathToFileURL } from 'url'; import { FastMCP } from 'fastmcp'; diff --git a/src/main/index.ts b/src/main/index.ts index 2fe0b20c..dc822884 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -415,10 +415,30 @@ async function createOpenCodeRuntimeAdapterRegistry( } reportProgress('runtime-bridge', 'Preparing OpenCode bridge...'); + const resolveBridgeCommandEnv = async (): Promise => { + const nextEnv = { ...bridgeEnv }; + if (!bridgeEnv.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_URL) { + return nextEnv; + } + try { + const mcpHttpServer = await agentTeamsMcpHttpServer.ensureStarted(); + bridgeEnv.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_URL = mcpHttpServer.url; + nextEnv.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_URL = mcpHttpServer.url; + } catch (error) { + delete nextEnv.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_URL; + logger.warn( + `[OpenCode] Runtime adapter bridge MCP HTTP server refresh failed: ${ + error instanceof Error ? error.message : String(error) + }` + ); + } + return nextEnv; + }; const bridgeClient = new OpenCodeBridgeCommandClient({ binaryPath, tempDirectory: join(app.getPath('temp'), 'claude-team-opencode-bridge'), env: bridgeEnv, + envProvider: resolveBridgeCommandEnv, }); const bridgeControlDir = join(app.getPath('userData'), 'opencode-bridge'); const clientIdentity = createOpenCodeBridgeClientIdentity({ diff --git a/src/main/services/team/opencode/bridge/OpenCodeBridgeCommandClient.ts b/src/main/services/team/opencode/bridge/OpenCodeBridgeCommandClient.ts index 4ec07423..f5602d64 100644 --- a/src/main/services/team/opencode/bridge/OpenCodeBridgeCommandClient.ts +++ b/src/main/services/team/opencode/bridge/OpenCodeBridgeCommandClient.ts @@ -51,6 +51,7 @@ export interface OpenCodeBridgeCommandClientOptions { diagnosticIdFactory?: () => string; clock?: () => Date; env?: NodeJS.ProcessEnv; + envProvider?: () => NodeJS.ProcessEnv | Promise; keepInputFile?: boolean; } @@ -102,6 +103,7 @@ export class OpenCodeBridgeCommandClient { private readonly diagnosticIdFactory: () => string; private readonly clock: () => Date; private readonly env: NodeJS.ProcessEnv; + private readonly envProvider: (() => NodeJS.ProcessEnv | Promise) | null; private readonly keepInputFile: boolean; constructor(options: OpenCodeBridgeCommandClientOptions) { @@ -114,6 +116,7 @@ export class OpenCodeBridgeCommandClient { options.diagnosticIdFactory ?? (() => `opencode-bridge-diagnostic-${randomUUID()}`); this.clock = options.clock ?? (() => new Date()); this.env = applyOpenCodeAutoUpdatePolicy(options.env ?? process.env); + this.envProvider = options.envProvider ?? null; this.keepInputFile = options.keepInputFile ?? false; } @@ -147,7 +150,7 @@ export class OpenCodeBridgeCommandClient { timeoutMs: options.timeoutMs, stdoutLimitBytes: options.stdoutLimitBytes ?? DEFAULT_STDOUT_LIMIT_BYTES, stderrLimitBytes: options.stderrLimitBytes ?? DEFAULT_STDERR_LIMIT_BYTES, - env: this.env, + env: await this.resolveEnv(), }); if (processResult.timedOut) { @@ -195,6 +198,13 @@ export class OpenCodeBridgeCommandClient { } } + private async resolveEnv(): Promise { + if (!this.envProvider) { + return this.env; + } + return applyOpenCodeAutoUpdatePolicy(await this.envProvider()); + } + private async writeInputFile( envelope: OpenCodeBridgeCommandEnvelope ): Promise { diff --git a/test/main/services/team/OpenCodeBridgeCommandClient.test.ts b/test/main/services/team/OpenCodeBridgeCommandClient.test.ts index f5daf35d..ef0c7479 100644 --- a/test/main/services/team/OpenCodeBridgeCommandClient.test.ts +++ b/test/main/services/team/OpenCodeBridgeCommandClient.test.ts @@ -189,6 +189,43 @@ describe('OpenCodeBridgeCommandClient', () => { }, }); }); + + it('resolves command env lazily for each bridge command', async () => { + runner.nextResult = { + stdout: `${JSON.stringify(bridgeSuccess({ data: { runId: 'run-1' } }))}\n`, + stderr: '', + exitCode: 0, + timedOut: false, + }; + let envVersion = 0; + const client = createClient({ + envProvider: () => { + envVersion += 1; + return { + PATH: '/usr/bin', + CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_URL: `http://127.0.0.1:${5000 + envVersion}/mcp`, + }; + }, + }); + + await client.execute('opencode.launchTeam', { runId: 'run-1' }, { + cwd: '/tmp/project', + timeoutMs: 10_000, + }); + await client.execute('opencode.launchTeam', { runId: 'run-2' }, { + cwd: '/tmp/project', + timeoutMs: 10_000, + }); + + expect(runner.calls[0].env).toMatchObject({ + CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_URL: 'http://127.0.0.1:5001/mcp', + OPENCODE_DISABLE_AUTOUPDATE: '1', + }); + expect(runner.calls[1].env).toMatchObject({ + CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_URL: 'http://127.0.0.1:5002/mcp', + OPENCODE_DISABLE_AUTOUPDATE: '1', + }); + }); }); describe('redactBridgeDiagnosticText', () => { @@ -205,7 +242,9 @@ describe('redactBridgeDiagnosticText', () => { }); }); -function createClient(): OpenCodeBridgeCommandClient { +function createClient( + overrides: Partial[0]> = {} +): OpenCodeBridgeCommandClient { return new OpenCodeBridgeCommandClient({ binaryPath: '/usr/local/bin/agent-teams-controller', tempDirectory: tempDir, @@ -215,6 +254,7 @@ function createClient(): OpenCodeBridgeCommandClient { diagnosticIdFactory: () => 'diag-1', clock: () => new Date('2026-04-21T12:00:00.000Z'), env: { PATH: '/usr/bin' }, + ...overrides, }); }