Files
orca/src/shared/powershell-native-argument.windows.test.ts
T
3d7b306183 fix(terminal): submit startup commands with Enter on every platform (#23672)
* fix(terminal): submit startup commands with Enter on every platform

LF is Ctrl+J to a line editor, so a shell that remaps it left the command at the prompt.

Fixes #23250

Co-authored-by: Aashish Mahato <145881415+aashish254@users.noreply.github.com>

* test(terminal): expect CR in pty ipc startup tests

* fix(terminal): submit every line of a raw multiline startup command with CR

The CR switch only replaced the trailing terminator. On the raw path — a shell
with no bracketed paste, so each line break submits its own line — interior LFs
stayed LF, which is the same remappable Ctrl+J the fix set out to remove. A
multiline startup command in sh/dash/nushell/PowerShell therefore still stalled
partway through for a user who rebinds Ctrl+J. Normalise interior breaks to CR
on that path only; the bracketed-paste payload keeps its literal newlines.

* fix(terminal): quote multi-line PowerShell arguments on one line

A multi-line agent prompt was quoted as a single-quoted PowerShell string
with raw line breaks. Typed into Windows PowerShell 5.1 without PSReadLine,
each break submits a line and the shell waits at `>>` forever. Values with
CR/LF now become one double-quoted line with backtick escapes, keeping every
byte; single-line values keep the single-quoted form unchanged.

* style(terminal): keep typographic quotes as \u escapes in PowerShell quoting

Restores the escape form the single-quoted regex used on main and applies
it to the new double-quoted regex and the native argv test, so the diff
against main stays minimal and the characters are visible in review.

---------

Co-authored-by: Aashish Mahato <145881415+aashish254@users.noreply.github.com>
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
2026-10-05 16:20:15 -07:00

51 lines
2.0 KiB
TypeScript

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 { runProcess } from './child-process/run-process'
import { quotePowerShellLiteral } from './powershell-native-argument'
async function captureNativeArgv(root: string, quotedArg: 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 result = await runProcess({
program: 'powershell.exe',
args: [
'-NoProfile',
'-NonInteractive',
'-Command',
`& ${quotePowerShellLiteral(process.execPath)} ${quotePowerShellLiteral(scriptPath)} ${quotedArg}`
],
cwd: root
})
expect(result.code, JSON.stringify(result)).toBe(0)
const argv: string[] = JSON.parse(await readFile(capturePath, 'utf8'))
return argv
}
// Why native: argv marshalling, not the generated text, is what a launched agent actually receives.
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-'))
try {
const plain = 'line one\r\nline two $HOME `tick\nO\u2019Brien \u201Cdouble\u201D end'
expect(await captureNativeArgv(root, 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)
)
} finally {
await rm(root, { recursive: true, force: true })
}
}
)