Fix PowerShell worktree terminal cwd (#7435)
This commit is contained in:
parent
cda97cec41
commit
c1d2c4be08
|
|
@ -2,10 +2,6 @@ import { existsSync, mkdtempSync, readFileSync, rmSync } from 'node:fs'
|
|||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
||||
import {
|
||||
encodePowerShellCommand,
|
||||
getPowerShellOsc133Bootstrap
|
||||
} from '../powershell-osc133-bootstrap'
|
||||
import {
|
||||
buildWslInteractiveLoginShellCommand,
|
||||
escapeWslShCommandForWindows
|
||||
|
|
@ -19,6 +15,15 @@ function expectedWslArgs(linuxCwd: string, distro?: string): string[] {
|
|||
return distro ? ['-d', distro, ...shellArgs] : shellArgs
|
||||
}
|
||||
|
||||
function decodePowerShellCommand(result: ReturnType<typeof resolveWindowsShellLaunchArgs>): string {
|
||||
expect(result.shellArgs.slice(0, 3)).toEqual(['-NoLogo', '-NoExit', '-EncodedCommand'])
|
||||
return Buffer.from(result.shellArgs[3] ?? '', 'base64').toString('utf16le')
|
||||
}
|
||||
|
||||
function expectedPowerShellRestoreCwdCommand(cwdLiteral: string): string {
|
||||
return `try { Set-Location -LiteralPath ${cwdLiteral} -ErrorAction Stop } catch { Write-Warning "Failed to restore working directory: $_" }`
|
||||
}
|
||||
|
||||
describe('resolveWindowsShellLaunchArgs', () => {
|
||||
let previousUserDataPath: string | undefined
|
||||
let userDataPath: string
|
||||
|
|
@ -90,14 +95,9 @@ describe('resolveWindowsShellLaunchArgs', () => {
|
|||
'C:\\Users\\alice',
|
||||
'C:\\Users\\alice'
|
||||
)
|
||||
expect(result.shellArgs).toEqual([
|
||||
'-NoLogo',
|
||||
'-NoExit',
|
||||
'-EncodedCommand',
|
||||
encodePowerShellCommand(getPowerShellOsc133Bootstrap())
|
||||
])
|
||||
expect(result.shellArgs).toEqual(['-NoLogo', '-NoExit', '-EncodedCommand', expect.any(String)])
|
||||
|
||||
const command = Buffer.from(result.shellArgs[3] ?? '', 'base64').toString('utf16le')
|
||||
const command = decodePowerShellCommand(result)
|
||||
const outputEncodingIndex = command.indexOf('[Console]::OutputEncoding')
|
||||
const opencodeRestoreIndex = command.indexOf(
|
||||
'$env:OPENCODE_CONFIG_DIR = $env:ORCA_OPENCODE_CONFIG_DIR'
|
||||
|
|
@ -106,6 +106,9 @@ describe('resolveWindowsShellLaunchArgs', () => {
|
|||
const ompExtensionIndex = command.indexOf('--extension $env:ORCA_OMP_STATUS_EXTENSION')
|
||||
const codexRestoreIndex = command.indexOf('$env:CODEX_HOME = $env:ORCA_CODEX_HOME')
|
||||
const promptIndex = command.indexOf('function Global:prompt')
|
||||
const cwdRestoreIndex = command.indexOf(
|
||||
expectedPowerShellRestoreCwdCommand("'C:\\Users\\alice'")
|
||||
)
|
||||
|
||||
expect(command).not.toContain('$PROFILE')
|
||||
expect(command).not.toContain('ORCA_PI_CODING_AGENT_DIR')
|
||||
|
|
@ -118,6 +121,7 @@ describe('resolveWindowsShellLaunchArgs', () => {
|
|||
expect(codexRestoreIndex).toBeGreaterThan(outputEncodingIndex)
|
||||
expect(codexRestoreIndex).toBeGreaterThan(ompWrapperIndex)
|
||||
expect(promptIndex).toBeGreaterThan(codexRestoreIndex)
|
||||
expect(cwdRestoreIndex).toBeGreaterThan(promptIndex)
|
||||
expect(command).toContain('Esc = [char]27')
|
||||
expect(command).toContain('Bel = [char]7')
|
||||
expect(command).toContain(')]133;D;$fakeExitCode$(')
|
||||
|
|
@ -134,6 +138,21 @@ describe('resolveWindowsShellLaunchArgs', () => {
|
|||
|
||||
expect(result.effectiveCwd).toBe('C:\\Users\\alice\\project')
|
||||
expect(result.validationCwd).toBe('C:\\Users\\alice\\project')
|
||||
expect(decodePowerShellCommand(result)).toContain(
|
||||
expectedPowerShellRestoreCwdCommand("'C:\\Users\\alice\\project'")
|
||||
)
|
||||
})
|
||||
|
||||
it('quotes the PowerShell cwd restore command literally', () => {
|
||||
const result = resolveWindowsShellLaunchArgs(
|
||||
'powershell.exe',
|
||||
"C:\\Users\\alice\\client's app",
|
||||
'C:\\Users\\alice'
|
||||
)
|
||||
|
||||
expect(decodePowerShellCommand(result)).toContain(
|
||||
expectedPowerShellRestoreCwdCommand("'C:\\Users\\alice\\client''s app'")
|
||||
)
|
||||
})
|
||||
|
||||
it('embeds short PowerShell startup commands after the OSC 133 bootstrap', () => {
|
||||
|
|
@ -146,8 +165,9 @@ describe('resolveWindowsShellLaunchArgs', () => {
|
|||
)
|
||||
expect(result.startupCommandDeliveredInShellArgs).toBe(true)
|
||||
|
||||
const command = Buffer.from(result.shellArgs[3] ?? '', 'base64').toString('utf16le')
|
||||
const command = decodePowerShellCommand(result)
|
||||
expect(command).toContain('function Global:prompt')
|
||||
expect(command).toContain(expectedPowerShellRestoreCwdCommand("'C:\\Users\\alice'"))
|
||||
expect(command.trimEnd().endsWith("& 'codex' '--no-alt-screen'")).toBe(true)
|
||||
})
|
||||
|
||||
|
|
@ -163,7 +183,7 @@ describe('resolveWindowsShellLaunchArgs', () => {
|
|||
)
|
||||
|
||||
expect(result.startupCommandDeliveredInShellArgs).toBe(true)
|
||||
const command = Buffer.from(result.shellArgs[3] ?? '', 'base64').toString('utf16le')
|
||||
const command = decodePowerShellCommand(result)
|
||||
expect(command).toContain(`\n${startupCommand}`)
|
||||
expect(command.trimEnd().endsWith(startupCommand)).toBe(true)
|
||||
})
|
||||
|
|
@ -178,22 +198,16 @@ describe('resolveWindowsShellLaunchArgs', () => {
|
|||
)
|
||||
|
||||
expect(result.startupCommandDeliveredInShellArgs).toBeUndefined()
|
||||
expect(result.shellArgs).toEqual([
|
||||
'-NoLogo',
|
||||
'-NoExit',
|
||||
'-EncodedCommand',
|
||||
encodePowerShellCommand(getPowerShellOsc133Bootstrap())
|
||||
])
|
||||
expect(result.shellArgs).toEqual(['-NoLogo', '-NoExit', '-EncodedCommand', expect.any(String)])
|
||||
expect(decodePowerShellCommand(result)).toContain(
|
||||
expectedPowerShellRestoreCwdCommand("'C:\\Users\\alice'")
|
||||
)
|
||||
})
|
||||
|
||||
it('handles pwsh.exe (PowerShell Core) the same as Windows PowerShell', () => {
|
||||
const result = resolveWindowsShellLaunchArgs('pwsh.exe', 'C:\\', 'C:\\Users\\alice')
|
||||
expect(result.shellArgs).toEqual([
|
||||
'-NoLogo',
|
||||
'-NoExit',
|
||||
'-EncodedCommand',
|
||||
encodePowerShellCommand(getPowerShellOsc133Bootstrap())
|
||||
])
|
||||
expect(result.shellArgs).toEqual(['-NoLogo', '-NoExit', '-EncodedCommand', expect.any(String)])
|
||||
expect(decodePowerShellCommand(result)).toContain(expectedPowerShellRestoreCwdCommand("'C:\\'"))
|
||||
})
|
||||
|
||||
it('starts Git Bash as an interactive login shell with UTF-8 console setup', () => {
|
||||
|
|
@ -362,12 +376,7 @@ describe('resolveWindowsShellLaunchArgs', () => {
|
|||
|
||||
it('is case-insensitive on the shell basename', () => {
|
||||
const result = resolveWindowsShellLaunchArgs('PowerShell.EXE', 'C:\\', 'C:\\')
|
||||
expect(result.shellArgs).toEqual([
|
||||
'-NoLogo',
|
||||
'-NoExit',
|
||||
'-EncodedCommand',
|
||||
encodePowerShellCommand(getPowerShellOsc133Bootstrap())
|
||||
])
|
||||
expect(result.shellArgs).toEqual(['-NoLogo', '-NoExit', '-EncodedCommand', expect.any(String)])
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import {
|
|||
encodePowerShellCommand,
|
||||
getPowerShellOsc133Bootstrap
|
||||
} from '../powershell-osc133-bootstrap'
|
||||
import { quoteStartupArg } from '../../shared/tui-agent-startup-shell'
|
||||
|
||||
const CMD_EXE_COMMAND_LINE_MAX_CHARS = 8191
|
||||
const STARTUP_COMMAND_TEXT_MAX_CHARS = 6000
|
||||
|
|
@ -80,11 +81,22 @@ function getCmdShellArgStartupCommand(command?: string): string | null {
|
|||
* Short startup commands are appended to the bootstrap and marked as delivered;
|
||||
* large payloads return the bootstrap alone so stdin delivery remains available.
|
||||
*/
|
||||
function getPowerShellEncodedCommand(startupCommand?: string): {
|
||||
function getPowerShellRestoreCwdCommand(cwd: string): string {
|
||||
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')
|
||||
}
|
||||
|
||||
function getPowerShellEncodedCommand(
|
||||
cwd: string,
|
||||
startupCommand?: string
|
||||
): {
|
||||
encodedCommand: string
|
||||
startupCommandDeliveredInShellArgs?: boolean
|
||||
} {
|
||||
const bootstrap = getPowerShellOsc133Bootstrap()
|
||||
const bootstrap = `${getPowerShellOsc133Bootstrap()}${getPowerShellRestoreCwdCommand(cwd)}`
|
||||
if (!startupCommand || startupCommand.length > STARTUP_COMMAND_TEXT_MAX_CHARS) {
|
||||
return { encodedCommand: encodePowerShellCommand(bootstrap) }
|
||||
}
|
||||
|
|
@ -168,7 +180,7 @@ export function resolveWindowsShellLaunchArgs(
|
|||
}
|
||||
|
||||
if (shellBasename === 'powershell.exe' || shellBasename === 'pwsh.exe') {
|
||||
const powerShellCommand = getPowerShellEncodedCommand(startupCommand)
|
||||
const powerShellCommand = getPowerShellEncodedCommand(nativeCwd, startupCommand)
|
||||
// Why: foreground-process status on Windows depends on OSC 133 C/D, and
|
||||
// PowerShell needs a prompt/readline bootstrap after profiles finish.
|
||||
return {
|
||||
|
|
|
|||
Loading…
Reference in New Issue