mirror of
https://github.com/stablyai/orca.git
synced 2026-11-03 10:37:35 +00:00
* 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>
75 lines
2.9 KiB
TypeScript
75 lines
2.9 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import {
|
|
buildStartupCommandSubmission,
|
|
isBracketedPasteSafeShell
|
|
} from './startup-command-submission'
|
|
|
|
describe('buildStartupCommandSubmission', () => {
|
|
it('submits a single-line command with CR', () => {
|
|
expect(buildStartupCommandSubmission('claude', { bracketedPasteSafe: true })).toBe('claude\r')
|
|
expect(buildStartupCommandSubmission('claude', { bracketedPasteSafe: false })).toBe('claude\r')
|
|
})
|
|
|
|
it('replaces a caller-supplied trailing terminator with CR', () => {
|
|
for (const command of ['claude\n', 'claude\r', 'claude\r\n']) {
|
|
expect(buildStartupCommandSubmission(command, { bracketedPasteSafe: true })).toBe('claude\r')
|
|
}
|
|
})
|
|
|
|
it('wraps a multiline command in bracketed paste with a trailing submit byte', () => {
|
|
const command = "claude 'first\nsecond'"
|
|
expect(buildStartupCommandSubmission(command, { bracketedPasteSafe: true })).toBe(
|
|
`\x1b[200~${command}\x1b[201~\r`
|
|
)
|
|
})
|
|
|
|
it('strips a trailing submit byte before bracket-wrapping the multiline body', () => {
|
|
const body = "claude 'first\nsecond'"
|
|
expect(buildStartupCommandSubmission(`${body}\n`, { bracketedPasteSafe: true })).toBe(
|
|
`\x1b[200~${body}\x1b[201~\r`
|
|
)
|
|
})
|
|
|
|
it('keeps the raw path for multiline commands when bracketed paste is unsafe', () => {
|
|
expect(buildStartupCommandSubmission('echo one\necho two', { bracketedPasteSafe: false })).toBe(
|
|
'echo one\recho two\r'
|
|
)
|
|
})
|
|
|
|
// Why: on the raw path every line break submits a line, so an interior LF is the same
|
|
// remappable Ctrl+J the trailing terminator was (#23250).
|
|
it('submits every line of a raw multiline command with CR', () => {
|
|
for (const command of ['echo one\necho two', 'echo one\r\necho two', 'echo one\recho two']) {
|
|
expect(buildStartupCommandSubmission(command, { bracketedPasteSafe: false })).toBe(
|
|
'echo one\recho two\r'
|
|
)
|
|
}
|
|
})
|
|
|
|
it('leaves interior line breaks intact inside a bracketed-paste payload', () => {
|
|
const command = "claude 'first\nsecond'"
|
|
expect(buildStartupCommandSubmission(command, { bracketedPasteSafe: true })).toBe(
|
|
`\x1b[200~${command}\x1b[201~\r`
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('isBracketedPasteSafeShell', () => {
|
|
it('always trusts bash and zsh', () => {
|
|
for (const shellName of ['bash', 'zsh', 'BASH']) {
|
|
expect(isBracketedPasteSafeShell({ shellName, waitsForShellReady: false })).toBe(true)
|
|
}
|
|
})
|
|
|
|
it('trusts fish only behind the shell-ready barrier', () => {
|
|
expect(isBracketedPasteSafeShell({ shellName: 'fish', waitsForShellReady: true })).toBe(true)
|
|
expect(isBracketedPasteSafeShell({ shellName: 'fish', waitsForShellReady: false })).toBe(false)
|
|
})
|
|
|
|
it('rejects shells with no known bracketed-paste line editor', () => {
|
|
for (const shellName of ['sh', 'dash', 'nu', 'powershell.exe', '']) {
|
|
expect(isBracketedPasteSafeShell({ shellName, waitsForShellReady: true })).toBe(false)
|
|
}
|
|
})
|
|
})
|