mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 08:02:28 +00:00
fix(agent-hooks): don't mirror the XDG default OpenCode config into the WSL overlay
OPENCODE_CONFIG_DIR is APPENDED to OpenCode's config-dir list, not a replacement for it. Verified against the shipped binary: the list is built as [Path.config, ...project .opencode dirs, ...OPENCODE_CONFIG_DIR ? [it] : []], and Path.config is derived independently from XDG_CONFIG_HOME/$HOME/.config. So ~/.config/opencode is read whether or not Orca overrides the var, and the earlier fallback that mirrored it into the overlay made OpenCode load the user's config -- and their plugins -- twice. Resolve only an explicitly-set dir, which is the one case that genuinely leaves the list when Orca overwrites the variable. This also restores parity with the SSH and local paths.
This commit is contained in:
@@ -63,20 +63,20 @@ describe.skipIf(process.platform === 'win32')('createInstallPluginsHandler (gues
|
||||
})
|
||||
})
|
||||
|
||||
it('rebuilds when a config root appears after the first install', () => {
|
||||
it('rebuilds when an explicitly-set config root appears after the first install', () => {
|
||||
withHome((home) => {
|
||||
const install = createInstallPluginsHandler(new PluginOverlayManager({ homeDir: home }), {
|
||||
HOME: home,
|
||||
ORCA_WSL_HOOK_INSTANCE: 'inst1'
|
||||
} as NodeJS.ProcessEnv)
|
||||
const userConfig = join(home, 'my-opencode')
|
||||
const env = { HOME: home, ORCA_WSL_HOOK_INSTANCE: 'inst1' } as NodeJS.ProcessEnv
|
||||
const install = createInstallPluginsHandler(new PluginOverlayManager({ homeDir: home }), env)
|
||||
const source = '// v1\n'
|
||||
install({ opencodePluginSource: source })
|
||||
|
||||
// Why: the user runs opencode outside Orca (or creates the dir by hand) after the
|
||||
// relay connected; keying the cache on the plugin source alone would never mirror it.
|
||||
const userConfig = join(home, '.config', 'opencode')
|
||||
// Why: the source dir is resolved per call, so a dir that only becomes
|
||||
// resolvable later still gets mirrored; keying the cache on the plugin
|
||||
// source alone would leave the overlay plugin-only for the relay's life.
|
||||
mkdirSync(userConfig, { recursive: true })
|
||||
writeFileSync(join(userConfig, 'opencode.json'), '{"model":"late"}')
|
||||
env.ORCA_OPENCODE_SOURCE_CONFIG_DIR = userConfig
|
||||
|
||||
const dir = install({ opencodePluginSource: source }).overlayDirs.opencode as string
|
||||
expect(readFileSync(join(dir, 'opencode.json'), 'utf8')).toBe('{"model":"late"}')
|
||||
@@ -128,17 +128,17 @@ describe.skipIf(process.platform === 'win32')('createInstallPluginsHandler (gues
|
||||
})
|
||||
})
|
||||
|
||||
it("mirrors the guest's default config root so it keeps applying in WSL panes", () => {
|
||||
it('mirrors an explicitly-set config root so overriding the var does not drop it', () => {
|
||||
withHome((home) => {
|
||||
// Why: nothing explicit is discoverable from the relay's own env, so without
|
||||
// this default the overlay would hold only Orca's plugin and the user's real
|
||||
// OpenCode config would silently stop applying once the pane points at it.
|
||||
const userConfig = join(home, '.config', 'opencode')
|
||||
// Why: setting OPENCODE_CONFIG_DIR to the overlay removes the user's own value
|
||||
// from OpenCode's config-dir list, so that one must be mirrored in.
|
||||
const userConfig = join(home, 'my-opencode')
|
||||
mkdirSync(userConfig, { recursive: true })
|
||||
writeFileSync(join(userConfig, 'opencode.json'), '{"model":"user-set"}')
|
||||
|
||||
const install = createInstallPluginsHandler(new PluginOverlayManager({ homeDir: home }), {
|
||||
HOME: home,
|
||||
ORCA_OPENCODE_SOURCE_CONFIG_DIR: userConfig,
|
||||
ORCA_WSL_HOOK_INSTANCE: 'inst1'
|
||||
} as NodeJS.ProcessEnv)
|
||||
const dir = install({ opencodePluginSource: '// v1\n' }).overlayDirs.opencode as string
|
||||
@@ -148,21 +148,23 @@ describe.skipIf(process.platform === 'win32')('createInstallPluginsHandler (gues
|
||||
})
|
||||
})
|
||||
|
||||
it('honours XDG_CONFIG_HOME for the default config root', () => {
|
||||
it('does not mirror the XDG default config root', () => {
|
||||
withHome((home) => {
|
||||
const xdg = join(home, 'xdg')
|
||||
const userConfig = join(xdg, 'opencode')
|
||||
mkdirSync(userConfig, { recursive: true })
|
||||
writeFileSync(join(userConfig, 'opencode.json'), '{"model":"xdg"}')
|
||||
// Why: OpenCode APPENDS OPENCODE_CONFIG_DIR to its config-dir list rather than
|
||||
// replacing it, so ~/.config/opencode is read anyway — mirroring it here would
|
||||
// load the user's config and plugins twice.
|
||||
const defaultConfig = join(home, '.config', 'opencode')
|
||||
mkdirSync(defaultConfig, { recursive: true })
|
||||
writeFileSync(join(defaultConfig, 'opencode.json'), '{"model":"default"}')
|
||||
|
||||
const install = createInstallPluginsHandler(new PluginOverlayManager({ homeDir: home }), {
|
||||
HOME: home,
|
||||
XDG_CONFIG_HOME: xdg,
|
||||
ORCA_WSL_HOOK_INSTANCE: 'inst1'
|
||||
} as NodeJS.ProcessEnv)
|
||||
const dir = install({ opencodePluginSource: '// v1\n' }).overlayDirs.opencode as string
|
||||
|
||||
expect(readFileSync(join(dir, 'opencode.json'), 'utf8')).toBe('{"model":"xdg"}')
|
||||
expect(existsSync(join(dir, 'opencode.json'))).toBe(false)
|
||||
expect(existsSync(join(dir, 'plugins', 'orca-opencode-status.js'))).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
// unit-testable without binding the hook server. Scope is OpenCode only for
|
||||
// now; the payload/response shape matches the SSH relay so Pi/OMP are additive.
|
||||
import { existsSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
|
||||
import { getRelayOpenCodePluginPath, type PluginOverlayManager } from './plugin-overlay'
|
||||
import { resolveOpenCodeSourceConfigDir } from './plugin-overlay-env'
|
||||
@@ -21,23 +20,11 @@ export type InstallPluginsResult = {
|
||||
|
||||
export type InstallPluginsHandler = (params: Record<string, unknown>) => InstallPluginsResult
|
||||
|
||||
function resolveGuestOpenCodeConfigDir(env: NodeJS.ProcessEnv): string | undefined {
|
||||
const explicit = resolveOpenCodeSourceConfigDir(env as Record<string, string>, env.SHELL)
|
||||
if (explicit) {
|
||||
return explicit
|
||||
}
|
||||
// Why: nothing explicit is normally discoverable from the relay's own env, and
|
||||
// without a source dir the overlay holds ONLY Orca's plugin — so pointing
|
||||
// OPENCODE_CONFIG_DIR at it would stop the guest user's real ~/.config/opencode
|
||||
// (models, agents, skills, mcp) from applying in WSL panes, where it applied before.
|
||||
const home = env.HOME
|
||||
if (!home) {
|
||||
return undefined
|
||||
}
|
||||
const defaultDir = join(env.XDG_CONFIG_HOME || join(home, '.config'), 'opencode')
|
||||
return existsSync(defaultDir) ? defaultDir : undefined
|
||||
}
|
||||
|
||||
// Why NOT to fall back to ~/.config/opencode here: OpenCode APPENDS
|
||||
// OPENCODE_CONFIG_DIR to its config-dir list, it does not replace it — the
|
||||
// XDG default is always read too. Mirroring the default into the overlay would
|
||||
// load the user's config (and plugins) twice. Only an explicitly-set dir is
|
||||
// mirrored, because that one leaves the list when we override the var.
|
||||
export function createInstallPluginsHandler(
|
||||
pluginOverlay: PluginOverlayManager,
|
||||
env: NodeJS.ProcessEnv
|
||||
@@ -69,7 +56,7 @@ export function createInstallPluginsHandler(
|
||||
const incoming = typeof opencode === 'string' ? opencode : null
|
||||
// Re-resolved every call so a config root created after the first install still
|
||||
// gets mirrored; the rc scan is memoized and the default branch is one existsSync.
|
||||
const sourceDir = resolveGuestOpenCodeConfigDir(env)
|
||||
const sourceDir = resolveOpenCodeSourceConfigDir(env as Record<string, string>, env.SHELL)
|
||||
const cached = materialized
|
||||
if (
|
||||
cached &&
|
||||
|
||||
Reference in New Issue
Block a user