feat(agent-hooks): support Prime Agent status in WSL

This commit is contained in:
Brennan Benson
2026-08-09 18:53:00 -07:00
parent 8a38e2f3f8
commit a6311f24f3
18 changed files with 575 additions and 98 deletions
@@ -233,6 +233,13 @@ describe('WslHookRelayManager', () => {
return { manager: new WslHookRelayManager(deps), deps }
}
it('exposes the stable guest endpoint instance key with a port fallback', () => {
expect(createManager({}).manager.getInstanceKey()).toBe('testinstance')
expect(createManager({ instanceKey: () => 'unsafe/key' }).manager.getInstanceKey()).toBe(
'port43117'
)
})
it('starts one relay per distro, installs hooks, exposes the guest endpoint path, and forwards envelopes', async () => {
const { manager, deps } = createManager({})
manager.ensureForDistro('Ubuntu')
+10 -2
View File
@@ -109,6 +109,15 @@ export class WslHookRelayManager {
return this.stateFor(distro)?.opencodeOverlayDir ?? null
}
getInstanceKey(): string | null {
const stableKey = sanitizeWslHookInstanceKey(this.deps.instanceKey() ?? undefined)
if (stableKey) {
return stableKey
}
const port = Number(this.deps.hookCoordsEnv().ORCA_AGENT_HOOK_PORT ?? '')
return Number.isInteger(port) && port > 0 ? `port${port}` : null
}
/** Kills every live relay. Non-permanent (hooks switched off mid-session) leaves the
* manager reusable, so re-enabling hooks can start relays again without an app restart. */
disposeAll({ permanent = true }: { permanent?: boolean } = {}): void {
@@ -172,8 +181,7 @@ export class WslHookRelayManager {
}
// Why: restart-stable instance identity keeps the guest endpoint file at
// ONE path across restarts so daemon-surviving agents re-coordinate.
const instanceKey =
sanitizeWslHookInstanceKey(this.deps.instanceKey() ?? undefined) ?? `port${port}`
const instanceKey = this.getInstanceKey() ?? `port${port}`
if (existing) {
this.recovery.clearTimers(existing)
}
+5
View File
@@ -604,6 +604,8 @@ describePosix('daemon shell-ready launch config', () => {
'[[ -n "${ORCA_CODEX_HOME:-}" ]] && export CODEX_HOME="${ORCA_CODEX_HOME}"'
const agentTeamsPathRestoreLine = '[[ -n "${ORCA_AGENT_TEAMS_SHIM_DIR:-}" ]] || return 0'
const ompWrapperLine = 'command omp --extension "${ORCA_OMP_STATUS_EXTENSION}" "$@"'
const primeWrapperLine =
'command prime-agent --extension "${ORCA_PRIME_AGENT_STATUS_EXTENSION}" "$@"'
expect(zshrc).toContain(restoreLine)
expect(zlogin).toContain(restoreLine)
expect(bashRc).toContain(restoreLine)
@@ -625,6 +627,9 @@ describePosix('daemon shell-ready launch config', () => {
expect(zshrc).toContain(ompWrapperLine)
expect(zlogin).toContain(ompWrapperLine)
expect(bashRc).toContain(ompWrapperLine)
expect(zshrc).toContain(primeWrapperLine)
expect(zlogin).toContain(primeWrapperLine)
expect(bashRc).toContain(primeWrapperLine)
})
// Why: regression guard for issue #2422 — bash wrapper must emit OSC 133 C/D so SSH sessions clear stale 'working' agent rows.
+4
View File
@@ -11,6 +11,7 @@ import {
isPowerShellExecutableName
} from '../powershell-osc133-bootstrap'
import { getPosixOmpShellWrapper } from '../pty/omp-shell-wrapper'
import { getPosixPrimeAgentShellWrapper } from '../pty/prime-agent-shell-wrapper'
import {
getFishShellReadyInitCommand,
getZshEnvTemplate,
@@ -122,6 +123,7 @@ __orca_restore_agent_teams_path
[[ -n "\${ORCA_OPENCODE_CONFIG_DIR:-}" ]] && export OPENCODE_CONFIG_DIR="\${ORCA_OPENCODE_CONFIG_DIR}"
[[ -n "\${ORCA_MIMOCODE_HOME:-}" ]] && export MIMOCODE_HOME="\${ORCA_MIMOCODE_HOME}"
${getPosixOmpShellWrapper()}
${getPosixPrimeAgentShellWrapper()}
# Why: Codex must keep using Orca's runtime CODEX_HOME after profile scripts.
[[ -n "\${ORCA_CODEX_HOME:-}" ]] && export CODEX_HOME="\${ORCA_CODEX_HOME}"
# Why: emit OSC 133 C/D so terminal-command-lifecycle can drop stale agent
@@ -243,6 +245,7 @@ if [[ ! -o login ]]; then
[[ -n "\${ORCA_OPENCODE_CONFIG_DIR:-}" ]] && export OPENCODE_CONFIG_DIR="\${ORCA_OPENCODE_CONFIG_DIR}"
[[ -n "\${ORCA_MIMOCODE_HOME:-}" ]] && export MIMOCODE_HOME="\${ORCA_MIMOCODE_HOME}"
${getPosixOmpShellWrapper()}
${getPosixPrimeAgentShellWrapper()}
[[ -n "\${ORCA_CODEX_HOME:-}" ]] && export CODEX_HOME="\${ORCA_CODEX_HOME}"
fi
__orca_osc133_precmd() {
@@ -306,6 +309,7 @@ __orca_restore_agent_teams_path
[[ -n "\${ORCA_OPENCODE_CONFIG_DIR:-}" ]] && export OPENCODE_CONFIG_DIR="\${ORCA_OPENCODE_CONFIG_DIR}"
[[ -n "\${ORCA_MIMOCODE_HOME:-}" ]] && export MIMOCODE_HOME="\${ORCA_MIMOCODE_HOME}"
${getPosixOmpShellWrapper()}
${getPosixPrimeAgentShellWrapper()}
[[ -n "\${ORCA_CODEX_HOME:-}" ]] && export CODEX_HOME="\${ORCA_CODEX_HOME}"
${getZshShellReadyMarkerRegistrationBlock(SHELL_READY_MARKER)}
${getZshFinalZdotdirRestoreBlock()}
+35 -1
View File
@@ -55,6 +55,7 @@ const {
clearAgentHookPaneStateMock,
registerPaneKeyAliasMock,
piBuildPtyEnvMock,
piBuildStatusOnlyPtyEnvMock,
piClearPtyMock,
isPwshAvailableMock,
trackMock,
@@ -91,6 +92,7 @@ const {
clearAgentHookPaneStateMock: vi.fn(),
registerPaneKeyAliasMock: vi.fn(),
piBuildPtyEnvMock: vi.fn(),
piBuildStatusOnlyPtyEnvMock: vi.fn(),
piClearPtyMock: vi.fn(),
trackMock: vi.fn(),
classifyErrorMock: vi.fn(),
@@ -175,6 +177,7 @@ vi.mock('../agent-hooks/server', () => ({
vi.mock('../pi/titlebar-extension-service', () => ({
piTitlebarExtensionService: {
buildPtyEnv: piBuildPtyEnvMock,
buildStatusOnlyPtyEnv: piBuildStatusOnlyPtyEnvMock,
clearPty: piClearPtyMock
}
}))
@@ -333,6 +336,7 @@ describe('registerPtyHandlers', () => {
const savedOrcaOmpStatusExtension = process.env.ORCA_OMP_STATUS_EXTENSION
const savedPrimeAgentDir = process.env.PRIME_AGENT_CODING_AGENT_DIR
const savedOrcaPrimeAgentSourceDir = process.env.ORCA_PRIME_AGENT_SOURCE_AGENT_DIR
const savedOrcaPrimeAgentStatusExtension = process.env.ORCA_PRIME_AGENT_STATUS_EXTENSION
const savedOrcaClaudeAgentStatusSettings = process.env.ORCA_CLAUDE_AGENT_STATUS_SETTINGS
const savedProcessPlatform = Object.getOwnPropertyDescriptor(process, 'platform')
const savedDisableMacosLoginShell = process.env.ORCA_DISABLE_MACOS_LOGIN_SHELL
@@ -360,6 +364,7 @@ describe('registerPtyHandlers', () => {
delete process.env.ORCA_OMP_STATUS_EXTENSION
delete process.env.PRIME_AGENT_CODING_AGENT_DIR
delete process.env.ORCA_PRIME_AGENT_SOURCE_AGENT_DIR
delete process.env.ORCA_PRIME_AGENT_STATUS_EXTENSION
handlers.clear()
handleMock.mockReset()
onMock.mockReset()
@@ -382,6 +387,7 @@ describe('registerPtyHandlers', () => {
clearAgentHookPaneStateMock.mockReset()
registerPaneKeyAliasMock.mockReset()
piBuildPtyEnvMock.mockReset()
piBuildStatusOnlyPtyEnvMock.mockReset()
piClearPtyMock.mockReset()
isPwshAvailableMock.mockReset()
trackMock.mockReset()
@@ -481,6 +487,10 @@ describe('registerPtyHandlers', () => {
}
}
)
piBuildStatusOnlyPtyEnvMock.mockReturnValue({
ORCA_PRIME_AGENT_STATUS_EXTENSION:
'C:\\Users\\test\\AppData\\Roaming\\Orca\\prime-agent-managed-status-extension\\orca-agent-status.ts'
})
isPwshAvailableMock.mockReturnValue(false)
spawnMock.mockReturnValue({
onData: vi.fn(() => makeDisposable()),
@@ -586,6 +596,11 @@ describe('registerPtyHandlers', () => {
} else {
delete process.env.ORCA_PRIME_AGENT_SOURCE_AGENT_DIR
}
if (savedOrcaPrimeAgentStatusExtension !== undefined) {
process.env.ORCA_PRIME_AGENT_STATUS_EXTENSION = savedOrcaPrimeAgentStatusExtension
} else {
delete process.env.ORCA_PRIME_AGENT_STATUS_EXTENSION
}
if (savedOrcaClaudeAgentStatusSettings === undefined) {
delete process.env.ORCA_CLAUDE_AGENT_STATUS_SETTINGS
} else {
@@ -4196,7 +4211,7 @@ describe('registerPtyHandlers', () => {
})
})
it('does not materialize Windows-host Prime extensions for a WSL launch', async () => {
it('bridges a host-managed Prime status extension into WSL without redirecting Prime home', async () => {
await withWin32Platform(async () => {
const env = await daemonSpawnAndGetEnv(
{ PRIME_AGENT_CODING_AGENT_DIR: 'C:\\Users\\test\\.prime\\agent' },
@@ -4207,11 +4222,30 @@ describe('registerPtyHandlers', () => {
)
expect(piBuildPtyEnvMock).not.toHaveBeenCalled()
expect(piBuildStatusOnlyPtyEnvMock).toHaveBeenCalledWith('prime-agent')
expect(env.ORCA_PRIME_AGENT_SOURCE_AGENT_DIR).toBeUndefined()
expect(env.ORCA_PRIME_AGENT_STATUS_EXTENSION).toBe(
'C:\\Users\\test\\AppData\\Roaming\\Orca\\prime-agent-managed-status-extension\\orca-agent-status.ts'
)
expect(env.ORCA_WSL_HOOK_INSTANCE).toBe('port5678')
expect(env.PRIME_AGENT_CODING_AGENT_DIR).toBe('C:\\Users\\test\\.prime\\agent')
})
})
it('prepares the Prime bridge for a typed launch in a bare WSL shell', async () => {
await withWin32Platform(async () => {
const env = await daemonSpawnAndGetEnv({}, undefined, undefined, undefined, {
shellOverride: 'wsl.exe'
})
expect(piBuildStatusOnlyPtyEnvMock).toHaveBeenCalledWith('prime-agent')
expect(env.ORCA_PRIME_AGENT_STATUS_EXTENSION).toContain(
'prime-agent-managed-status-extension'
)
expect(env.PRIME_AGENT_CODING_AGENT_DIR).toBeUndefined()
})
})
it('points OPENCODE_CONFIG_DIR at the guest overlay when the WSL relay reports it', async () => {
const guestDir = '/home/jin/.orca-relay/opencode-overlays/abc'
const spy = vi.spyOn(wslHookRelayManager, 'getOpenCodeOverlayDir').mockReturnValue(guestDir)
+23 -7
View File
@@ -1513,6 +1513,7 @@ function clearPiAgentShadowEnv(baseEnv: Record<string, string>, kind: PiAgentKin
}
if (kind === 'prime-agent') {
delete baseEnv.ORCA_PRIME_AGENT_SOURCE_AGENT_DIR
delete baseEnv.ORCA_PRIME_AGENT_STATUS_EXTENSION
return
}
delete baseEnv.ORCA_PI_CODING_AGENT_DIR
@@ -1544,6 +1545,11 @@ function exposePiManagedExtensionEnv(
} else {
delete baseEnv.ORCA_PRIME_AGENT_SOURCE_AGENT_DIR
}
if (managedEnv.ORCA_PRIME_AGENT_STATUS_EXTENSION) {
baseEnv.ORCA_PRIME_AGENT_STATUS_EXTENSION = managedEnv.ORCA_PRIME_AGENT_STATUS_EXTENSION
} else {
delete baseEnv.ORCA_PRIME_AGENT_STATUS_EXTENSION
}
return
}
delete baseEnv.ORCA_PI_CODING_AGENT_DIR
@@ -1686,6 +1692,8 @@ export function buildPtyHostEnv(
})
const shouldPrepareOmpShadow = piAgentKind === 'omp' || !hasLaunchCommand
const shouldPreparePrimeStatus =
piAgentKind === 'prime-agent' || (opts.isWsl && !hasLaunchCommand)
// Why: source shadows are agent-scoped; trusting the other kind's source reintroduces Pi/OMP extension-state shadowing.
const preexistingPiAgentDir = resolvePiAgentSourceDir(baseEnv, 'pi')
const preexistingOmpAgentDir =
@@ -1744,6 +1752,12 @@ export function buildPtyHostEnv(
if (opts.isWsl === true) {
// Why: hook POSTs to 127.0.0.1 die inside WSL's NAT namespace; use the guest-resident relay's endpoint instead of the Windows one.
const distro = opts.wslDistro ?? null
const hookInstance = wslHookRelayManager.getInstanceKey()
if (hookInstance) {
baseEnv.ORCA_WSL_HOOK_INSTANCE = hookInstance
} else {
delete baseEnv.ORCA_WSL_HOOK_INSTANCE
}
wslHookRelayManager.ensureForDistro(distro)
const guestEndpoint = wslHookRelayManager.getGuestEndpointFilePath(distro)
if (guestEndpoint) {
@@ -1790,13 +1804,14 @@ export function buildPtyHostEnv(
exposePiManagedExtensionEnv(baseEnv, 'omp', ompEnv)
}
if (piAgentKind === 'prime-agent' && !opts.isWsl) {
const primeEnv = piTitlebarExtensionService.buildPtyEnv(
id,
preexistingPrimeAgentDir,
'prime-agent',
{ materializeDefaultHome: explicitPiAgentKind === 'prime-agent' }
)
if (shouldPreparePrimeStatus) {
// Why: the Windows host cannot safely resolve or write Prime's guest
// config root; WSL loads the host-managed status file explicitly.
const primeEnv = opts.isWsl
? piTitlebarExtensionService.buildStatusOnlyPtyEnv('prime-agent')
: piTitlebarExtensionService.buildPtyEnv(id, preexistingPrimeAgentDir, 'prime-agent', {
materializeDefaultHome: explicitPiAgentKind === 'prime-agent'
})
Object.assign(baseEnv, primeEnv)
exposePiManagedExtensionEnv(baseEnv, 'prime-agent', primeEnv)
}
@@ -1814,6 +1829,7 @@ export function buildPtyHostEnv(
})
delete baseEnv.ORCA_OMP_STATUS_EXTENSION
delete baseEnv.ORCA_PRIME_AGENT_SOURCE_AGENT_DIR
delete baseEnv.ORCA_PRIME_AGENT_STATUS_EXTENSION
}
// Why: keep the Codex home override PTY-scoped so dev/prod Orcas don't share hooks through ~/.codex.
@@ -30,6 +30,7 @@ type Harness = {
fsMock: {
existsSync: ReturnType<typeof vi.fn>
readFileSync: ReturnType<typeof vi.fn>
statSync: ReturnType<typeof vi.fn>
}
handlers: Record<string, HookHandler>
processEnv: Record<string, string | undefined>
@@ -62,6 +63,7 @@ function createHarness(args: {
argv?: string[]
existsSync?: (path: string) => boolean
readFileSync?: (path: string, encoding: string) => string
statSync?: (path: string) => { mtimeMs: number; size: number; ino: number }
fetchImpl?: (...params: Parameters<typeof fetch>) => Promise<unknown>
}): Harness {
const fetchMock = vi.fn(
@@ -86,6 +88,12 @@ function createHarness(args: {
const fsMock = {
existsSync: vi.fn(args.existsSync ?? (() => false)),
statSync: vi.fn(
args.statSync ??
((path: string) => {
throw Object.assign(new Error(`ENOENT: ${path}`), { code: 'ENOENT' })
})
),
readFileSync: vi.fn(
args.readFileSync ??
((path: string) => {
@@ -562,6 +570,31 @@ describe('getPiAgentStatusExtensionSource', () => {
)
})
it('uses current Windows coordinates when a same-token guest endpoint is stale', async () => {
const endpointPath = '/home/u/.orca-wsl/agent-hooks/instance-test/endpoint.env'
const harness = createHarness({
kind: 'prime-agent',
env: { WSL_DISTRO_NAME: 'Ubuntu', ORCA_AGENT_HOOK_ENDPOINT: endpointPath },
existsSync: (path) => path === '/mnt/c/Windows/System32/curl.exe',
statSync: () => ({ mtimeMs: 1, size: 80, ino: 1 }),
readFileSync: (path) => {
if (path === endpointPath) {
return 'ORCA_AGENT_HOOK_PORT=9999\nORCA_AGENT_HOOK_TOKEN=token-1\n'
}
throw Object.assign(new Error(`ENOENT: ${path}`), { code: 'ENOENT' })
},
fetchImpl: vi.fn(async () => {
throw new Error('stale guest relay')
})
})
await harness.callHook('agent_start')
expect(harness.fetchMock.mock.calls[0]?.[0]).toBe('http://127.0.0.1:9999/hook/prime-agent')
await vi.waitFor(() => expect(harness.spawnMock).toHaveBeenCalledTimes(1))
expect(harness.spawnMock.mock.calls[0]?.[1]).toContain('http://127.0.0.1:4321/hook/prime-agent')
})
it('probes WSL evidence and the curl path once per process', async () => {
const harness = createHarness({
kind: 'omp',
+3 -81
View File
@@ -13,6 +13,7 @@
import type { PiAgentKind } from '../../shared/pi-agent-kind'
import { getPiAgentStatusHandlerSourceLines } from './agent-status-handler-source'
import { getPiAgentStatusRuntimeDetectionSourceLines } from './agent-status-runtime-detection-source'
import { getPiAgentStatusWslCurlSourceLines } from './agent-status-wsl-curl-source'
export const ORCA_PI_AGENT_STATUS_EXTENSION_FILE = 'orca-agent-status.ts'
@@ -228,92 +229,13 @@ export function getPiAgentStatusExtensionSource(kind: PiAgentKind = 'pi'): strin
' // Why: status reporting must never fail the pi run just because Orca',
' // is unavailable or the loopback request failed (e.g. Orca restart).',
' if (!isWslRuntime()) return',
' postViaWindowsCurl(url, coords, body)',
' postViaWindowsCurl(body, ompRuntime)',
' } finally {',
' if (timeout) clearTimeout(timeout)',
' }',
'}',
'',
'// Why: WSL-ness and curl.exe presence cannot change within a process',
'// lifetime; re-probing /proc and /mnt/c on every failed event would add',
'// filesystem work to the per-event path.',
'let cachedIsWslRuntime: boolean | null = null',
'let cachedWindowsCurlPath: string | null | undefined',
'',
'function isWslRuntime(): boolean {',
' if (cachedIsWslRuntime !== null) return cachedIsWslRuntime',
' cachedIsWslRuntime = detectWslRuntime()',
' return cachedIsWslRuntime',
'}',
'',
'function detectWslRuntime(): boolean {',
' if (process.env.WSL_DISTRO_NAME) return true',
' try {',
" const fs = require('fs')",
" for (const path of ['/proc/sys/kernel/osrelease', '/proc/version']) {",
' try {',
" const contents = String(fs.readFileSync(path, 'utf8'))",
' if (/microsoft|wsl/i.test(contents)) return true',
' } catch {',
' // Why: probe the next runtime hint when a proc file is absent or unreadable.',
' }',
' }',
' } catch {',
' return false',
' }',
' return false',
'}',
'',
'function resolveWindowsCurlPath(): string | null {',
' if (cachedWindowsCurlPath !== undefined) return cachedWindowsCurlPath',
' try {',
" const fs = require('fs')",
" const curlPath = '/mnt/c/Windows/System32/curl.exe'",
' cachedWindowsCurlPath = fs.existsSync(curlPath) ? curlPath : null',
' } catch {',
' cachedWindowsCurlPath = null',
' }',
' return cachedWindowsCurlPath',
'}',
'',
'// Why: WSL loopback is not the Windows loopback, so a WSL-side POST cannot',
'// reach Orca. curl.exe runs on the Windows side, where 127.0.0.1 IS the',
'// listener Orca binds. Fire-and-forget: blocking on the spawn would stall',
'// the pi event loop (and the TUI) on every hook event.',
'function postViaWindowsCurl(url: string, coords: { token: string }, body: string): void {',
' const curlPath = resolveWindowsCurlPath()',
' if (!curlPath) return',
' try {',
" const { spawn } = require('child_process')",
' const child = spawn(',
' curlPath,',
' [',
" '-sS',",
' // Why: the spawn is detached from the event loop, so these bounds',
' // size a background process, not TUI latency. WSL->Win32 interop',
' // connects can exceed 0.5s on loaded machines (observed 3/3 drops',
' // to a healthy listener); size for delivery, not snappiness.',
" '--connect-timeout', '3',",
" '--max-time', '10',",
" '--noproxy', '127.0.0.1',",
" '-o', 'NUL',",
" '-X', 'POST',",
" '-H', 'Content-Type: application/json',",
" '-H', `X-Orca-Agent-Hook-Token: ${coords.token}`,",
" '--data-binary', '@-',",
' url',
' ],',
" { stdio: ['pipe', 'ignore', 'ignore'] }",
' )',
" child.on('error', () => {})",
" child.stdin.on('error', () => {})",
' child.stdin.end(body)',
' } catch {',
' // Why: the bridge is best-effort; a failed spawn must not surface',
' // inside the pi TUI.',
' }',
'}',
'',
...getPiAgentStatusWslCurlSourceLines(),
...getPiAgentStatusHandlerSourceLines(kind)
].join('\n')
}
@@ -0,0 +1,81 @@
export function getPiAgentStatusWslCurlSourceLines(): string[] {
return [
'// Why: WSL-ness and curl.exe presence cannot change within a process',
'// lifetime; re-probing /proc and /mnt/c on every failed event would add',
'// filesystem work to the per-event path.',
'let cachedIsWslRuntime: boolean | null = null',
'let cachedWindowsCurlPath: string | null | undefined',
'',
'function isWslRuntime(): boolean {',
' if (cachedIsWslRuntime !== null) return cachedIsWslRuntime',
' cachedIsWslRuntime = detectWslRuntime()',
' return cachedIsWslRuntime',
'}',
'',
'function detectWslRuntime(): boolean {',
' if (process.env.WSL_DISTRO_NAME) return true',
' try {',
" const fs = require('fs')",
" for (const path of ['/proc/sys/kernel/osrelease', '/proc/version']) {",
' try {',
" const contents = String(fs.readFileSync(path, 'utf8'))",
' if (/microsoft|wsl/i.test(contents)) return true',
' } catch {',
' // Why: probe the next runtime hint when a proc file is absent or unreadable.',
' }',
' }',
' } catch {',
' return false',
' }',
' return false',
'}',
'',
'function resolveWindowsCurlPath(): string | null {',
' if (cachedWindowsCurlPath !== undefined) return cachedWindowsCurlPath',
' try {',
" const fs = require('fs')",
" const curlPath = '/mnt/c/Windows/System32/curl.exe'",
' cachedWindowsCurlPath = fs.existsSync(curlPath) ? curlPath : null',
' } catch {',
' cachedWindowsCurlPath = null',
' }',
' return cachedWindowsCurlPath',
'}',
'',
'// Why: WSL loopback is not the Windows loopback, so use curl.exe on the host.',
'function postViaWindowsCurl(body: string, ompRuntime: boolean): void {',
' const curlPath = resolveWindowsCurlPath()',
' const windowsPort = process.env.ORCA_AGENT_HOOK_PORT',
' const windowsToken = process.env.ORCA_AGENT_HOOK_TOKEN',
' if (!curlPath || !windowsPort || !windowsToken) return',
' // Why: a stale guest endpoint must fall back to current host coordinates.',
' const windowsUrl = `http://127.0.0.1:${windowsPort}${resolveHookPath(ompRuntime)}`',
' try {',
" const { spawn } = require('child_process')",
' const child = spawn(',
' curlPath,',
' [',
" '-sS',",
' // Why: detached delivery may take seconds under loaded WSL interop.',
" '--connect-timeout', '3',",
" '--max-time', '10',",
" '--noproxy', '127.0.0.1',",
" '-o', 'NUL',",
" '-X', 'POST',",
" '-H', 'Content-Type: application/json',",
" '-H', `X-Orca-Agent-Hook-Token: ${windowsToken}`,",
" '--data-binary', '@-',",
' windowsUrl',
' ],',
" { stdio: ['pipe', 'ignore', 'ignore'] }",
' )',
" child.on('error', () => {})",
" child.stdin.on('error', () => {})",
' child.stdin.end(body)',
' } catch {',
' // Why: status delivery must not surface inside the agent TUI.',
' }',
'}',
''
]
}
@@ -94,6 +94,10 @@ describe('PiTitlebarExtensionService', () => {
rmSync(join(userDataDir, 'pi-agent-overlays'), { recursive: true, force: true })
rmSync(join(userDataDir, 'omp-agent-overlays'), { recursive: true, force: true })
rmSync(join(userDataDir, 'omp-managed-status-extension'), { recursive: true, force: true })
rmSync(join(userDataDir, 'prime-agent-managed-status-extension'), {
recursive: true,
force: true
})
})
function expectPiHomeIntact(): void {
@@ -181,6 +185,21 @@ describe('PiTitlebarExtensionService', () => {
expectPiHomeIntact()
})
it('writes a Prime status-only extension outside the guest-owned config dir', () => {
const svc = new PiTitlebarExtensionService()
const env = svc.buildStatusOnlyPtyEnv('prime-agent')
const statusPath = join(
userDataDir,
'prime-agent-managed-status-extension',
'orca-agent-status.ts'
)
expect(env).toEqual({ ORCA_PRIME_AGENT_STATUS_EXTENSION: statusPath })
expect(readFileSync(statusPath, 'utf8')).toContain('/hook/prime-agent')
expect(readFileSync(statusPath, 'utf8')).not.toContain("return '/hook/omp'")
expectPiHomeIntact()
})
it('uses the same source dir for multiple PTYs with the same Pi dir', () => {
const svc = new PiTitlebarExtensionService()
const firstEnv = svc.buildPtyEnv('pty-shared-1', piHome, 'pi')
+17 -5
View File
@@ -27,7 +27,10 @@ export const isSafeDescendCandidate = sharedIsSafeDescendCandidate
const PI_AGENT_SUBDIR = 'agent'
const ORCA_MANAGED_EXTENSION_MARKER = '@orca-managed-pi-extension'
const OMP_MANAGED_STATUS_EXTENSION_DIR = 'omp-managed-status-extension'
const MANAGED_STATUS_EXTENSION_DIR: Record<'omp' | 'prime-agent', string> = {
omp: 'omp-managed-status-extension',
'prime-agent': 'prime-agent-managed-status-extension'
}
type ManagedExtensionWriteResult = 'written' | 'skipped-user-owned' | 'failed'
@@ -121,8 +124,11 @@ export class PiTitlebarExtensionService {
}
}
private writeOmpFallbackStatusExtension(source: string): string | undefined {
const fallbackDir = join(app.getPath('userData'), OMP_MANAGED_STATUS_EXTENSION_DIR)
private writeFallbackStatusExtension(
source: string,
kind: keyof typeof MANAGED_STATUS_EXTENSION_DIR
): string | undefined {
const fallbackDir = join(app.getPath('userData'), MANAGED_STATUS_EXTENSION_DIR[kind])
try {
mkdirSync(fallbackDir, { recursive: true })
} catch {
@@ -165,7 +171,7 @@ export class PiTitlebarExtensionService {
statusResult === 'written'
? statusExtensionPath
: kind === 'omp'
? this.writeOmpFallbackStatusExtension(statusSource)
? this.writeFallbackStatusExtension(statusSource, 'omp')
: undefined
}
}
@@ -195,7 +201,7 @@ export class PiTitlebarExtensionService {
if (!existsSync(sourceAgentDir) && !materializeDefaultHome) {
if (kind === 'omp') {
const statusSource = withOrcaManagedExtensionMarker(getPiAgentStatusExtensionSource(kind))
const statusExtensionPath = this.writeOmpFallbackStatusExtension(statusSource)
const statusExtensionPath = this.writeFallbackStatusExtension(statusSource, 'omp')
return statusExtensionPath ? { ORCA_OMP_STATUS_EXTENSION: statusExtensionPath } : {}
}
return {}
@@ -220,6 +226,12 @@ export class PiTitlebarExtensionService {
return env
}
buildStatusOnlyPtyEnv(kind: 'prime-agent'): Record<string, string> {
const source = withOrcaManagedExtensionMarker(getPiAgentStatusExtensionSource(kind))
const statusExtensionPath = this.writeFallbackStatusExtension(source, kind)
return statusExtensionPath ? { ORCA_PRIME_AGENT_STATUS_EXTENSION: statusExtensionPath } : {}
}
clearPty(ptyId: string): void {
// Why: PTY teardown doesn't know which kind was launched (the daemon
// exit path discards the launch command). Sweep both old PTY-scoped
@@ -526,6 +526,8 @@ describePosix('local PTY shell-ready launch config', () => {
'[[ -n "${ORCA_CODEX_HOME:-}" ]] && export CODEX_HOME="${ORCA_CODEX_HOME}"'
const agentTeamsPathRestoreLine = '[[ -n "${ORCA_AGENT_TEAMS_SHIM_DIR:-}" ]] || return 0'
const ompWrapperLine = 'command omp --extension "${ORCA_OMP_STATUS_EXTENSION}" "$@"'
const primeWrapperLine =
'command prime-agent --extension "${ORCA_PRIME_AGENT_STATUS_EXTENSION}" "$@"'
expect(zshrc).toContain(restoreLine)
expect(zlogin).toContain(restoreLine)
expect(bashRc).toContain(restoreLine)
@@ -547,6 +549,9 @@ describePosix('local PTY shell-ready launch config', () => {
expect(zshrc).toContain(ompWrapperLine)
expect(zlogin).toContain(ompWrapperLine)
expect(bashRc).toContain(ompWrapperLine)
expect(zshrc).toContain(primeWrapperLine)
expect(zlogin).toContain(primeWrapperLine)
expect(bashRc).toContain(primeWrapperLine)
})
// Why: issue #2422 — without OSC 133 C/D markers, bash sessions kept the worktree spinner "working" ~30min after the agent exited.
@@ -15,6 +15,7 @@ import {
isPowerShellExecutableName
} from '../powershell-osc133-bootstrap'
import { getPosixOmpShellWrapper } from '../pty/omp-shell-wrapper'
import { getPosixPrimeAgentShellWrapper } from '../pty/prime-agent-shell-wrapper'
import { buildStartupCommandSubmission } from '../../shared/startup-command-submission'
import {
getFishShellReadyInitCommand,
@@ -129,6 +130,7 @@ __orca_restore_agent_teams_path
[[ -n "\${ORCA_OPENCODE_CONFIG_DIR:-}" ]] && export OPENCODE_CONFIG_DIR="\${ORCA_OPENCODE_CONFIG_DIR}"
[[ -n "\${ORCA_MIMOCODE_HOME:-}" ]] && export MIMOCODE_HOME="\${ORCA_MIMOCODE_HOME}"
${getPosixOmpShellWrapper()}
${getPosixPrimeAgentShellWrapper()}
# Why: Codex must keep using Orca's runtime CODEX_HOME after profile scripts.
[[ -n "\${ORCA_CODEX_HOME:-}" ]] && export CODEX_HOME="\${ORCA_CODEX_HOME}"
# Why: emit OSC 133 C/D so terminal-command-lifecycle can drop stale agent
@@ -245,6 +247,7 @@ if [[ ! -o login ]]; then
[[ -n "\${ORCA_OPENCODE_CONFIG_DIR:-}" ]] && export OPENCODE_CONFIG_DIR="\${ORCA_OPENCODE_CONFIG_DIR}"
[[ -n "\${ORCA_MIMOCODE_HOME:-}" ]] && export MIMOCODE_HOME="\${ORCA_MIMOCODE_HOME}"
${getPosixOmpShellWrapper()}
${getPosixPrimeAgentShellWrapper()}
# Why: Codex must keep using Orca's runtime CODEX_HOME after rc files.
[[ -n "\${ORCA_CODEX_HOME:-}" ]] && export CODEX_HOME="\${ORCA_CODEX_HOME}"
fi
@@ -305,6 +308,7 @@ __orca_restore_agent_teams_path
[[ -n "\${ORCA_OPENCODE_CONFIG_DIR:-}" ]] && export OPENCODE_CONFIG_DIR="\${ORCA_OPENCODE_CONFIG_DIR}"
[[ -n "\${ORCA_MIMOCODE_HOME:-}" ]] && export MIMOCODE_HOME="\${ORCA_MIMOCODE_HOME}"
${getPosixOmpShellWrapper()}
${getPosixPrimeAgentShellWrapper()}
[[ -n "\${ORCA_CODEX_HOME:-}" ]] && export CODEX_HOME="\${ORCA_CODEX_HOME}"
${getZshShellReadyMarkerRegistrationBlock(SHELL_READY_MARKER_ESCAPED)}
${getZshFinalZdotdirRestoreBlock()}
@@ -280,13 +280,16 @@ describe('resolveWindowsShellLaunchArgs', () => {
expect(existsSync(join(userDataPath, 'shell-ready', 'bash', 'rcfile'))).toBe(true)
expect(existsSync(join(userDataPath, 'shell-ready', 'zsh', '.zshenv'))).toBe(true)
// Why: the point of materializing wrappers for WSL is that a typed `omp`
// picks up Orca's status extension; pin that shim end to end.
// Why: typed agents need the host-managed status extension in WSL.
const bashRcfile = readFileSync(join(userDataPath, 'shell-ready', 'bash', 'rcfile'), 'utf8')
const zshLogin = readFileSync(join(userDataPath, 'shell-ready', 'zsh', '.zlogin'), 'utf8')
for (const wrapperFile of [bashRcfile, zshLogin]) {
expect(wrapperFile).toContain('command omp --extension "${ORCA_OMP_STATUS_EXTENSION}" "$@"')
expect(wrapperFile).toContain('omp() { __orca_omp "$@"; }')
expect(wrapperFile).toContain(
'command prime-agent --extension "${ORCA_PRIME_AGENT_STATUS_EXTENSION}" "$@"'
)
expect(wrapperFile).toContain('prime-agent() { __orca_prime_agent "$@"; }')
}
})
@@ -0,0 +1,244 @@
import { spawnSync } from 'node:child_process'
import { chmodSync, mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { dirname, join } from 'node:path'
import { afterEach, describe, expect, it } from 'vitest'
import { getPosixPrimeAgentShellWrapper } from './prime-agent-shell-wrapper'
const describePosix = process.platform === 'win32' ? describe.skip : describe
const hasBash = process.platform !== 'win32' && spawnSync('bash', ['--version']).status === 0
const itWithBash = hasBash ? it : it.skip
const tempDirs: string[] = []
function makeFixture(): {
root: string
extensionPath: string
capturePath: string
endpointPath: string
primeHome: string
rcfile: string
} {
const root = mkdtempSync(join(tmpdir(), 'orca-prime-wrapper-'))
tempDirs.push(root)
const binDir = join(root, 'bin')
const primeHome = join(root, 'guest-prime-home')
const extensionPath = join(root, 'orca-agent-status.ts')
const capturePath = join(root, 'capture')
const endpointPath = join(
root,
'.orca-wsl',
'agent-hooks',
'instance-testinstance',
'endpoint.env'
)
const rcfile = join(root, 'rcfile')
mkdirSync(binDir)
mkdirSync(primeHome)
mkdirSync(dirname(endpointPath), { recursive: true })
writeFileSync(extensionPath, 'export default {}')
writeFileSync(endpointPath, 'ORCA_AGENT_HOOK_PORT=4567\nORCA_AGENT_HOOK_TOKEN=current-token\n')
writeFileSync(rcfile, getPosixPrimeAgentShellWrapper())
writeFileSync(
join(binDir, 'prime-agent'),
`#!/bin/sh
{
printf 'HOME=%s\n' "$PRIME_AGENT_CODING_AGENT_DIR"
printf 'ENDPOINT=%s\n' "$ORCA_AGENT_HOOK_ENDPOINT"
i=0
for arg in "$@"; do
i=$((i + 1))
printf 'ARG%s=%s\n' "$i" "$arg"
done
} > "$ORCA_CAPTURE_FILE"
if [ "\${1:-}" = "config" ]; then
printf 'preserved\n' > "$PRIME_AGENT_CODING_AGENT_DIR/config.yml"
fi
`,
{ mode: 0o755 }
)
chmodSync(join(binDir, 'prime-agent'), 0o755)
return { root, extensionPath, capturePath, endpointPath, primeHome, rcfile }
}
function runPrime(
fixture: ReturnType<typeof makeFixture>,
command: string,
extensionPath = fixture.extensionPath
): string {
const result = spawnSync('bash', ['--noprofile', '--rcfile', fixture.rcfile, '-ic', command], {
cwd: fixture.root,
env: {
...process.env,
HOME: fixture.root,
PATH: `${join(fixture.root, 'bin')}:${process.env.PATH ?? ''}`,
PRIME_AGENT_CODING_AGENT_DIR: fixture.primeHome,
ORCA_PRIME_AGENT_STATUS_EXTENSION: extensionPath,
ORCA_WSL_HOOK_INSTANCE: 'testinstance',
ORCA_AGENT_HOOK_ENDPOINT: '/mnt/c/current/endpoint.cmd',
ORCA_AGENT_HOOK_PORT: '4567',
ORCA_AGENT_HOOK_TOKEN: 'current-token',
ORCA_CAPTURE_FILE: fixture.capturePath
},
encoding: 'utf8'
})
expect(result.status, result.stderr).toBe(0)
return readFileSync(fixture.capturePath, 'utf8')
}
describePosix('Prime Agent shell wrapper', () => {
afterEach(() => {
for (const dir of tempDirs.splice(0)) {
rmSync(dir, { recursive: true, force: true })
}
})
itWithBash('injects the managed extension into an interactive launch', () => {
const fixture = makeFixture()
const capture = runPrime(fixture, 'prime-agent ask')
expect(capture).toContain(`HOME=${fixture.primeHome}`)
expect(capture).toContain(`ENDPOINT=${fixture.endpointPath}`)
expect(capture).toContain('ARG1=--extension')
expect(capture).toContain(`ARG2=${fixture.extensionPath}`)
expect(capture).toContain('ARG3=ask')
})
itWithBash('preserves config subcommands and the guest Prime home', () => {
const fixture = makeFixture()
const capture = runPrime(fixture, 'prime-agent config')
expect(capture).toContain(`HOME=${fixture.primeHome}`)
expect(capture).toContain('ARG1=config')
expect(capture).not.toContain('ARG1=--extension')
expect(readFileSync(join(fixture.primeHome, 'config.yml'), 'utf8')).toBe('preserved\n')
})
itWithBash.each([
'agents',
'doctor',
'help',
'list',
'model',
'package',
'rename',
'schedule',
'send',
'session',
'shutdown',
'status',
'stop',
'update'
])('preserves the %s subcommand argv', (subcommand) => {
const fixture = makeFixture()
const capture = runPrime(fixture, `prime-agent ${subcommand}`)
expect(capture).toContain(`ARG1=${subcommand}`)
expect(capture).not.toContain('ARG1=--extension')
})
itWithBash.each(['stop', 'rename'])(
'preserves --daemon-socket before the %s management command',
(subcommand) => {
const fixture = makeFixture()
const capture = runPrime(
fixture,
`prime-agent --daemon-socket /tmp/prime.sock ${subcommand} agent-123`
)
expect(capture).toContain('ARG1=--daemon-socket')
expect(capture).toContain('ARG2=/tmp/prime.sock')
expect(capture).toContain(`ARG3=${subcommand}`)
expect(capture).not.toContain(fixture.extensionPath)
}
)
itWithBash('injects the extension after the attach agent operand', () => {
const fixture = makeFixture()
const capture = runPrime(fixture, 'prime-agent attach agent-123 --follow')
expect(capture).toContain('ARG1=attach')
expect(capture).toContain('ARG2=agent-123')
expect(capture).toContain('ARG3=--extension')
expect(capture).toContain(`ARG4=${fixture.extensionPath}`)
expect(capture).toContain('ARG5=--follow')
})
itWithBash('preserves attach help without inventing an agent operand', () => {
const fixture = makeFixture()
const capture = runPrime(fixture, 'prime-agent attach --help')
expect(capture).toContain('ARG1=attach')
expect(capture).toContain('ARG2=--help')
expect(capture).not.toContain(fixture.extensionPath)
})
itWithBash('does not override an explicit user extension', () => {
const fixture = makeFixture()
const capture = runPrime(fixture, 'prime-agent --extension /tmp/user-extension.ts ask')
expect(capture).toContain('ARG1=--extension')
expect(capture).toContain('ARG2=/tmp/user-extension.ts')
expect(capture).not.toContain(fixture.extensionPath)
})
itWithBash('does not add a second extension to attach', () => {
const fixture = makeFixture()
const capture = runPrime(
fixture,
'prime-agent attach agent-123 --extension /tmp/user-extension.ts'
)
expect(capture).toContain('ARG1=attach')
expect(capture).toContain('ARG2=agent-123')
expect(capture).toContain('ARG3=--extension')
expect(capture).toContain('ARG4=/tmp/user-extension.ts')
expect(capture).not.toContain(fixture.extensionPath)
})
itWithBash('degrades to the original argv when the managed extension is unavailable', () => {
const fixture = makeFixture()
const capture = runPrime(fixture, 'prime-agent ask', join(fixture.root, 'missing.ts'))
expect(capture).toContain('ARG1=ask')
expect(capture).not.toContain('ARG1=--extension')
})
itWithBash('keeps the stable guest endpoint path while its relay refreshes stale data', () => {
const fixture = makeFixture()
writeFileSync(fixture.endpointPath, 'ORCA_AGENT_HOOK_PORT=4567\nORCA_AGENT_HOOK_TOKEN=stale\n')
const capture = runPrime(fixture, 'prime-agent ask')
expect(capture).toContain(`ENDPOINT=${fixture.endpointPath}`)
expect(capture).toContain('ARG1=--extension')
})
itWithBash('uses the guest endpoint path when the relay binds a fallback port', () => {
const fixture = makeFixture()
writeFileSync(
fixture.endpointPath,
'ORCA_AGENT_HOOK_PORT=9876\nORCA_AGENT_HOOK_TOKEN=current-token\n'
)
const capture = runPrime(fixture, 'prime-agent ask')
expect(capture).toContain(`ENDPOINT=${fixture.endpointPath}`)
expect(capture).toContain('ARG1=--extension')
})
itWithBash('does not define wrapper helpers when the bridge env is absent', () => {
const fixture = makeFixture()
const result = spawnSync(
'bash',
['--noprofile', '--rcfile', fixture.rcfile, '-ic', 'type -t __orca_prime_agent'],
{
cwd: fixture.root,
env: { ...process.env, ORCA_PRIME_AGENT_STATUS_EXTENSION: '' },
encoding: 'utf8'
}
)
expect(result.status).not.toBe(0)
expect(result.stdout.trim()).toBe('')
})
})
+74
View File
@@ -0,0 +1,74 @@
import { wslHookRelayEndpointFilePath } from '../../shared/wsl-hook-relay-contract'
const PRIME_AGENT_SUBCOMMANDS = [
'agents',
'config',
'doctor',
'help',
'list',
'model',
'package',
'rename',
'schedule',
'send',
'session',
'shutdown',
'status',
'stop',
'update'
] as const
export function getPosixPrimeAgentShellWrapper(): string {
const subcommands = PRIME_AGENT_SUBCOMMANDS.join('|')
const guestEndpointPath = wslHookRelayEndpointFilePath('${HOME%/}', '${ORCA_WSL_HOOK_INSTANCE}')
return `# Why: WSL cannot install into Prime's guest-owned config root from the
# Windows host, so pass Orca's status extension only to interactive launches.
if [[ -n "\${ORCA_PRIME_AGENT_STATUS_EXTENSION:-}" ]]; then
__orca_prime_agent_should_skip_extension() {
if [[ "\${1:-}" == "--daemon-socket" && ( "\${3:-}" == "stop" || "\${3:-}" == "rename" ) ]]; then
return 0
fi
if [[ "\${1:-}" == --daemon-socket=* && ( "\${2:-}" == "stop" || "\${2:-}" == "rename" ) ]]; then
return 0
fi
case "\${1:-}" in
help|--help|-h|--version|-v|--extension|--extension=*) return 0 ;;
${subcommands}) return 0 ;;
esac
return 1
}
__orca_prime_agent_has_explicit_extension() {
local __orca_arg
for __orca_arg in "$@"; do
case "$__orca_arg" in
--extension|--extension=*) return 0 ;;
esac
done
return 1
}
__orca_prime_agent() {
if [[ "\${1:-}" == "attach" && ( -z "\${2:-}" || "\${2:-}" == -* ) ]]; then
command prime-agent "$@"
return
fi
if ! __orca_prime_agent_should_skip_extension "$@" && ! __orca_prime_agent_has_explicit_extension "$@" && [[ -f "\${ORCA_PRIME_AGENT_STATUS_EXTENSION}" ]]; then
local __orca_guest_endpoint="${guestEndpointPath}"
if [[ -n "\${HOME:-}" && -n "\${ORCA_WSL_HOOK_INSTANCE:-}" ]]; then
local ORCA_AGENT_HOOK_ENDPOINT="$__orca_guest_endpoint"
export ORCA_AGENT_HOOK_ENDPOINT
fi
if [[ "\${1:-}" == "attach" && -n "\${2:-}" && "\${2:-}" != -* ]]; then
local __orca_attach_agent="$2"
shift 2
command prime-agent attach "$__orca_attach_agent" --extension "\${ORCA_PRIME_AGENT_STATUS_EXTENSION}" "$@"
else
command prime-agent --extension "\${ORCA_PRIME_AGENT_STATUS_EXTENSION}" "$@"
fi
else
command prime-agent "$@"
fi
}
prime-agent() { __orca_prime_agent "$@"; }
fi
`
}
+5
View File
@@ -48,6 +48,8 @@ describe('addOrcaWslInteropEnv', () => {
ORCA_USER_DATA_PATH: 'C:\\Users\\jin\\AppData\\Roaming\\Orca',
ORCA_CLI_COMMAND: 'orca-ide',
ORCA_OMP_STATUS_EXTENSION: 'C:\\Users\\jin\\.omp\\agent\\extensions\\orca-agent-status.ts',
ORCA_PRIME_AGENT_STATUS_EXTENSION:
'C:\\Users\\jin\\AppData\\Roaming\\Orca\\prime-agent-managed-status-extension\\orca-agent-status.ts',
ORCA_PANE_KEY: 'tab-1:leaf-1',
ORCA_TAB_ID: 'tab-1',
ORCA_WORKTREE_ID: 'repo::\\\\wsl.localhost\\Ubuntu\\home\\jin\\repo',
@@ -56,6 +58,7 @@ describe('addOrcaWslInteropEnv', () => {
ORCA_AGENT_HOOK_TOKEN: 'token',
ORCA_AGENT_HOOK_ENV: 'dev',
ORCA_AGENT_HOOK_VERSION: '1',
ORCA_WSL_HOOK_INSTANCE: 'testinstance',
ORCA_ORCHESTRATION_COMPATIBILITY_HOST_KIND: 'wsl',
ORCA_ORCHESTRATION_COMPATIBILITY_HOST_ID: 'local',
ORCA_ORCHESTRATION_COMPATIBILITY_HOST_INCARNATION: 'Ubuntu'
@@ -67,6 +70,7 @@ describe('addOrcaWslInteropEnv', () => {
expect(env.WSLENV).toContain('ORCA_USER_DATA_PATH/p')
expect(env.WSLENV).toContain('ORCA_CLI_COMMAND/u')
expect(env.WSLENV).toContain('ORCA_OMP_STATUS_EXTENSION/p')
expect(env.WSLENV).toContain('ORCA_PRIME_AGENT_STATUS_EXTENSION/p')
expect(env.WSLENV).toContain('ORCA_PANE_KEY/u')
expect(env.WSLENV).toContain('ORCA_TAB_ID/u')
expect(env.WSLENV).toContain('ORCA_WORKTREE_ID/u')
@@ -75,6 +79,7 @@ describe('addOrcaWslInteropEnv', () => {
expect(env.WSLENV).toContain('ORCA_AGENT_HOOK_TOKEN/u')
expect(env.WSLENV).toContain('ORCA_AGENT_HOOK_ENV/u')
expect(env.WSLENV).toContain('ORCA_AGENT_HOOK_VERSION/u')
expect(env.WSLENV).toContain('ORCA_WSL_HOOK_INSTANCE/u')
expect(env.WSLENV).toContain('ORCA_ORCHESTRATION_COMPATIBILITY_HOST_KIND/u')
expect(env.WSLENV).toContain('ORCA_ORCHESTRATION_COMPATIBILITY_HOST_ID/u')
expect(env.WSLENV).toContain('ORCA_ORCHESTRATION_COMPATIBILITY_HOST_INCARNATION/u')
+1
View File
@@ -96,6 +96,7 @@ export function addOrcaWslInteropEnv(env: Record<string, string>): void {
'ORCA_WSL_HOOK_INSTANCE/u',
'ORCA_OMP_SOURCE_AGENT_DIR/p',
'ORCA_OMP_STATUS_EXTENSION/p',
'ORCA_PRIME_AGENT_STATUS_EXTENSION/p',
...worktreeSetupWslenvEntries(env)
]
applyWslenvPassthrough(env, passthroughEntries)