mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
fix(windows): codex and claude get a prompt's double quotes intact in PowerShell (#30424)
* fix(windows): codex and claude get a prompt's double quotes in Windows PowerShell Windows PowerShell 5.1 (and pwsh before 7.3) does not escape an embedded `"` when it builds a native program's command line, so every quote in a launch prompt was dropped, and quotes could split the prompt into extra arguments. Orca's PowerShell codex and claude wrappers now escape their arguments at runtime for the PowerShell that is actually running, through one shared __OrcaNativeArgs function. pwsh 7.3+ already escapes, so it gets the text untouched. * fix(windows): escape agent args for .cmd shims under pwsh 7.3+ too pwsh 7.3+ defaults to the Windows argument mode, which still passes .cmd/.bat targets the Legacy way, so a codex or claude .cmd shim kept dropping quotes there. The function now takes the program it escapes for and names the modes that escape by themselves, and anchors the 5.1 trailing-backslash fix at the true end of the text (`$` also matched before a final newline). Tests now run the real codex wrapper with a quoted prompt, cover pwsh's Legacy mode and the newline case, and pin the claude wrapper's call. * fix(windows): strip comment lines from the PowerShell bootstrap payload The shared escaping function pushed the encoded bootstrap to 27,324 of the 28,000-character budget, leaving almost no room for a startup command to ride in -EncodedCommand, so short commands fell back to stdin (the path #7236 broke). Comment lines document the source but only spend that budget in the payload; dropping them frees 3,732 encoded characters, more headroom than main had. * fix(windows): quote agent args with "" where cmd.exe or Windows PowerShell reads them Escaping embedded quotes as \" let pwsh 7.3+'s .cmd quoting expose a prompt's `&` to cmd.exe, which then ran the rest as a command. Where cmd.exe reads the line, or Windows PowerShell counts quotes naively, the function now quotes the argument itself and writes each quote as "", as process-host's windows-command-line.ts already does. That also delivers text 5.1 used to strip, so its special cases go away. The codex and claude wrappers now come as one fragment with the function they call, the bootstrap test pins the startup-command budget instead of restating the strip, and the cwd-restore comment leaves the payload too. --------- Co-authored-by: Orca Worker <orca-worker@localhost>
This commit is contained in:
@@ -8,6 +8,20 @@ import { resolveWindowsShellLaunchArgs } from './providers/windows-shell-args'
|
||||
import { STARTUP_COMMAND_FEATURES } from './shell-startup-launch-intent-fixtures'
|
||||
|
||||
describe('PowerShell OSC 133 bootstrap', () => {
|
||||
// Why: the bootstrap shares the -EncodedCommand budget with the startup command riding along.
|
||||
it('leaves room for a long startup command to ride in the shell args', () => {
|
||||
const cwd = `C:\\Users\\alice\\${'nested-project-folder\\'.repeat(5)}repo`
|
||||
const result = resolveWindowsShellLaunchArgs(
|
||||
'pwsh.exe',
|
||||
cwd,
|
||||
cwd,
|
||||
undefined,
|
||||
`orca ${'x'.repeat(1000)}`
|
||||
)
|
||||
|
||||
expect(result.startupCommandDeliveredInShellArgs).toBe(true)
|
||||
})
|
||||
|
||||
it('wraps prompt/readline without bypassing profiles or execution policy', () => {
|
||||
const script = getPowerShellOsc133Bootstrap()
|
||||
|
||||
@@ -50,7 +64,7 @@ describe('PowerShell OSC 133 bootstrap', () => {
|
||||
|
||||
// Why pinned: the MDE review (see powershell-osc133-bootstrap.ts) declined a
|
||||
// switch to -Command. Any future delivery shape must still hand PowerShell this
|
||||
// payload byte for byte -- comments, quotes, `$` and newlines included.
|
||||
// payload byte for byte -- quotes, `$` and newlines included.
|
||||
describe.each([
|
||||
[
|
||||
'daemon shell-ready',
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { getPowerShellOmpShellWrapper } from './pty/omp-shell-wrapper'
|
||||
import { MANAGED_DATA_ACCOUNT_POWERSHELL_RESTORE } from '../shared/managed-data-account-shell'
|
||||
import { getPowerShellCodexShellLaunchPreflight } from '../shared/codex-shell-function'
|
||||
import { getPowerShellClaudeShellFunction } from '../shared/claude-shell-function'
|
||||
import { getPowerShellAgentWrappers } from '../shared/powershell-agent-wrappers'
|
||||
export { encodePowerShellCommand } from '../shared/powershell-command-encoding'
|
||||
|
||||
/**
|
||||
@@ -59,7 +58,7 @@ if ($ExecutionContext.SessionState.LanguageMode -eq "FullLanguage" -and
|
||||
} catch { Write-Error $_ -ErrorAction Continue }
|
||||
|
||||
${getPowerShellOmpShellWrapper()}
|
||||
${getPowerShellCodexShellLaunchPreflight() + getPowerShellClaudeShellFunction()}
|
||||
${getPowerShellAgentWrappers()}
|
||||
|
||||
$Global:__OrcaOsc133State = @{
|
||||
OriginalPrompt = $function:prompt
|
||||
@@ -102,8 +101,15 @@ ${getPowerShellCodexShellLaunchPreflight() + getPowerShellClaudeShellFunction()}
|
||||
}
|
||||
`
|
||||
|
||||
// Why strip comment lines: they document the source, but in the payload they only spend the
|
||||
// command-line budget that lets a startup command ride along (windows-shell-args.ts).
|
||||
const POWERSHELL_OSC133_BOOTSTRAP_PAYLOAD = POWERSHELL_OSC133_BOOTSTRAP.replace(
|
||||
/^[ \t]*#.*\n/gm,
|
||||
''
|
||||
)
|
||||
|
||||
export function getPowerShellOsc133Bootstrap(): string {
|
||||
return POWERSHELL_OSC133_BOOTSTRAP
|
||||
return POWERSHELL_OSC133_BOOTSTRAP_PAYLOAD
|
||||
}
|
||||
|
||||
export function isPowerShellExecutableName(shellName: string): boolean {
|
||||
|
||||
@@ -100,9 +100,9 @@ function getCmdShellArgStartupCommand(command?: string): string | null {
|
||||
* large payloads return the bootstrap alone so stdin delivery remains available.
|
||||
*/
|
||||
function getPowerShellRestoreCwdCommand(cwd: string): string {
|
||||
// Why: profiles can change location, so restore the PTY cwd after profile loading.
|
||||
return [
|
||||
'',
|
||||
'# Profiles can change location; restore the PTY cwd after profile loading.',
|
||||
`try { Set-Location -LiteralPath ${quoteStartupArg(cwd, 'powershell')} -ErrorAction Stop } catch { Write-Warning "Failed to restore working directory: $_" }`
|
||||
].join('\n')
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
getPowerShellCodexShellLaunchPreflight
|
||||
} from '../../shared/codex-shell-function'
|
||||
import { fishRequirementViolation, resolveFishBinary } from '../../shared/fish-binary-requirement'
|
||||
import { getPowerShellAgentWrappers } from '../../shared/powershell-agent-wrappers'
|
||||
|
||||
const roots: string[] = []
|
||||
const zshAvailable = existsSync('/bin/zsh')
|
||||
@@ -392,7 +393,7 @@ describe('PowerShell Codex shell launch preflight', () => {
|
||||
[
|
||||
'$ErrorActionPreference = "Stop"',
|
||||
'$PSNativeCommandUseErrorActionPreference = $true',
|
||||
getPowerShellCodexShellLaunchPreflight(),
|
||||
getPowerShellAgentWrappers(),
|
||||
'codex'
|
||||
].join('\n')
|
||||
],
|
||||
@@ -563,7 +564,7 @@ describe.skipIf(process.platform === 'win32')('Codex preflight paths containing
|
||||
|
||||
const result = spawnSync(
|
||||
'pwsh',
|
||||
['-NoLogo', '-NoProfile', '-Command', `${getPowerShellCodexShellLaunchPreflight()}\ncodex`],
|
||||
['-NoLogo', '-NoProfile', '-Command', `${getPowerShellAgentWrappers()}\ncodex`],
|
||||
{
|
||||
encoding: 'utf-8',
|
||||
env: {
|
||||
|
||||
@@ -13,10 +13,10 @@ import { spawnSync } from 'node:child_process'
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import {
|
||||
getFishCodexShellLaunchPreflight,
|
||||
getPosixCodexShellLaunchPreflight,
|
||||
getPowerShellCodexShellLaunchPreflight
|
||||
getPosixCodexShellLaunchPreflight
|
||||
} from '../../shared/codex-shell-function'
|
||||
import { resolveFishBinary } from '../../shared/fish-binary-requirement'
|
||||
import { getPowerShellAgentWrappers } from '../../shared/powershell-agent-wrappers'
|
||||
|
||||
const isWindows = process.platform === 'win32'
|
||||
const fishLookup = resolveFishBinary()
|
||||
@@ -31,6 +31,8 @@ const HELP_WITHOUT_FLAG = 'Usage: codex [OPTIONS] [PROMPT]\n --no-alt-scree
|
||||
const ADDED: string[][] = [
|
||||
[],
|
||||
['fix the bug'],
|
||||
// Why: Windows PowerShell and pwsh's .cmd passing drop embedded quotes unless the wrapper escapes them.
|
||||
['say "hi" to "bob"'],
|
||||
['exec the plan'],
|
||||
['-m', 'gpt-5', '--yolo', 'x'],
|
||||
['resume'],
|
||||
@@ -160,7 +162,7 @@ function run(
|
||||
shell === 'fish'
|
||||
? getFishCodexShellLaunchPreflight()
|
||||
: isPowerShell(shell)
|
||||
? getPowerShellCodexShellLaunchPreflight()
|
||||
? getPowerShellAgentWrappers()
|
||||
: getPosixCodexShellLaunchPreflight()
|
||||
// Why the guard: rows include `login`, which a real codex would run against the host's account.
|
||||
const guard = isPowerShell(shell)
|
||||
|
||||
@@ -209,7 +209,7 @@ it("leaves the shell's Anthropic auth alone in the PowerShell function", () => {
|
||||
|
||||
it('restores PowerShell process env without creating empty variables', () => {
|
||||
const script = getPowerShellClaudeShellFunction()
|
||||
expect(script.startsWith('\n$orcaClaudeCommand = Get-Command claude')).toBe(true)
|
||||
expect(script.startsWith('$orcaClaudeCommand = Get-Command claude')).toBe(true)
|
||||
expect(script).toContain('finally {')
|
||||
// .NET 9+ turns a $null/'' SetEnvironmentVariable into an empty variable, not a removal.
|
||||
expect(script).not.toMatch(/SetEnvironmentVariable\([^)]*,\s*(\$null|''|"")\s*,/)
|
||||
@@ -229,6 +229,12 @@ it('prints no Orca text from the PowerShell function and creates a missing accou
|
||||
expect(script).toContain('-not [IO.Path]::IsPathRooted($orcaClaudeHome)')
|
||||
})
|
||||
|
||||
it('escapes its arguments for the resolved PowerShell binary before splatting them', () => {
|
||||
expect(getPowerShellClaudeShellFunction()).toContain(
|
||||
'$claudeArgs = @(__OrcaEscapeNativeArgs $binary.Source $args)'
|
||||
)
|
||||
})
|
||||
|
||||
it('trims the PowerShell pointer read as POSIX command substitution does', () => {
|
||||
expect(getPowerShellClaudeShellFunction()).toContain(
|
||||
'[IO.File]::ReadAllText($env:ORCA_CLAUDE_PROFILE_POINTER).TrimEnd()'
|
||||
|
||||
@@ -71,10 +71,8 @@ set -e __orca_claude_type
|
||||
`
|
||||
}
|
||||
|
||||
/** Leading newline: the codex fragment it follows ends without one. */
|
||||
export function getPowerShellClaudeShellFunction(): string {
|
||||
return `
|
||||
$orcaClaudeCommand = Get-Command claude -ErrorAction SilentlyContinue | Select-Object -First 1
|
||||
return `$orcaClaudeCommand = Get-Command claude -ErrorAction SilentlyContinue | Select-Object -First 1
|
||||
if ($env:ORCA_CLAUDE_PROFILE_POINTER -and $orcaClaudeCommand -and
|
||||
$orcaClaudeCommand.CommandType -in @("Application", "ExternalScript")) {
|
||||
function Global:claude {
|
||||
@@ -104,7 +102,8 @@ function Global:claude {
|
||||
$env:ORCA_CLAUDE_INJECTED_CONFIG_DIR = $orcaClaudeHome
|
||||
}
|
||||
$binary = Get-Command claude -CommandType Application,ExternalScript -ErrorAction Stop | Select-Object -First 1
|
||||
if ($MyInvocation.ExpectingInput) { $input | & $binary.Source @args } else { & $binary.Source @args }
|
||||
$claudeArgs = @(__OrcaEscapeNativeArgs $binary.Source $args)
|
||||
if ($MyInvocation.ExpectingInput) { $input | & $binary.Source @claudeArgs } else { & $binary.Source @claudeArgs }
|
||||
$global:LASTEXITCODE = $LASTEXITCODE
|
||||
} catch { $global:LASTEXITCODE = 1; Write-Error $_ -ErrorAction Continue }
|
||||
finally {
|
||||
|
||||
@@ -81,11 +81,12 @@ if ($orcaCodexCommand -and
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
$orcaCodexArgs = @(__OrcaEscapeNativeArgs $orcaCodexExecutable.Source $args)
|
||||
# Why: a native command inside a function never sees the function's pipeline input on its own.
|
||||
if ($MyInvocation.ExpectingInput) {
|
||||
$input | & $orcaCodexExecutable.Source @orcaCodexFlags @args
|
||||
$input | & $orcaCodexExecutable.Source @orcaCodexFlags @orcaCodexArgs
|
||||
} else {
|
||||
& $orcaCodexExecutable.Source @orcaCodexFlags @args
|
||||
& $orcaCodexExecutable.Source @orcaCodexFlags @orcaCodexArgs
|
||||
}
|
||||
$global:LASTEXITCODE = $LASTEXITCODE
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { getPowerShellClaudeShellFunction } from './claude-shell-function'
|
||||
import { getPowerShellCodexShellLaunchPreflight } from './codex-shell-function'
|
||||
import { getPowerShellEscapeNativeArgsFunction } from './powershell-native-argument'
|
||||
|
||||
/** Orca's PowerShell `codex` and `claude` wrappers, after the argument escaping both call. */
|
||||
export function getPowerShellAgentWrappers(): string {
|
||||
return [
|
||||
getPowerShellEscapeNativeArgsFunction(),
|
||||
getPowerShellCodexShellLaunchPreflight(),
|
||||
getPowerShellClaudeShellFunction()
|
||||
].join('\n')
|
||||
}
|
||||
@@ -19,6 +19,33 @@ function quotePowerShellMultilineLiteral(value: string): string {
|
||||
return `"${escaped}"`
|
||||
}
|
||||
|
||||
/**
|
||||
* PowerShell function that escapes arguments for `$Program`, for Orca's agent wrappers to splat.
|
||||
*
|
||||
* Why at runtime: the escaping depends on the PowerShell reading the line. Legacy argv passing
|
||||
* (Windows PowerShell 5.1, pwsh before 7.3, and 7.3's `Windows` mode for `.cmd`/`.bat`) leaves
|
||||
* embedded `"` unescaped, so the program drops them; otherwise PowerShell escapes them itself and
|
||||
* must get the text untouched.
|
||||
*
|
||||
* Where cmd.exe reads the line, or Windows PowerShell decides quoting by counting `"` naively, the
|
||||
* argument is quoted here with `""` for each quote, as process-host's windows-command-line.ts does:
|
||||
* a `\"` would flip cmd's quote parity and turn a later `&` into an operator.
|
||||
*/
|
||||
export function getPowerShellEscapeNativeArgsFunction(): string {
|
||||
return `function Global:__OrcaEscapeNativeArgs([string]$Program, [object[]]$Arguments) {
|
||||
Set-StrictMode -Off
|
||||
$mode = $PSNativeCommandArgumentPassing
|
||||
$cmd = $Program -match '\\.(bat|cmd)$'
|
||||
if ($mode -eq 'Standard' -or ($mode -eq 'Windows' -and -not $cmd)) { return $Arguments }
|
||||
$selfQuote = $cmd -or $PSVersionTable.PSVersion.Major -lt 6
|
||||
foreach ($arg in $Arguments) {
|
||||
if ($arg -isnot [string] -or $arg -notmatch '["\\\\]') { $arg; continue }
|
||||
if ($selfQuote) { '"' + (($arg -replace '(\\\\*)"', '$1$1""') -replace '(\\\\+)\\z', '$1$1') + '"' }
|
||||
else { $arg -replace '(\\\\*)"', '$1$1\\"' }
|
||||
}
|
||||
}`
|
||||
}
|
||||
|
||||
export function quotePowerShellNativeArgument(value: string): string {
|
||||
// Why: Windows PowerShell 5.1 drops unescaped embedded quotes when it
|
||||
// constructs argv for native executables such as wsl.exe.
|
||||
|
||||
@@ -1,25 +1,31 @@
|
||||
import { spawnSync } from 'node:child_process'
|
||||
import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { expect, it } from 'vitest'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { runProcess } from '@orca/process-host'
|
||||
import { quotePowerShellLiteral } from './powershell-native-argument'
|
||||
import { getPowerShellAgentWrappers } from './powershell-agent-wrappers'
|
||||
import {
|
||||
getPowerShellEscapeNativeArgsFunction,
|
||||
quotePowerShellLiteral
|
||||
} from './powershell-native-argument'
|
||||
|
||||
async function captureNativeArgv(root: string, quotedArg: string): Promise<string[]> {
|
||||
/** Runs `build(native)` in `powershell`, where `native` invokes a program that records its argv. */
|
||||
async function captureNativeArgv(
|
||||
root: string,
|
||||
powershell: string,
|
||||
build: (native: string) => string
|
||||
): Promise<string[]> {
|
||||
const capturePath = join(root, 'argv.json')
|
||||
const scriptPath = join(root, 'capture.js')
|
||||
await writeFile(
|
||||
scriptPath,
|
||||
`require('node:fs').writeFileSync(${JSON.stringify(capturePath)}, JSON.stringify(process.argv.slice(2)))`
|
||||
)
|
||||
const native = `& ${quotePowerShellLiteral(process.execPath)} ${quotePowerShellLiteral(scriptPath)}`
|
||||
const result = await runProcess({
|
||||
program: 'powershell.exe',
|
||||
args: [
|
||||
'-NoProfile',
|
||||
'-NonInteractive',
|
||||
'-Command',
|
||||
`& ${quotePowerShellLiteral(process.execPath)} ${quotePowerShellLiteral(scriptPath)} ${quotedArg}`
|
||||
],
|
||||
program: powershell,
|
||||
args: ['-NoProfile', '-NonInteractive', '-Command', build(native)],
|
||||
cwd: root
|
||||
})
|
||||
expect(result.code, JSON.stringify(result)).toBe(0)
|
||||
@@ -32,19 +38,120 @@ it.skipIf(process.platform !== 'win32')(
|
||||
'passes a multi-line value to a native program exactly as the single-quoted form did',
|
||||
async () => {
|
||||
const root = await mkdtemp(join(tmpdir(), 'orca-ps-multiline-'))
|
||||
const capture = (quoted: string): Promise<string[]> =>
|
||||
captureNativeArgv(root, 'powershell.exe', (native) => `${native} ${quoted}`)
|
||||
try {
|
||||
const plain = 'line one\r\nline two $HOME `tick\nO\u2019Brien \u201Cdouble\u201D end'
|
||||
expect(await captureNativeArgv(root, quotePowerShellLiteral(plain))).toEqual([plain])
|
||||
expect(await capture(quotePowerShellLiteral(plain))).toEqual([plain])
|
||||
|
||||
// Why compare with the old raw single-quoted form: 5.1 drops embedded `"` from native argv
|
||||
// either way, and the one-line form must not change what reaches the program.
|
||||
const withQuotes = 'say "hi"\nbye'
|
||||
const legacy = `'${withQuotes.replace(/'/g, "''")}'`
|
||||
expect(await captureNativeArgv(root, quotePowerShellLiteral(withQuotes))).toEqual(
|
||||
await captureNativeArgv(root, legacy)
|
||||
)
|
||||
expect(await capture(quotePowerShellLiteral(withQuotes))).toEqual(await capture(legacy))
|
||||
} finally {
|
||||
await rm(root, { recursive: true, force: true })
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const isWindows = process.platform === 'win32'
|
||||
const pwshAvailable =
|
||||
isWindows &&
|
||||
spawnSync('pwsh.exe', ['-NoProfile', '-Command', 'exit 0'], { windowsHide: true }).status === 0
|
||||
|
||||
const POWERSHELLS = [
|
||||
{ name: 'Windows PowerShell', powershell: 'powershell.exe', prelude: '', available: isWindows },
|
||||
{ name: 'pwsh', powershell: 'pwsh.exe', prelude: '', available: pwshAvailable },
|
||||
{
|
||||
name: 'pwsh with Legacy passing',
|
||||
powershell: 'pwsh.exe',
|
||||
prelude: "$PSNativeCommandArgumentPassing = 'Legacy'",
|
||||
available: pwshAvailable
|
||||
}
|
||||
]
|
||||
|
||||
describe.each(POWERSHELLS)(
|
||||
'__OrcaEscapeNativeArgs under $name',
|
||||
({ powershell, prelude, available }) => {
|
||||
it.skipIf(!available)('delivers embedded quotes and backslashes exactly', async () => {
|
||||
const root = await mkdtemp(join(tmpdir(), 'orca-ps-native-args-'))
|
||||
try {
|
||||
for (const value of [
|
||||
'say "hi" to "bob"',
|
||||
'line1 "a"\nline2 "b c"',
|
||||
'path C:\\dir\\" end "q"',
|
||||
'a "" b',
|
||||
'a"b',
|
||||
'a"b c"d',
|
||||
'"fix the login bug"',
|
||||
'look in C:\\dir\\',
|
||||
'look in C:\\dir\\\n',
|
||||
'C:\\no-space\\',
|
||||
'C:\\no-space\\\n',
|
||||
'Fix " --dangerously-bypass-approvals-and-sandbox " thanks'
|
||||
]) {
|
||||
// Why assign then splat: the agent wrappers hand their $args to the program this way.
|
||||
const argv = await captureNativeArgv(
|
||||
root,
|
||||
powershell,
|
||||
(native) =>
|
||||
`${prelude}\n${getPowerShellEscapeNativeArgsFunction()}\n` +
|
||||
`function agent { $a = @(__OrcaEscapeNativeArgs 'node.exe' $args); ${native} @a }\n` +
|
||||
`agent ${quotePowerShellLiteral(value)}`
|
||||
)
|
||||
expect(argv).toEqual([value])
|
||||
}
|
||||
} finally {
|
||||
await rm(root, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
describe.each(POWERSHELLS.slice(0, 2))(
|
||||
'agent wrappers under $name',
|
||||
({ powershell, available }) => {
|
||||
it.skipIf(!available)(
|
||||
'hand a quoted prompt to a .cmd shim intact, without cmd running any of it',
|
||||
async () => {
|
||||
const root = await mkdtemp(join(tmpdir(), 'orca-ps-agent-wrappers-'))
|
||||
try {
|
||||
await writeFile(
|
||||
join(root, 'capture.js'),
|
||||
'console.log(JSON.stringify([require("node:path").parse(process.argv[2]).name, ...process.argv.slice(3)]))'
|
||||
)
|
||||
for (const agent of ['codex', 'claude']) {
|
||||
await writeFile(join(root, `${agent}.cmd`), `@node "%~dp0capture.js" "%~f0" %*\r\n`)
|
||||
}
|
||||
// Why this shape: escaping it as `\"` makes pwsh's .cmd quoting expose `&` to cmd.
|
||||
const prompt = 'a"b & echo INJECTED'
|
||||
const result = await runProcess({
|
||||
program: powershell,
|
||||
args: [
|
||||
'-NoProfile',
|
||||
'-NonInteractive',
|
||||
'-Command',
|
||||
`${getPowerShellAgentWrappers()}\ncodex ${quotePowerShellLiteral(prompt)}\nclaude ${quotePowerShellLiteral(prompt)}`
|
||||
],
|
||||
cwd: root,
|
||||
env: {
|
||||
...process.env,
|
||||
PATH: `${root};${process.env.PATH ?? ''}`,
|
||||
ORCA_CODEX_ISOLATE: '0',
|
||||
ORCA_CLAUDE_PROFILE_POINTER: join(root, 'no-pointer')
|
||||
}
|
||||
})
|
||||
|
||||
expect(result.code, JSON.stringify(result)).toBe(0)
|
||||
expect(result.stdout.trim().split(/\r?\n/)).toEqual([
|
||||
JSON.stringify(['codex', prompt]),
|
||||
JSON.stringify(['claude', prompt])
|
||||
])
|
||||
} finally {
|
||||
await rm(root, { recursive: true, force: true })
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user