fix(opencode): refresh bridge MCP env per command
This commit is contained in:
parent
48826af00b
commit
f3f38dd0e9
4 changed files with 73 additions and 3 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
import { pathToFileURL } from 'node:url';
|
import { pathToFileURL } from 'url';
|
||||||
|
|
||||||
import { FastMCP } from 'fastmcp';
|
import { FastMCP } from 'fastmcp';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -415,10 +415,30 @@ async function createOpenCodeRuntimeAdapterRegistry(
|
||||||
}
|
}
|
||||||
|
|
||||||
reportProgress('runtime-bridge', 'Preparing OpenCode bridge...');
|
reportProgress('runtime-bridge', 'Preparing OpenCode bridge...');
|
||||||
|
const resolveBridgeCommandEnv = async (): Promise<NodeJS.ProcessEnv> => {
|
||||||
|
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({
|
const bridgeClient = new OpenCodeBridgeCommandClient({
|
||||||
binaryPath,
|
binaryPath,
|
||||||
tempDirectory: join(app.getPath('temp'), 'claude-team-opencode-bridge'),
|
tempDirectory: join(app.getPath('temp'), 'claude-team-opencode-bridge'),
|
||||||
env: bridgeEnv,
|
env: bridgeEnv,
|
||||||
|
envProvider: resolveBridgeCommandEnv,
|
||||||
});
|
});
|
||||||
const bridgeControlDir = join(app.getPath('userData'), 'opencode-bridge');
|
const bridgeControlDir = join(app.getPath('userData'), 'opencode-bridge');
|
||||||
const clientIdentity = createOpenCodeBridgeClientIdentity({
|
const clientIdentity = createOpenCodeBridgeClientIdentity({
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@ export interface OpenCodeBridgeCommandClientOptions {
|
||||||
diagnosticIdFactory?: () => string;
|
diagnosticIdFactory?: () => string;
|
||||||
clock?: () => Date;
|
clock?: () => Date;
|
||||||
env?: NodeJS.ProcessEnv;
|
env?: NodeJS.ProcessEnv;
|
||||||
|
envProvider?: () => NodeJS.ProcessEnv | Promise<NodeJS.ProcessEnv>;
|
||||||
keepInputFile?: boolean;
|
keepInputFile?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -102,6 +103,7 @@ export class OpenCodeBridgeCommandClient {
|
||||||
private readonly diagnosticIdFactory: () => string;
|
private readonly diagnosticIdFactory: () => string;
|
||||||
private readonly clock: () => Date;
|
private readonly clock: () => Date;
|
||||||
private readonly env: NodeJS.ProcessEnv;
|
private readonly env: NodeJS.ProcessEnv;
|
||||||
|
private readonly envProvider: (() => NodeJS.ProcessEnv | Promise<NodeJS.ProcessEnv>) | null;
|
||||||
private readonly keepInputFile: boolean;
|
private readonly keepInputFile: boolean;
|
||||||
|
|
||||||
constructor(options: OpenCodeBridgeCommandClientOptions) {
|
constructor(options: OpenCodeBridgeCommandClientOptions) {
|
||||||
|
|
@ -114,6 +116,7 @@ export class OpenCodeBridgeCommandClient {
|
||||||
options.diagnosticIdFactory ?? (() => `opencode-bridge-diagnostic-${randomUUID()}`);
|
options.diagnosticIdFactory ?? (() => `opencode-bridge-diagnostic-${randomUUID()}`);
|
||||||
this.clock = options.clock ?? (() => new Date());
|
this.clock = options.clock ?? (() => new Date());
|
||||||
this.env = applyOpenCodeAutoUpdatePolicy(options.env ?? process.env);
|
this.env = applyOpenCodeAutoUpdatePolicy(options.env ?? process.env);
|
||||||
|
this.envProvider = options.envProvider ?? null;
|
||||||
this.keepInputFile = options.keepInputFile ?? false;
|
this.keepInputFile = options.keepInputFile ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -147,7 +150,7 @@ export class OpenCodeBridgeCommandClient {
|
||||||
timeoutMs: options.timeoutMs,
|
timeoutMs: options.timeoutMs,
|
||||||
stdoutLimitBytes: options.stdoutLimitBytes ?? DEFAULT_STDOUT_LIMIT_BYTES,
|
stdoutLimitBytes: options.stdoutLimitBytes ?? DEFAULT_STDOUT_LIMIT_BYTES,
|
||||||
stderrLimitBytes: options.stderrLimitBytes ?? DEFAULT_STDERR_LIMIT_BYTES,
|
stderrLimitBytes: options.stderrLimitBytes ?? DEFAULT_STDERR_LIMIT_BYTES,
|
||||||
env: this.env,
|
env: await this.resolveEnv(),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (processResult.timedOut) {
|
if (processResult.timedOut) {
|
||||||
|
|
@ -195,6 +198,13 @@ export class OpenCodeBridgeCommandClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async resolveEnv(): Promise<NodeJS.ProcessEnv> {
|
||||||
|
if (!this.envProvider) {
|
||||||
|
return this.env;
|
||||||
|
}
|
||||||
|
return applyOpenCodeAutoUpdatePolicy(await this.envProvider());
|
||||||
|
}
|
||||||
|
|
||||||
private async writeInputFile<TBody>(
|
private async writeInputFile<TBody>(
|
||||||
envelope: OpenCodeBridgeCommandEnvelope<TBody>
|
envelope: OpenCodeBridgeCommandEnvelope<TBody>
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
|
|
|
||||||
|
|
@ -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', () => {
|
describe('redactBridgeDiagnosticText', () => {
|
||||||
|
|
@ -205,7 +242,9 @@ describe('redactBridgeDiagnosticText', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function createClient(): OpenCodeBridgeCommandClient {
|
function createClient(
|
||||||
|
overrides: Partial<ConstructorParameters<typeof OpenCodeBridgeCommandClient>[0]> = {}
|
||||||
|
): OpenCodeBridgeCommandClient {
|
||||||
return new OpenCodeBridgeCommandClient({
|
return new OpenCodeBridgeCommandClient({
|
||||||
binaryPath: '/usr/local/bin/agent-teams-controller',
|
binaryPath: '/usr/local/bin/agent-teams-controller',
|
||||||
tempDirectory: tempDir,
|
tempDirectory: tempDir,
|
||||||
|
|
@ -215,6 +254,7 @@ function createClient(): OpenCodeBridgeCommandClient {
|
||||||
diagnosticIdFactory: () => 'diag-1',
|
diagnosticIdFactory: () => 'diag-1',
|
||||||
clock: () => new Date('2026-04-21T12:00:00.000Z'),
|
clock: () => new Date('2026-04-21T12:00:00.000Z'),
|
||||||
env: { PATH: '/usr/bin' },
|
env: { PATH: '/usr/bin' },
|
||||||
|
...overrides,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue