From 1187abdbc2554f69e281868a83f4ab0ca045276b Mon Sep 17 00:00:00 2001 From: hwantage <82494320+hwantage@users.noreply.github.com> Date: Mon, 26 Oct 2026 21:14:19 +0900 Subject: [PATCH] fix(windows): mark PowerShell startup command boundaries with OSC 133 C/D (#18182) A startup command embedded in the PowerShell -EncodedCommand payload runs before the first prompt, and the prompt wrapper suppresses 133;D until it has printed once. An agent launched this way exited with no command-finished boundary, so its pane was never retired. Emit 133;C before the embedded startup command and prime the prompt latch so the first prompt reports 133;D with the exit code. Stdin-typed startup commands keep readline's own marks. The encoded-command budget grows by the mark's size so real commands keep the prior limit. Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com> --- src/main/powershell-osc133-bootstrap.ts | 5 +++++ src/main/providers/windows-shell-args.test.ts | 12 +++++++++--- src/main/providers/windows-shell-args.ts | 8 +++++--- src/main/runtime/terminal-command-paint.ts | 2 +- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/main/powershell-osc133-bootstrap.ts b/src/main/powershell-osc133-bootstrap.ts index 72bec5aa1da..574eddb94e7 100644 --- a/src/main/powershell-osc133-bootstrap.ts +++ b/src/main/powershell-osc133-bootstrap.ts @@ -93,6 +93,11 @@ export function getPowerShellOsc133Bootstrap(): string { return POWERSHELL_OSC133_BOOTSTRAP_PAYLOAD } +// Startup commands run before the first prompt, which suppresses 133;D; mark them like typed commands. +export function getPowerShellOsc133StartupCommandMark(): string { + return 'if (Test-Path variable:global:__OrcaOsc133State) { [Console]::Write("$([char]27)]133;C$([char]7)"); $Global:__OrcaOsc133State.HasSeenPrompt = $true }' +} + export function isPowerShellExecutableName(shellName: string): boolean { const normalized = shellName.toLowerCase() return ( diff --git a/src/main/providers/windows-shell-args.test.ts b/src/main/providers/windows-shell-args.test.ts index d69abee50e6..c3a829379bc 100644 --- a/src/main/providers/windows-shell-args.test.ts +++ b/src/main/providers/windows-shell-args.test.ts @@ -9,6 +9,7 @@ import { resolveWindowsShellLaunchArgs } from './windows-shell-args' import { getShellReadyWrapperRoot } from './local-pty-shell-ready-wrapper-root' const CODEX_LAUNCH_PREFLIGHT = 'C:\\Program Files\\Orca\\orca.exe' +const POWERSHELL_STARTUP_COMMAND_C_MARK = '[Console]::Write("$([char]27)]133;C$([char]7)")' const CMD_CODEX_LAUNCH_PREFLIGHT = 'if defined ORCA_CODEX_LAUNCH_PREFLIGHT call %ORCA_CODEX_LAUNCH_PREFLIGHT_CMD_QUOTE%%ORCA_CODEX_LAUNCH_PREFLIGHT%%ORCA_CODEX_LAUNCH_PREFLIGHT_CMD_QUOTE% agent hooks prepare-codex > nul 2>&1' @@ -200,6 +201,10 @@ describe('resolveWindowsShellLaunchArgs', () => { expect(command).toContain('function Global:prompt') expect(command).toContain(expectedPowerShellRestoreCwdCommand("'C:\\Users\\alice'")) expect(command.trimEnd().endsWith("& 'codex' '--no-alt-screen'")).toBe(true) + // Without the mark, the first prompt after the agent exits withholds 133;D (#18180). + expect(command).toContain( + `${POWERSHELL_STARTUP_COMMAND_C_MARK}; $Global:__OrcaOsc133State.HasSeenPrompt = $true }\n& 'codex' '--no-alt-screen'` + ) }) it('preserves complex PowerShell startup command text through EncodedCommand', () => { @@ -230,9 +235,10 @@ describe('resolveWindowsShellLaunchArgs', () => { expect(result.startupCommandDeliveredInShellArgs).toBeUndefined() expect(result.shellArgs).toEqual(['-NoLogo', '-NoExit', '-EncodedCommand', expect.any(String)]) - expect(decodePowerShellCommand(result)).toContain( - expectedPowerShellRestoreCwdCommand("'C:\\Users\\alice'") - ) + const command = decodePowerShellCommand(result) + expect(command).toContain(expectedPowerShellRestoreCwdCommand("'C:\\Users\\alice'")) + // Stdin-typed commands get readline's 133;C; an early latch would emit a false 133;D. + expect(command).not.toContain(POWERSHELL_STARTUP_COMMAND_C_MARK) }) it('handles pwsh.exe (PowerShell Core) the same as Windows PowerShell', () => { diff --git a/src/main/providers/windows-shell-args.ts b/src/main/providers/windows-shell-args.ts index 0cfaab92cfb..0e94cfaa035 100644 --- a/src/main/providers/windows-shell-args.ts +++ b/src/main/providers/windows-shell-args.ts @@ -10,14 +10,16 @@ import { getBashWrapperLaunchArgs } from './local-pty-shell-ready' import { ensureShellReadyWrappersAt } from './local-pty-shell-ready-wrapper-generation' import { encodePowerShellCommand, - getPowerShellOsc133Bootstrap + getPowerShellOsc133Bootstrap, + getPowerShellOsc133StartupCommandMark } from '../powershell-osc133-bootstrap' import { quoteStartupArg } from '../../shared/tui-agent-startup-shell' /** cmd.exe's own documented ceiling; callers that go through sshd budget below it. */ export const CMD_EXE_COMMAND_LINE_MAX_CHARS = 8191 const STARTUP_COMMAND_TEXT_MAX_CHARS = 6000 -const POWERSHELL_ENCODED_COMMAND_ARG_MAX_CHARS = 28_000 +// Includes ~400 encoded chars for the startup command mark so real commands keep the prior budget. +const POWERSHELL_ENCODED_COMMAND_ARG_MAX_CHARS = 28_400 const CMD_UTF8_SETUP_COMMAND = 'chcp 65001 > nul' export const ORCA_CODEX_LAUNCH_PREFLIGHT_CMD_QUOTE_ENV = 'ORCA_CODEX_LAUNCH_PREFLIGHT_CMD_QUOTE' const CMD_CODEX_LAUNCH_PREFLIGHT = `if defined ORCA_CODEX_LAUNCH_PREFLIGHT call %${ORCA_CODEX_LAUNCH_PREFLIGHT_CMD_QUOTE_ENV}%%ORCA_CODEX_LAUNCH_PREFLIGHT%%${ORCA_CODEX_LAUNCH_PREFLIGHT_CMD_QUOTE_ENV}% agent hooks prepare-codex > nul 2>&1` @@ -88,7 +90,7 @@ function getPowerShellEncodedCommand( return { encodedCommand: encodePowerShellCommand(bootstrap) } } - const command = `${bootstrap}\n${startupCommand}` + const command = `${bootstrap}\n${getPowerShellOsc133StartupCommandMark()}\n${startupCommand}` const encodedCommand = encodePowerShellCommand(command) // UTF-16/base64 expands payloads; leave margin under CreateProcess 32,767-character limit. if (encodedCommand.length > POWERSHELL_ENCODED_COMMAND_ARG_MAX_CHARS) { diff --git a/src/main/runtime/terminal-command-paint.ts b/src/main/runtime/terminal-command-paint.ts index 2f5144e1f04..c861f1b860e 100644 --- a/src/main/runtime/terminal-command-paint.ts +++ b/src/main/runtime/terminal-command-paint.ts @@ -52,7 +52,7 @@ export function observeTerminalCommandPaint( record.commandPaint.observe(data, normalizedText) } -/** Without markers (older fish, relay zsh, unwrapped shells, Windows command args), any output is the only paint evidence. */ +/** Without markers (older fish, relay zsh, unwrapped shells, cmd.exe /K startup args), any output is the only paint evidence. */ export function hasTerminalCommandPainted(record: TerminalCommandPaintRecord): boolean { return record.commandPaint?.hasPainted() ?? true }