mirror of
https://github.com/stablyai/orca.git
synced 2026-10-07 08:02:21 +00:00
Install OpenCode hooks in the terminal's config directory (#25296)
* test: reproduce OpenCode plugin installation in the wrong config root * fix: install OpenCode hooks in the execution config directory * test: isolate config installation from CLI version probing * style: format consumer config installation controls * fix: use checked startup environment and supported relay shell context * fix: install OpenCode hooks using the selected execution shell * test(opencode): assert consumer config root in PTY fixtures --------- Co-authored-by: Codex <codex@openai.com> Co-authored-by: Orca <orca@stably.ai>
This commit is contained in:
@@ -7,7 +7,8 @@ import {
|
||||
import {
|
||||
expectedOmpStatusExtension,
|
||||
TEST_CODEX_HOME,
|
||||
TEST_CODEX_AUTH_JSON
|
||||
TEST_CODEX_AUTH_JSON,
|
||||
TEST_MANAGED_ROOT
|
||||
} from './pty-ipc-test-constants'
|
||||
import { setupPtyIpcSuite } from './pty-ipc-test-harness'
|
||||
import { createDaemonActiveProviderFixtures } from './pty-ipc-daemon-provider-fixtures'
|
||||
@@ -15,6 +16,8 @@ import { join } from 'node:path'
|
||||
import { SETUP_AGENT_SEQUENCE_STARTUP_COMMAND_ENV } from '../../shared/setup-agent-sequencing'
|
||||
import { registerPtyHandlers, resolveCodexHomeAfterManagedAuthReadiness } from './pty'
|
||||
|
||||
const consumerConfigHome = join(TEST_MANAGED_ROOT, 'opencode-consumer-config')
|
||||
|
||||
vi.mock('electron', () => import('./pty-ipc-mock-registry').then((m) => m.electronModuleMock()))
|
||||
vi.mock('fs', () => import('./pty-ipc-mock-registry').then((m) => m.fsModuleMock()))
|
||||
vi.mock('node-pty', () => import('./pty-ipc-mock-registry').then((m) => m.nodePtyModuleMock()))
|
||||
@@ -435,11 +438,15 @@ describe('registerPtyHandlers', () => {
|
||||
expect(env.ORCA_OPENCODE_HOOK_PORT).toBe('4567')
|
||||
})
|
||||
it('mirrors a user-provided OPENCODE_CONFIG_DIR into a source-scoped overlay on the daemon path', async () => {
|
||||
const env = await daemonSpawnAndGetEnv({ OPENCODE_CONFIG_DIR: '/user/custom/opencode' })
|
||||
const env = await daemonSpawnAndGetEnv({
|
||||
OPENCODE_CONFIG_DIR: '/user/custom/opencode',
|
||||
XDG_CONFIG_HOME: consumerConfigHome
|
||||
})
|
||||
// Why: OpenCode loads config from a single dir, so the user's path is mirrored into a source-scoped overlay, not passed through.
|
||||
expect(openCodeBuildPtyEnvMock).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
'/user/custom/opencode'
|
||||
'/user/custom/opencode',
|
||||
join(consumerConfigHome, 'opencode')
|
||||
)
|
||||
expect(env.OPENCODE_CONFIG_DIR).toBe('/tmp/orca-opencode-overlay')
|
||||
expect(env.ORCA_OPENCODE_CONFIG_DIR).toBe('/tmp/orca-opencode-overlay')
|
||||
@@ -448,11 +455,13 @@ describe('registerPtyHandlers', () => {
|
||||
it('uses source OpenCode config env instead of remirroring a parent overlay', async () => {
|
||||
const env = await daemonSpawnAndGetEnv({
|
||||
OPENCODE_CONFIG_DIR: '/tmp/parent-orca-opencode-overlay',
|
||||
ORCA_OPENCODE_SOURCE_CONFIG_DIR: '/user/custom/opencode'
|
||||
ORCA_OPENCODE_SOURCE_CONFIG_DIR: '/user/custom/opencode',
|
||||
XDG_CONFIG_HOME: consumerConfigHome
|
||||
})
|
||||
expect(openCodeBuildPtyEnvMock).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
'/user/custom/opencode'
|
||||
'/user/custom/opencode',
|
||||
join(consumerConfigHome, 'opencode')
|
||||
)
|
||||
expect(env.OPENCODE_CONFIG_DIR).toBe('/tmp/orca-opencode-overlay')
|
||||
expect(env.ORCA_OPENCODE_CONFIG_DIR).toBe('/tmp/orca-opencode-overlay')
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { join } from 'node:path'
|
||||
import { withFreshOmpLaunch } from '../../shared/omp-fresh-launch'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import {
|
||||
@@ -7,10 +8,12 @@ import {
|
||||
mimoCodeBuildPtyEnvMock,
|
||||
piBuildPtyEnvMock
|
||||
} from './pty-ipc-mock-registry'
|
||||
import { posixOnlyIt } from './pty-ipc-test-constants'
|
||||
import { posixOnlyIt, TEST_MANAGED_ROOT } from './pty-ipc-test-constants'
|
||||
import { setupPtyIpcSuite } from './pty-ipc-test-harness'
|
||||
import { SETUP_AGENT_SEQUENCE_STARTUP_COMMAND_ENV } from '../../shared/setup-agent-sequencing'
|
||||
|
||||
const consumerConfigHome = join(TEST_MANAGED_ROOT, 'opencode-consumer-config')
|
||||
|
||||
vi.mock('electron', () => import('./pty-ipc-mock-registry').then((m) => m.electronModuleMock()))
|
||||
vi.mock('fs', () => import('./pty-ipc-mock-registry').then((m) => m.fsModuleMock()))
|
||||
vi.mock('node-pty', () => import('./pty-ipc-mock-registry').then((m) => m.nodePtyModuleMock()))
|
||||
@@ -99,11 +102,13 @@ describe('registerPtyHandlers', () => {
|
||||
it('mirrors the original OpenCode source dir when launched from an Orca overlay shell', async () => {
|
||||
const env = await spawnAndGetEnv({
|
||||
OPENCODE_CONFIG_DIR: '/tmp/parent-orca-opencode-overlay',
|
||||
ORCA_OPENCODE_SOURCE_CONFIG_DIR: '/tmp/user-opencode-config'
|
||||
ORCA_OPENCODE_SOURCE_CONFIG_DIR: '/tmp/user-opencode-config',
|
||||
XDG_CONFIG_HOME: consumerConfigHome
|
||||
})
|
||||
expect(openCodeBuildPtyEnvMock).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
'/tmp/user-opencode-config'
|
||||
'/tmp/user-opencode-config',
|
||||
join(consumerConfigHome, 'opencode')
|
||||
)
|
||||
expect(env.OPENCODE_CONFIG_DIR).toBe('/tmp/orca-opencode-overlay')
|
||||
expect(env.ORCA_OPENCODE_CONFIG_DIR).toBe('/tmp/orca-opencode-overlay')
|
||||
@@ -112,10 +117,15 @@ describe('registerPtyHandlers', () => {
|
||||
it('does not treat inherited Orca OpenCode config as user config without a source dir', async () => {
|
||||
const env = await spawnAndGetEnv({
|
||||
OPENCODE_CONFIG_DIR: '/tmp/parent-orca-opencode-overlay',
|
||||
ORCA_OPENCODE_CONFIG_DIR: '/tmp/parent-orca-opencode-overlay'
|
||||
ORCA_OPENCODE_CONFIG_DIR: '/tmp/parent-orca-opencode-overlay',
|
||||
XDG_CONFIG_HOME: consumerConfigHome
|
||||
})
|
||||
|
||||
expect(openCodeBuildPtyEnvMock).toHaveBeenCalledWith(expect.any(String), undefined)
|
||||
expect(openCodeBuildPtyEnvMock).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
undefined,
|
||||
join(consumerConfigHome, 'opencode')
|
||||
)
|
||||
expect(env.OPENCODE_CONFIG_DIR).toBe('/tmp/orca-opencode-config')
|
||||
expect(env.ORCA_OPENCODE_CONFIG_DIR).toBe('/tmp/orca-opencode-config')
|
||||
expect(env.ORCA_OPENCODE_SOURCE_CONFIG_DIR).toBeUndefined()
|
||||
@@ -222,16 +232,20 @@ describe('registerPtyHandlers', () => {
|
||||
return ''
|
||||
})
|
||||
|
||||
const env = await spawnAndGetEnv(undefined, {
|
||||
HOME: '/home/pim',
|
||||
SHELL: '/bin/zsh',
|
||||
OPENCODE_CONFIG_DIR: undefined,
|
||||
ORCA_OPENCODE_SOURCE_CONFIG_DIR: undefined
|
||||
})
|
||||
const env = await spawnAndGetEnv(
|
||||
{ XDG_CONFIG_HOME: consumerConfigHome },
|
||||
{
|
||||
HOME: '/home/pim',
|
||||
SHELL: '/bin/zsh',
|
||||
OPENCODE_CONFIG_DIR: undefined,
|
||||
ORCA_OPENCODE_SOURCE_CONFIG_DIR: undefined
|
||||
}
|
||||
)
|
||||
|
||||
expect(openCodeBuildPtyEnvMock).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
'/home/pim/company/opencode-config'
|
||||
'/home/pim/company/opencode-config',
|
||||
join(consumerConfigHome, 'opencode')
|
||||
)
|
||||
expect(env.OPENCODE_CONFIG_DIR).toBe('/tmp/orca-opencode-overlay')
|
||||
expect(env.ORCA_OPENCODE_CONFIG_DIR).toBe('/tmp/orca-opencode-overlay')
|
||||
|
||||
@@ -6,6 +6,8 @@ import { openCode2HookService, openCodeHookService } from '../../../opencode/hoo
|
||||
import { resolveOpenCodeSourceConfigDir, restoreOrStripOverlayEnv } from './pi-agent'
|
||||
import { selectOpenCodeHookAgent } from '../../../../shared/opencode-launch-command'
|
||||
import { isTuiAgentEnabled } from '../../../../shared/tui-agent-selection'
|
||||
import { resolveOpenCodeConfigDirectory } from '../../../../shared/opencode-config-directory'
|
||||
import { readSessionShellStartupEnvVar } from '../../../pty/shell-startup-env'
|
||||
import type { BuildPtyHostEnvOptions } from './types'
|
||||
|
||||
type OpenCodeSourceConfig = {
|
||||
@@ -41,7 +43,7 @@ export function applyOpenCodeStatusPluginEnv(
|
||||
config: OpenCodeSourceConfig,
|
||||
options: Pick<
|
||||
BuildPtyHostEnvOptions,
|
||||
'launchAgent' | 'agentStatusHooksEnabled' | 'disabledTuiAgents' | 'isWsl'
|
||||
'launchAgent' | 'agentStatusHooksEnabled' | 'disabledTuiAgents' | 'isWsl' | 'shellPath'
|
||||
>,
|
||||
command: string | undefined
|
||||
): 'opencode' | 'opencode2' | null {
|
||||
@@ -69,7 +71,13 @@ export function applyOpenCodeStatusPluginEnv(
|
||||
env.ORCA_OPENCODE_AGENT = agent
|
||||
// WSL owns its config writes; only the guest overlay may enter a WSL pane.
|
||||
if (!options.isWsl) {
|
||||
Object.assign(env, service.buildPtyEnv(id, config.directory))
|
||||
const executionEnv = { ...process.env, ...env }
|
||||
const shellConfigHome = readSessionShellStartupEnvVar('XDG_CONFIG_HOME', env, options.shellPath)
|
||||
const defaultConfigDir = resolveOpenCodeConfigDirectory({
|
||||
...executionEnv,
|
||||
XDG_CONFIG_HOME: shellConfigHome ?? executionEnv.XDG_CONFIG_HOME
|
||||
})
|
||||
Object.assign(env, service.buildPtyEnv(id, config.directory, defaultConfigDir))
|
||||
}
|
||||
if (env.OPENCODE_CONFIG_DIR) {
|
||||
// Shell startup can re-export the default; preserve this pane's overlay and original source.
|
||||
|
||||
@@ -77,6 +77,85 @@ afterEach(() => {
|
||||
})
|
||||
|
||||
describe('OpenCode installation uses the current enabled agents', () => {
|
||||
it.each(
|
||||
process.platform === 'win32' ? (['home', 'xdg'] as const) : (['home', 'xdg', 'shell'] as const)
|
||||
)(
|
||||
'consumer config root follows the %s execution environment without redirecting JSON',
|
||||
(kind) => {
|
||||
const home = join(root, 'consumer-home')
|
||||
const xdg = join(root, 'consumer-xdg')
|
||||
const consumer = kind === 'home' ? join(home, '.config', 'opencode') : join(xdg, 'opencode')
|
||||
mkdirSync(home, { recursive: true })
|
||||
mkdirSync(consumer, { recursive: true })
|
||||
const settings = '{"model":"consumer-model","description":"external-settings"}'
|
||||
writeFileSync(join(consumer, 'opencode.json'), settings)
|
||||
if (kind === 'shell') {
|
||||
writeFileSync(join(home, '.zshrc'), `export XDG_CONFIG_HOME='${xdg}'\n`)
|
||||
}
|
||||
const input = {
|
||||
HOME: home,
|
||||
USERPROFILE: home,
|
||||
XDG_CONFIG_HOME: kind === 'xdg' ? xdg : '',
|
||||
SHELL: kind === 'shell' ? '/bin/zsh' : '/bin/sh'
|
||||
}
|
||||
const env = buildPtyHostEnv(
|
||||
'folder-pane',
|
||||
{ ...input },
|
||||
{ ...options, launchAgent: 'opencode' }
|
||||
)
|
||||
expect(existsSync(plugin(consumer, 'opencode'))).toBe(true)
|
||||
expect(existsSync(plugin(config, 'opencode'))).toBe(false)
|
||||
expect(readFileSync(join(consumer, 'opencode.json'), 'utf8')).toBe(settings)
|
||||
expect(env.OPENCODE_CONFIG_DIR).toBeUndefined()
|
||||
for (const key of ['HOME', 'USERPROFILE', 'XDG_CONFIG_HOME', 'SHELL'] as const) {
|
||||
expect(env[key]).toBe(input[key])
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
it.skipIf(process.platform === 'win32').each([
|
||||
{ selected: '/bin/bash', inherited: '/bin/zsh', file: '.bash_profile' },
|
||||
{ selected: '/bin/zsh', inherited: '/bin/bash', file: '.zshrc' }
|
||||
])('consumer config follows selected $selected over inherited $inherited', (selection) => {
|
||||
const home = join(root, 'selected-shell-home')
|
||||
const selectedXdg = join(root, 'selected-shell-xdg')
|
||||
const inheritedXdg = join(root, 'inherited-shell-xdg')
|
||||
mkdirSync(home)
|
||||
writeFileSync(
|
||||
join(home, '.bash_profile'),
|
||||
`export XDG_CONFIG_HOME='${selection.file === '.bash_profile' ? selectedXdg : inheritedXdg}'\n`
|
||||
)
|
||||
writeFileSync(
|
||||
join(home, '.zshrc'),
|
||||
`export XDG_CONFIG_HOME='${selection.file === '.zshrc' ? selectedXdg : inheritedXdg}'\n`
|
||||
)
|
||||
const input = { HOME: home, USERPROFILE: home, XDG_CONFIG_HOME: '', SHELL: selection.inherited }
|
||||
const launchOptions = {
|
||||
...options,
|
||||
launchAgent: 'opencode' as const,
|
||||
shellPath: selection.selected
|
||||
}
|
||||
const env = buildPtyHostEnv('selected-shell-pane', input, launchOptions)
|
||||
expect(existsSync(plugin(join(selectedXdg, 'opencode'), 'opencode'))).toBe(true)
|
||||
expect(existsSync(plugin(join(inheritedXdg, 'opencode'), 'opencode'))).toBe(false)
|
||||
expect(env.SHELL).toBe(selection.inherited)
|
||||
expect(env.OPENCODE_CONFIG_DIR).toBeUndefined()
|
||||
})
|
||||
|
||||
it.each([false, true])('consumer config root is untouched with hooks disabled %s', (disabled) => {
|
||||
const consumer = join(root, 'disabled-consumer')
|
||||
buildPtyHostEnv(
|
||||
'pane',
|
||||
{ XDG_CONFIG_HOME: consumer },
|
||||
{
|
||||
...options,
|
||||
agentStatusHooksEnabled: disabled,
|
||||
disabledTuiAgents: disabled ? ['opencode', 'opencode2'] : []
|
||||
}
|
||||
)
|
||||
expect(existsSync(join(consumer, 'opencode'))).toBe(false)
|
||||
})
|
||||
|
||||
it('refuses spawn if a prepared startup intent loses its owned installer', () => {
|
||||
mkdirSync(fixture.userData, { recursive: true })
|
||||
writeFileSync(
|
||||
|
||||
@@ -23,6 +23,8 @@ export type BuildPtyHostEnvOptions = {
|
||||
launchCommand?: string
|
||||
/** Trusted agent identity for wrapped commands that cannot be recognized from text. */
|
||||
launchAgent?: TuiAgent
|
||||
/** Selected execution shell, which may differ from inherited SHELL. */
|
||||
shellPath?: string
|
||||
isWsl?: boolean
|
||||
/** Distro for WSL spawns (null = Windows default distro); drives the WSL hook relay + endpoint repoint. Only read when isWsl. */
|
||||
wslDistro?: string | null
|
||||
|
||||
@@ -138,6 +138,7 @@ export async function assemblePtyIpcSpawnCodexEnv(ctx: PtyIpcSpawnState): Promis
|
||||
stripInheritedOrcaCodexHome: ctx.stripInheritedOrcaCodexHome,
|
||||
launchCommand: ctx.launchCommand,
|
||||
launchAgent: isTuiAgent(args.launchAgent) ? args.launchAgent : undefined,
|
||||
shellPath: ctx.effectiveShellOverride,
|
||||
isWsl: shouldSkipCodexHomeEnvForWindowsShell(ctx.effectiveShellOverride, ctx.cwd),
|
||||
wslDistro: ctx.codexSelectionTarget.runtime === 'wsl' ? ctx.expectedWslDistro : null,
|
||||
agentStatusHooksEnabled: isAgentStatusHooksEnabled(ptySettings),
|
||||
|
||||
@@ -89,6 +89,7 @@ export function configureLocalPtyProvider(args: {
|
||||
}),
|
||||
launchCommand: ctx?.command,
|
||||
launchAgent: ctx?.launchAgent,
|
||||
shellPath: ctx?.shellPath,
|
||||
isWsl: ctx?.isWsl,
|
||||
wslDistro: ctx?.wslDistro ?? null,
|
||||
agentStatusHooksEnabled: isAgentStatusHooksEnabled(ptySettings),
|
||||
|
||||
@@ -272,6 +272,7 @@ export async function prepareRuntimePtySpawn(
|
||||
stripInheritedOrcaCodexHome: ctx.stripInheritedOrcaCodexHome,
|
||||
launchCommand: ctx.launchCommand,
|
||||
launchAgent: isTuiAgent(args.launchAgent) ? args.launchAgent : undefined,
|
||||
shellPath: ctx.daemonShellOverride,
|
||||
isWsl: shouldSkipCodexHomeEnvForWindowsShell(ctx.daemonShellOverride, ctx.cwd),
|
||||
wslDistro: ctx.codexSelectionTarget.runtime === 'wsl' ? ctx.expectedWslDistro : null,
|
||||
agentStatusHooksEnabled: isAgentStatusHooksEnabled(ptySettings),
|
||||
|
||||
@@ -121,7 +121,11 @@ export class OpenCodeHookService {
|
||||
}
|
||||
}
|
||||
|
||||
buildPtyEnv(ptyId: string, existingConfigDir?: string | undefined): Record<string, string> {
|
||||
buildPtyEnv(
|
||||
ptyId: string,
|
||||
existingConfigDir?: string | undefined,
|
||||
defaultConfigDir?: string
|
||||
): Record<string, string> {
|
||||
if (!isUsableId(ptyId)) {
|
||||
// Why: on a bad id, still preserve a user-set OPENCODE_CONFIG_DIR; only the Orca status plugin is forfeited.
|
||||
return existingConfigDir ? { OPENCODE_CONFIG_DIR: existingConfigDir } : {}
|
||||
@@ -131,7 +135,7 @@ export class OpenCodeHookService {
|
||||
const managedConfigDir = this.getSharedConfigDir()
|
||||
if (!existingConfigDir || existingConfigDir === managedConfigDir) {
|
||||
try {
|
||||
this.writePluginToConfigDir(resolveOpenCodeConfigDirectory())
|
||||
this.writePluginToConfigDir(defaultConfigDir ?? resolveOpenCodeConfigDirectory())
|
||||
return {}
|
||||
} catch {
|
||||
return {}
|
||||
|
||||
@@ -27,6 +27,9 @@ vi.mock('node-pty', () => ({ spawn: mocks.mockPtySpawn }))
|
||||
vi.mock('../main/shell-prompt-readiness-probe', () => ({
|
||||
createShellPromptReadinessProbe: mocks.mockCreateShellPromptReadinessProbe
|
||||
}))
|
||||
vi.mock('../main/opencode/opencode-launch-capabilities', () => ({
|
||||
probeOpenCodeLaunchCapabilities: async () => null
|
||||
}))
|
||||
vi.mock('../main/pty/posix-pty-process-groups', () => ({
|
||||
forceKillPosixPtyProcessGroups: vi.fn((_pid: number, fallback: () => void) => fallback())
|
||||
}))
|
||||
@@ -94,6 +97,38 @@ async function spawn(params: Record<string, unknown> = {}): Promise<Record<strin
|
||||
}
|
||||
|
||||
describe('relay OpenCode source selection on real fixture files', () => {
|
||||
it.each(['home', 'xdg', 'shell'] as const)(
|
||||
'consumer config root follows the remote %s environment',
|
||||
async (kind) => {
|
||||
await install('// remote plugin', '')
|
||||
const home = join(root, 'remote-home')
|
||||
const xdg = join(root, 'remote-xdg')
|
||||
const consumer = kind === 'home' ? join(home, '.config', 'opencode') : join(xdg, 'opencode')
|
||||
mkdirSync(home, { recursive: true })
|
||||
mkdirSync(consumer, { recursive: true })
|
||||
writeFileSync(join(consumer, 'opencode.json'), '{"model":"remote-model"}')
|
||||
if (kind === 'shell') {
|
||||
writeFileSync(join(home, '.zshrc'), `export XDG_CONFIG_HOME='${xdg}'\n`)
|
||||
}
|
||||
const env = await spawn({
|
||||
cwd: home,
|
||||
launchAgent: 'opencode',
|
||||
env: {
|
||||
HOME: home,
|
||||
USERPROFILE: home,
|
||||
XDG_CONFIG_HOME: kind === 'xdg' ? xdg : '',
|
||||
SHELL: kind === 'shell' ? '/bin/zsh' : '/bin/sh'
|
||||
}
|
||||
})
|
||||
expect(readFileSync(plugin(consumer, 'opencode'), 'utf8')).toBe('// remote plugin')
|
||||
expect(existsSync(plugin(join(root, 'xdg', 'opencode'), 'opencode'))).toBe(false)
|
||||
expect(readFileSync(join(consumer, 'opencode.json'), 'utf8')).toBe('{"model":"remote-model"}')
|
||||
expect(env.OPENCODE_CONFIG_DIR).toBeUndefined()
|
||||
expect(env.HOME).toBe(home)
|
||||
expect(env.XDG_CONFIG_HOME).toBe(kind === 'xdg' ? xdg : '')
|
||||
}
|
||||
)
|
||||
|
||||
it('leaves a standalone relay without supplied sources unconfigured', async () => {
|
||||
const env = await spawn()
|
||||
expect(env.ORCA_OPENCODE_AGENT).toBeUndefined()
|
||||
|
||||
@@ -25,6 +25,7 @@ import { selectOpenCodeHookAgent } from '../shared/opencode-launch-command'
|
||||
import { relayLogLine } from './relay-diagnostic-log'
|
||||
import { restoreOrStripOverlayEnv } from '../shared/agent-overlay-env'
|
||||
import { registerManagedHookInstaller } from './managed-hook-installer'
|
||||
import { readSessionShellStartupEnvVar } from '../main/pty/shell-startup-env'
|
||||
|
||||
export class RelayAgentHookRuntime {
|
||||
private readonly hookServer: RelayAgentHookServer
|
||||
@@ -122,7 +123,12 @@ export class RelayAgentHookRuntime {
|
||||
env.ORCA_OPENCODE_SOURCE_CONFIG_DIR = sourceDir
|
||||
}
|
||||
} else {
|
||||
this.pluginOverlay.installOpenCodePlugin(opencodeAgent, context.env)
|
||||
this.pluginOverlay.installOpenCodePlugin(opencodeAgent, {
|
||||
...context.env,
|
||||
XDG_CONFIG_HOME:
|
||||
readSessionShellStartupEnvVar('XDG_CONFIG_HOME', context.env, context.shell) ??
|
||||
context.env.XDG_CONFIG_HOME
|
||||
})
|
||||
}
|
||||
}
|
||||
const explicitKind = isPiCompatibleAgentType(context.launchAgent)
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import { join } from 'node:path'
|
||||
import { resolveOpenCodeConfigDirectory } from './opencode-config-directory'
|
||||
|
||||
const originalPlatform = Object.getOwnPropertyDescriptor(process, 'platform')
|
||||
afterEach(() => {
|
||||
if (originalPlatform) {
|
||||
Object.defineProperty(process, 'platform', originalPlatform)
|
||||
}
|
||||
})
|
||||
|
||||
describe('OpenCode consumer config directory', () => {
|
||||
it('uses the execution HOME instead of the installer home on POSIX', () => {
|
||||
Object.defineProperty(process, 'platform', { value: 'linux' })
|
||||
expect(resolveOpenCodeConfigDirectory({ HOME: '/consumer' }, '/installer')).toBe(
|
||||
join('/consumer', '.config', 'opencode')
|
||||
)
|
||||
})
|
||||
|
||||
it('uses the execution USERPROFILE on Windows', () => {
|
||||
Object.defineProperty(process, 'platform', { value: 'win32' })
|
||||
expect(
|
||||
resolveOpenCodeConfigDirectory({ USERPROFILE: '/consumer', HOME: '/shell' }, '/installer')
|
||||
).toBe(join('/consumer', '.config', 'opencode'))
|
||||
})
|
||||
|
||||
it('keeps the host home fallback when the execution home is absent', () => {
|
||||
expect(resolveOpenCodeConfigDirectory({}, '/installer')).toBe(
|
||||
join('/installer', '.config', 'opencode')
|
||||
)
|
||||
})
|
||||
|
||||
it('does not treat a Windows shell HOME as the native OpenCode home', () => {
|
||||
Object.defineProperty(process, 'platform', { value: 'win32' })
|
||||
expect(resolveOpenCodeConfigDirectory({ HOME: '/shell' }, '/installer')).toBe(
|
||||
join('/installer', '.config', 'opencode')
|
||||
)
|
||||
})
|
||||
|
||||
it('keeps XDG_CONFIG_HOME ahead of the execution home', () => {
|
||||
expect(
|
||||
resolveOpenCodeConfigDirectory({ HOME: '/consumer', XDG_CONFIG_HOME: '/xdg' }, '/installer')
|
||||
).toBe(join('/xdg', 'opencode'))
|
||||
})
|
||||
})
|
||||
@@ -6,5 +6,9 @@ export function resolveOpenCodeConfigDirectory(
|
||||
environment: NodeJS.ProcessEnv | Record<string, string> = process.env,
|
||||
homeDirectory = homedir()
|
||||
): string {
|
||||
return join(environment.XDG_CONFIG_HOME?.trim() || join(homeDirectory, '.config'), 'opencode')
|
||||
const executionHome = process.platform === 'win32' ? environment.USERPROFILE : environment.HOME
|
||||
return join(
|
||||
environment.XDG_CONFIG_HOME?.trim() || join(executionHome || homeDirectory, '.config'),
|
||||
'opencode'
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user