import { chmodSync, mkdirSync, mkdtempSync, rmSync } from 'node:fs' import { tmpdir } from 'node:os' import { join, resolve } from 'node:path' import { afterEach, describe, expect, it } from 'vitest' import { remoteRuntimeBundleManifestSchema, type RemoteRuntimeBundleManifest } from '../shared/remote-runtime-launch-contracts' import { createOpenCodeLaunchProfile } from './opencode-runtime-profile' const temporaryPaths: string[] = [] afterEach(() => { for (const path of temporaryPaths.splice(0)) { rmSync(path, { recursive: true, force: true }) } }) describe('OpenCode direct launch profile', () => { it('runs Ask directly in the Workspace', () => { const fixture = createFixture() const profile = createOpenCodeLaunchProfile({ ...fixture, workMode: 'ask' }) expect(profile.executable).toBe( resolve(fixture.bundleDirectory, 'bin', 'opencode') ) expect(profile.processExecutable).toBe(profile.executable) expect(profile.args).toEqual(['acp']) expect(profile.cwd).toBe(fixture.workspaceDirectory) expect(profile.env).toMatchObject({ HOME: process.env.HOME ?? fixture.workspaceDirectory, PATH: process.env.PATH || '/usr/bin:/bin' }) expect( JSON.parse(profile.env.OPENCODE_CONFIG_CONTENT!) ).toMatchObject({ permission: 'ask', agent: { build: { permission: 'ask' } } }) }) it('runs Execute directly with the SSH account environment', () => { const fixture = createFixture() const execute = createOpenCodeLaunchProfile({ ...fixture, workMode: 'execute' }) expect(execute.executable).toBe( resolve(fixture.bundleDirectory, 'bin', 'opencode') ) expect(execute.processExecutable).toBe(execute.executable) expect(execute.args).toEqual(['acp']) expect(execute.cwd).toBe(fixture.workspaceDirectory) expect(execute.env).toMatchObject({ HOME: process.env.HOME ?? fixture.workspaceDirectory, LANG: process.env.LANG || 'C.UTF-8', LC_ALL: process.env.LC_ALL || 'C.UTF-8', PATH: process.env.PATH || '/usr/bin:/bin', TMPDIR: process.env.TMPDIR || '/tmp', XDG_CACHE_HOME: process.env.XDG_CACHE_HOME || resolve( process.env.HOME ?? fixture.workspaceDirectory, '.cache' ), XDG_CONFIG_HOME: process.env.XDG_CONFIG_HOME || resolve( process.env.HOME ?? fixture.workspaceDirectory, '.config' ), XDG_DATA_HOME: process.env.XDG_DATA_HOME || resolve( process.env.HOME ?? fixture.workspaceDirectory, '.local', 'share' ), XDG_STATE_HOME: process.env.XDG_STATE_HOME || resolve( process.env.HOME ?? fixture.workspaceDirectory, '.local', 'state' ) }) expect(execute.args).not.toContain('--unshare-all') }) it('keeps Runtime arguments credential-free and rejects a changed manifest', () => { const fixture = createFixture() const profile = createOpenCodeLaunchProfile({ ...fixture, workMode: 'ask' }) expect(profile.args.join('\0')).not.toContain('ANTHROPIC_API_KEY') expect(() => createOpenCodeLaunchProfile({ ...fixture, manifest: remoteRuntimeBundleManifestSchema.parse({ ...fixture.manifest, allowedEnvironmentNames: ['HOME'] }), workMode: 'ask' }) ).toThrow(/environment allowlist/iu) expect(() => createOpenCodeLaunchProfile({ ...fixture, workspaceDirectory: 'relative/workspace', workMode: 'ask' }) ).toThrow(/normalized absolute/iu) }) it('does not impose mount-layout constraints on direct launches', () => { const fixture = createFixture() for (const conflict of [ { workspaceDirectory: resolve( fixture.bundleDirectory, 'workspace' ) }, { workspaceDirectory: resolve( fixture.bundleDirectory, '..' ) } ]) { expect( createOpenCodeLaunchProfile({ ...fixture, ...conflict, workMode: 'ask' }) ).toMatchObject({ workMode: 'ask' }) } }) it('launches the signed Agent helper with fixed credential-free bridge argv', () => { const fixture = createFixture() const bridgeDirectory = resolve( fixture.workspaceDirectory, '..', 'bridge' ) mkdirSync(bridgeDirectory, { mode: 0o700 }) const agentInstallationDirectory = resolve( fixture.workspaceDirectory, '..', 'agent-installation' ) mkdirSync(agentInstallationDirectory, { mode: 0o700 }) const agentExecutablePath = resolve( agentInstallationDirectory, 'goodbuddy-agent' ) const socketPath = resolve( bridgeDirectory, 'model-bridge.sock' ) const profile = createOpenCodeLaunchProfile({ ...fixture, workMode: 'ask', modelBridge: { agentExecutablePath, bridgeDirectory, socketPath, policy: { protocol: 'anthropic-messages', model: 'private-model', modelProfileDigest: `sha256:${'9'.repeat(64)}`, supportsImageInput: false } } }) expect(profile.executable).toBe( resolve(agentInstallationDirectory, 'node') ) expect(profile.args).toEqual([ resolve(agentInstallationDirectory, 'lib', 'agent.cjs'), 'model-bridge-helper', '--socket-path', socketPath, '--protocol', 'anthropic-messages', '--model', 'private-model', '--supports-image-input', 'false', '--work-mode', 'ask', '--opencode-entrypoint', resolve(fixture.bundleDirectory, 'bin', 'opencode') ]) expect(profile.processExecutable).toBe( resolve(agentInstallationDirectory, 'node') ) expect(profile.args.join('\0')).not.toMatch( /api[-_]?key|authorization|bearer|(?:^|\0)--token(?:\0|$)/iu ) const executeProfile = createOpenCodeLaunchProfile({ ...fixture, workMode: 'execute', modelBridge: { agentExecutablePath, bridgeDirectory, socketPath, policy: { protocol: 'anthropic-messages', model: 'private-model', modelProfileDigest: `sha256:${'9'.repeat(64)}`, supportsImageInput: false } } }) expect(executeProfile.executable).toBe( resolve(agentInstallationDirectory, 'node') ) expect(executeProfile.processExecutable).toBe( resolve(agentInstallationDirectory, 'node') ) expect(executeProfile.args[0]).toBe( resolve(agentInstallationDirectory, 'lib', 'agent.cjs') ) }) it('allows Execute to use a workspace that contains managed Runtime paths', () => { const fixture = createFixture() const workspaceDirectory = resolve( fixture.workspaceDirectory, '..' ) const managedRoot = resolve(workspaceDirectory, '.goodbuddy') const bundleDirectory = resolve( managedRoot, 'runtimes', 'opencode' ) const agentInstallationDirectory = resolve( managedRoot, 'agent', 'installation' ) const bridgeDirectory = resolve( managedRoot, 'bridges', 'prompt' ) const modelBridge = { agentExecutablePath: resolve( agentInstallationDirectory, 'goodbuddy-agent' ), bridgeDirectory, socketPath: resolve( bridgeDirectory, 'model-bridge.sock' ), policy: { protocol: 'anthropic-messages' as const, model: 'private-model', modelProfileDigest: `sha256:${'9'.repeat(64)}`, supportsImageInput: false } } expect( createOpenCodeLaunchProfile({ manifest: fixture.manifest, bundleDirectory, workspaceDirectory, workMode: 'execute', modelBridge }) ).toMatchObject({ cwd: workspaceDirectory, workMode: 'execute' }) expect( createOpenCodeLaunchProfile({ manifest: fixture.manifest, bundleDirectory, workspaceDirectory, workMode: 'ask', modelBridge }) ).toMatchObject({ cwd: workspaceDirectory, workMode: 'ask' }) }) }) function createFixture(): { manifest: RemoteRuntimeBundleManifest bundleDirectory: string workspaceDirectory: string } { const root = resolve( mkdtempSync(join(tmpdir(), 'goodbuddy-opencode-profile-')) ) temporaryPaths.push(root) const bundleDirectory = resolve(root, 'bundle') const workspaceDirectory = resolve(root, 'workspace') for (const directory of [root, bundleDirectory, workspaceDirectory]) { mkdirSync(directory, { recursive: true, mode: 0o700 }) if (process.platform !== 'win32') { chmodSync(directory, 0o700) } } return { bundleDirectory, workspaceDirectory, manifest: remoteRuntimeBundleManifestSchema.parse({ formatVersion: 2, product: 'GoodBuddy', runtimeId: 'opencode', runtimeVersion: '1.18.29', provider: 'opencode', platform: 'linux', architecture: 'x64', signingKeyId: 'runtime-test', bundleDigest: `sha256:${'a'.repeat(64)}`, adapterDigest: `sha256:${'b'.repeat(64)}`, sourcePackage: { name: 'opencode-linux-x64-baseline', integrity: 'sha512-x4KiJk9EF7ktM18Ru5Jue4kTntxMvlhWb7tHniQGGRvY2KeoK1iIkyAFd7ri5H/fSkM22hNv/Gg1Jk6/h9IlxQ==' }, entrypoint: { identity: 'opencode-acp', path: 'bin/opencode', sha256: 'd'.repeat(64), argvPrefix: ['acp'] }, files: [ { path: 'bin/opencode', size: 64, sha256: 'd'.repeat(64), mode: '0755' }, { path: 'licenses/opencode.txt', size: 4, sha256: 'e'.repeat(64), mode: '0644' } ], licenses: [ { package: 'opencode-ai', version: '1.18.29', spdx: 'MIT', path: 'licenses/opencode.txt' } ], allowedEnvironmentNames: [ 'HOME', 'LANG', 'LC_ALL', 'PATH', 'TMPDIR', 'XDG_CACHE_HOME', 'XDG_CONFIG_HOME', 'XDG_DATA_HOME', 'XDG_STATE_HOME' ], protocol: { major: 1, minor: 0 }, acpCapabilitiesDigest: `sha256:${'f'.repeat(64)}`, limits: { maximumPromptRuntimeMilliseconds: 60_000, maximumPromptInputBytes: 4096, maximumPromptOutputBytes: 1024 * 1024 } }) } }