mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 00:02:31 +00:00
* Honor configured shells during worktree setup
* Align setup launch paths with selected Windows shells
* Carry setup shell selection through deferred launches
* Prove Windows setup shell routing at its real adapters
* Ground remote PowerShell proof in the real writer
* Preserve Git Bash across deferred setup launches
* Harden Windows setup runner shell selection
- Resolve remote PowerShell binary without local pwsh probe: for SSH/remote
Windows worktrees, isPwshAvailable() reflects only the LOCAL host, so an
'auto' implementation could route the remote runner to a pwsh.exe the remote
lacks. Add resolveSetupRunnerShell(..., { probeLocalPwsh: false }) so remote
auto keeps the always-present powershell.exe; explicit pwsh.exe still honored.
- Preserve native exit codes in the PowerShell runner by checking
$LASTEXITCODE before $?, so a failing native command surfaces its real code
instead of a generic exit 1; $? still catches cmdlet soft-failures.
- Write the PowerShell runner with a UTF-8 BOM so Windows PowerShell 5.1 (the
new default powershell.exe) reads it as UTF-8 instead of ANSI, preventing
non-ASCII setup-script corruption.
- Add unit tests for the remote-probe behavior.
* Restore setup-shell scope narrowing over the rebase
The force-pushed rebase dropped five review-fix commits that were already
on this branch; this reapplies their combined effect on top of the new
base and the hardening commit:
- Keep SSH setup shell selection remote-owned (no local terminalWindowsShell
or pwsh routing for remote hosts; supersedes the probeLocalPwsh guard)
- Preserve cmd setup compatibility outside POSIX shells (no .ps1 runner
family, so the BOM/exit-code hardening is no longer applicable)
- Route WSL setup runners from the project runtime
- Avoid blocking PowerShell probes during setup creation
- Correct SSH and WSL background setup fixtures
* Satisfy the changed-code gates for the setup-shell runner
- createWorktreeRunnerScript took 7 positional parameters, tripping the
changed-code max-params gate; move it to a single options object.
- hooks-runner.test.ts deep-equals the createSetupRunnerScript result, so
assert the cmd shell now returned for native Windows worktrees.
* Carry the setup launch shell through observed and issue runners
- buildObservedSetupCommand takes the runner's launch shell so WSL-routed
Windows-drive setup replays use /mnt/c instead of Git Bash /c
- resolveSetupRunnerShell gates the posix runner on the same Git Bash
resolution the PTY uses, so a missing or non-MSYS bash keeps the cmd runner
- issue-command runners carry their launch shell, and the renderer passes it
when building the queued command
- treat a bare `bash` shell setting as POSIX like `bash.exe`
Co-authored-by: Orca <help@stably.ai>
* fix(worktrees): close counsel P1 gaps for Windows setup shells
Route windowless/headless creates through the shell-aware setup runner when a
PTY controller is available, existence-check explicit Git Bash paths before
committing to .sh runners, thread the resolved shell into issue-command
runners, and document the intentional Git Bash interpreter flip with a narrow
scope table.
* Convert setup env to MSYS form and harden the bare cmd runner launch
C3: a Git Bash setup runner now receives ORCA_*/CONDUCTOR_*/GHOSTX_* path
values in /c/... form, matching the runner path and the shell's own HOME/PWD.
C5: extension-less `bash` resolves to Git Bash everywhere, matching how
resolveWindowsShellStartupFamily already classifies it.
C7: runner paths carrying characters that cannot be quoted on a cmd command
line launch through a delayed-expansion PowerShell shim instead, and the batch
runner disables inherited delayed expansion so `!` in setup lines survives.
Co-authored-by: Orca <help@stably.ai>
* docs: note MSYS ORCA_* paths and bare bash Git Bash resolution
Keep the setup-shell release note aligned with C3 env conversion and C5 bare
bash resolution so the published claim matches runtime behavior.
* revert: drop windows-setup-shell doc allowlist and AGENTS link
Keep the counsel P1/P2 product fixes without expanding the docs allowlist
or AGENTS.md guidance surface.
* fix(plugins): contain Parcel unsubscribe rejections under Vitest
Dev plugin watchers fire-and-forget unsubscribe, and in-process Parcel
can reject when temp watch roots are already deleted. Catch those
rejections so they cannot fail the suite as unhandled errors.
* fix(plugins): keep in-process unsubscribe rejection surface
Swallowing Parcel unsubscribe errors broke mocked unsubscribe tests
that return non-Promises and expect rejections. Contain failures only
in PluginDevWatcher fire-and-forget paths.
---------
Co-authored-by: OrcaWin <alpha-eng@stably.ai>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
142 lines
4.6 KiB
TypeScript
142 lines
4.6 KiB
TypeScript
import { isWindowsAbsolutePathLike } from './cross-platform-path'
|
|
import {
|
|
buildWindowsCmdRunnerDelayedLaunchCommand,
|
|
windowsRunnerPathNeedsCmdGuard
|
|
} from './windows-cmd-runner-delayed-launch'
|
|
|
|
export type SetupRunnerCommandPlatform = 'windows' | 'posix'
|
|
export type SetupRunnerShellFamily = 'posix' | 'cmd'
|
|
export type SetupRunnerCommandShell = 'posix' | 'windows'
|
|
export type SetupRunnerShell = {
|
|
family: SetupRunnerShellFamily
|
|
executable?: string
|
|
}
|
|
|
|
export type SetupRunnerCommandResolution = {
|
|
command: string
|
|
runnerScriptPathForShell: string
|
|
shell: SetupRunnerCommandShell
|
|
}
|
|
|
|
export function buildSetupRunnerCommand(
|
|
runnerScriptPath: string,
|
|
platform: SetupRunnerCommandPlatform,
|
|
shell?: SetupRunnerShell
|
|
): string {
|
|
return resolveSetupRunnerCommand(runnerScriptPath, platform, shell).command
|
|
}
|
|
|
|
export function getSetupRunnerCommandPlatformForPath(
|
|
runnerScriptPath: string,
|
|
fallbackPlatform: SetupRunnerCommandPlatform
|
|
): SetupRunnerCommandPlatform {
|
|
if (isWindowsAbsolutePathLike(runnerScriptPath)) {
|
|
return 'windows'
|
|
}
|
|
if (runnerScriptPath.startsWith('/')) {
|
|
return 'posix'
|
|
}
|
|
return fallbackPlatform
|
|
}
|
|
|
|
export function resolveSetupRunnerCommand(
|
|
runnerScriptPath: string,
|
|
platform: SetupRunnerCommandPlatform,
|
|
shell?: SetupRunnerShell
|
|
): SetupRunnerCommandResolution {
|
|
if (platform === 'windows') {
|
|
if (isWslUncPath(runnerScriptPath)) {
|
|
const linuxPath = wslUncToLinuxPath(runnerScriptPath)
|
|
return {
|
|
command: `bash ${quotePosixArg(linuxPath)}`,
|
|
runnerScriptPathForShell: linuxPath,
|
|
shell: 'posix'
|
|
}
|
|
}
|
|
if (runnerScriptPath.startsWith('/') && !isWindowsAbsolutePathLike(runnerScriptPath)) {
|
|
return {
|
|
command: `bash ${quotePosixArg(runnerScriptPath)}`,
|
|
runnerScriptPathForShell: runnerScriptPath,
|
|
shell: 'posix'
|
|
}
|
|
}
|
|
if (shell?.family === 'posix' || /\.sh$/i.test(runnerScriptPath)) {
|
|
// Why: WSL shells need /mnt/... paths, while Git Bash expects /c/... when replaying deferred setup scripts.
|
|
if (isWslExecutable(shell?.executable)) {
|
|
const wslPath = nativeWindowsPathToWslShellPath(runnerScriptPath)
|
|
return {
|
|
command: `bash ${quotePosixArg(wslPath)}`,
|
|
runnerScriptPathForShell: wslPath,
|
|
shell: 'posix'
|
|
}
|
|
}
|
|
// Why: queued setup launches can outlive the process that generated them, so convert native paths before handing off to POSIX shells.
|
|
const posixPath = nativeWindowsPathToPosixShellPath(runnerScriptPath)
|
|
return {
|
|
command: `bash ${quotePosixArg(posixPath)}`,
|
|
runnerScriptPathForShell: posixPath,
|
|
shell: 'posix'
|
|
}
|
|
}
|
|
return {
|
|
// Why: some path characters survive no amount of quoting on a cmd command line, so those
|
|
// paths take a delayed-expansion launcher instead. Every other path keeps the plain form.
|
|
command: windowsRunnerPathNeedsCmdGuard(runnerScriptPath)
|
|
? buildWindowsCmdRunnerDelayedLaunchCommand(runnerScriptPath)
|
|
: `cmd.exe /c ${quoteWindowsArg(runnerScriptPath)}`,
|
|
runnerScriptPathForShell: runnerScriptPath,
|
|
shell: 'windows'
|
|
}
|
|
}
|
|
|
|
return {
|
|
command: `bash ${quotePosixArg(runnerScriptPath)}`,
|
|
runnerScriptPathForShell: runnerScriptPath,
|
|
shell: 'posix'
|
|
}
|
|
}
|
|
|
|
export function isWslUncPath(path: string): boolean {
|
|
const normalized = path.replace(/\\/g, '/')
|
|
return /^\/\/(wsl\.localhost|wsl\$)\//i.test(normalized)
|
|
}
|
|
|
|
export function wslUncToLinuxPath(windowsPath: string): string {
|
|
const normalized = windowsPath.replace(/\\/g, '/')
|
|
const match = normalized.match(/^\/\/(wsl\.localhost|wsl\$)\/[^/]+(\/.*)?$/i)
|
|
return match?.[2] || '/'
|
|
}
|
|
|
|
function quotePosixArg(value: string): string {
|
|
if (/^[A-Za-z0-9_./:-]+$/.test(value)) {
|
|
return value
|
|
}
|
|
|
|
return `'${value.replace(/'/g, `'\\''`)}'`
|
|
}
|
|
|
|
function quoteWindowsArg(value: string): string {
|
|
return `"${value.replace(/"/g, '""')}"`
|
|
}
|
|
|
|
export function nativeWindowsPathToPosixShellPath(value: string): string {
|
|
const driveMatch = value.match(/^([A-Za-z]):[\\/](.*)$/)
|
|
if (driveMatch) {
|
|
return `/${driveMatch[1].toLowerCase()}/${driveMatch[2].replace(/\\/g, '/')}`
|
|
}
|
|
return value.replace(/\\/g, '/')
|
|
}
|
|
|
|
function nativeWindowsPathToWslShellPath(value: string): string {
|
|
const driveMatch = value.match(/^([A-Za-z]):[\\/](.*)$/)
|
|
if (driveMatch) {
|
|
return `/mnt/${driveMatch[1].toLowerCase()}/${driveMatch[2].replace(/\\/g, '/')}`
|
|
}
|
|
return value.replace(/\\/g, '/')
|
|
}
|
|
|
|
function isWslExecutable(value: string | undefined): boolean {
|
|
const basename = value?.trim().replaceAll('\\', '/').split('/').pop()?.toLowerCase() ?? ''
|
|
return basename === 'wsl.exe' || basename === 'wsl'
|
|
}
|