Keep Orca CLI first after shell startup (#25130)

* Restore the owning Orca CLI path after shell profiles

* Use a literal marker for the Bash lookup regression

* Preserve plain panes and initialize zsh after prompt hook replacement

* Preserve user line-editor dispatchers during deferred startup

* fix: retain CLI startup when global Zsh replaces prompt hooks

* test: replay global Zsh hook replacement after host startup

* test: isolate controlled Zsh widgets from distro keyboard setup

* fix(shell): preserve user hooks during deferred zsh initialization

* Keep completed Zsh startup hooks retired when the wrapper is sourced again

---------

Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Orca maintenance <orca-maintenance@users.noreply.github.com>
Co-authored-by: Orca campaign <orca-campaign@local.invalid>
This commit is contained in:
Neil
2026-10-04 02:55:59 -07:00
committed by GitHub
co-authored by Codex Orca maintenance Orca campaign
parent c4e8735f45
commit d9173ffbdb
29 changed files with 793 additions and 30 deletions
+1
View File
@@ -720,6 +720,7 @@ jobs:
src/main/pty/omp-shell-wrapper.node-pty.test.ts \
src/main/fish-xdg-data-dirs-handoff.test.ts \
src/main/shell-startup-feature-channel.test.ts \
src/main/zsh-deferred-startup-line-init.live-shell.test.ts \
src/main/terminal-history-fish-session.node-pty.test.ts \
src/main/zsh-scoped-histfile.live-shell.test.ts \
src/main/zsh-startup-hook-user-config-equivalence.live-shell.test.ts \
+1
View File
@@ -23,6 +23,7 @@ export const UNIT_EXCLUDE = [
'src/main/pty/omp-shell-wrapper-alias-safety.test.ts',
'src/main/pty/omp-shell-wrapper.node-pty.test.ts',
'src/main/shell-startup-feature-channel.test.ts',
'src/main/zsh-deferred-startup-line-init.live-shell.test.ts',
'src/main/terminal-history-fish-session.node-pty.test.ts',
'src/main/zsh-scoped-histfile.live-shell.test.ts',
'src/main/zsh-startup-hook-user-config-equivalence.live-shell.test.ts',
@@ -27,6 +27,7 @@ const shellContractFiles = [
'src/main/pty/omp-shell-wrapper-alias-safety.test.ts',
'src/main/pty/omp-shell-wrapper.node-pty.test.ts',
'src/main/shell-startup-feature-channel.test.ts',
'src/main/zsh-deferred-startup-line-init.live-shell.test.ts',
'src/main/zsh-scoped-histfile.live-shell.test.ts',
'src/main/zsh-startup-hook-user-config-equivalence.live-shell.test.ts',
'src/main/zsh-wrapper-version-mismatch.live-shell.test.ts',
@@ -30,6 +30,12 @@ __orca_restore_agent_teams_path() {
export PATH="${ORCA_AGENT_TEAMS_SHIM_DIR}:$PATH"
}
__orca_restore_agent_teams_path
if [ -n "${ORCA_CLI_BIN_DIR:-}" ]; then
case "${PATH:-}" in
"$ORCA_CLI_BIN_DIR"|"$ORCA_CLI_BIN_DIR":*) ;;
*) export PATH="$ORCA_CLI_BIN_DIR${PATH:+:$PATH}" ;;
esac
fi
# Why: user startup files may set the default OpenCode config after Orca's
# spawn env; restore the Orca-managed config dir before the first prompt.
[[ -n "${ORCA_OPENCODE_CONFIG_DIR:-}" ]] && export OPENCODE_CONFIG_DIR="${ORCA_OPENCODE_CONFIG_DIR}"
@@ -45,6 +45,40 @@ __orca_osc133_preexec() {
# which prints a warning above every command under warn_create_global.
builtin typeset -g __orca_in_command=1
}
__orca_deferred_line_init() {
builtin emulate -L zsh
(( ${+functions[__orca_deferred_init]} )) || return 0
local __orca_direct_line_init=0
[[ "${widgets[zle-line-init]:-}" == user:__orca_deferred_line_init ]] && __orca_direct_line_init=1
__orca_deferred_init
if (( __orca_direct_line_init && ${+widgets[zle-line-init]} )); then
zle zle-line-init "$@"
elif [[ "${widgets[zle-line-init]:-}" == user:__orca_prompt_mark ]]; then
local __orca_prev_line_init_fn=""
__orca_prompt_mark "$@"
fi
}
# Why: scheduled callbacks run after user prompt hooks without copying their function metadata.
__orca_deferred_sched_init() {
local __orca_prompt_status=$?
builtin emulate -L zsh
(( ${+functions[__orca_deferred_init]} )) && __orca_deferred_init
builtin unset __orca_deferred_sched_armed
builtin unfunction __orca_deferred_sched_init
return $__orca_prompt_status
}
__orca_arm_deferred_line_init() {
builtin emulate -L zsh
if [[ "${widgets[zle-line-init]:-}" != user:__orca_deferred_line_init ]]; then
if (( ${+widgets[zle-line-init]} )); then
zle -A zle-line-init __orca_saved_line_init
fi
zle -N zle-line-init __orca_deferred_line_init
fi
if (( ! $+__orca_deferred_sched_armed )) && builtin zmodload -F zsh/sched b:sched 2>/dev/null; then
builtin sched +0 __orca_deferred_sched_init && builtin typeset -g __orca_deferred_sched_armed=1
fi
}
__orca_deferred_init() {
# Why first: this body runs after the user's own config, so it would otherwise
# inherit whatever options that config left set. Under NO_UNSET an unset
@@ -54,12 +88,27 @@ __orca_deferred_init() {
(( $+_orca_deferred_init_done )) && return 0
builtin typeset -g _orca_deferred_init_done=1
builtin typeset -g precmd_functions
if (( ${+widgets[__orca_saved_line_init]} )); then
if [[ "${widgets[zle-line-init]:-}" == user:__orca_deferred_line_init ]]; then
zle -A __orca_saved_line_init zle-line-init
fi
zle -D __orca_saved_line_init
elif [[ "${widgets[zle-line-init]:-}" == user:__orca_deferred_line_init ]]; then
zle -D zle-line-init
fi
if __orca_has_feature markers; then
precmd_functions=(${precmd_functions:/__orca_deferred_init/__orca_osc133_precmd})
(( ${precmd_functions[(Ie)__orca_osc133_precmd]} )) || precmd_functions+=(__orca_osc133_precmd)
preexec_functions=(__orca_osc133_preexec ${preexec_functions[@]})
else
precmd_functions=(${precmd_functions:#__orca_deferred_init})
fi
if [ -n "${ORCA_CLI_BIN_DIR:-}" ]; then
case "${PATH:-}" in
"$ORCA_CLI_BIN_DIR"|"$ORCA_CLI_BIN_DIR":*) ;;
*) export PATH="$ORCA_CLI_BIN_DIR${PATH:+:$PATH}" ;;
esac
fi
if __orca_has_feature overlay; then
# Why: ~/.zshrc can export the user's default OpenCode config after spawn.
__orca_restore_agent_teams_path() {
@@ -202,13 +251,25 @@ __orca_deferred_init() {
# the permanent hook has not run yet and the first prompt would lose its mark.
__orca_has_feature markers && __orca_osc133_precmd
builtin unset _orca_shell_features _orca_histfile
builtin unfunction __orca_deferred_init __orca_has_feature
(( $+__orca_deferred_sched_armed )) || builtin unfunction __orca_deferred_sched_init
local __orca_widget __orca_line_init_bound=0
for __orca_widget in "${(v)widgets[@]}"; do
if [[ "$__orca_widget" == user:__orca_deferred_line_init ]]; then
__orca_line_init_bound=1
break
fi
done
(( __orca_line_init_bound )) || builtin unfunction __orca_deferred_line_init
builtin unfunction __orca_deferred_init __orca_has_feature __orca_arm_deferred_line_init
}
{
builtin typeset _orca_user_zshenv="${ZDOTDIR-$HOME}/.zshenv"
[[ ! -r "$_orca_user_zshenv" ]] || builtin source -- "$_orca_user_zshenv"
} always {
builtin unset _orca_user_zshenv
builtin typeset -ag precmd_functions
(( ${precmd_functions[(Ie)__orca_deferred_init]} )) || precmd_functions+=(__orca_deferred_init)
if (( ! $+_orca_deferred_init_done )); then
builtin typeset -ag precmd_functions
(( ${precmd_functions[(Ie)__orca_deferred_init]} )) || precmd_functions+=(__orca_deferred_init)
__orca_arm_deferred_line_init
fi
}
@@ -33,6 +33,12 @@ __orca_restore_agent_teams_path() {
export PATH="${ORCA_AGENT_TEAMS_SHIM_DIR}:$PATH"
}
__orca_restore_agent_teams_path
if [ -n "${ORCA_CLI_BIN_DIR:-}" ]; then
case "${PATH:-}" in
"$ORCA_CLI_BIN_DIR"|"$ORCA_CLI_BIN_DIR":*) ;;
*) export PATH="$ORCA_CLI_BIN_DIR${PATH:+:$PATH}" ;;
esac
fi
if [ -n "${ORCA_WSL_CLI_DIR:-}" ]; then
if [ -x "$ORCA_WSL_CLI_DIR/${ORCA_CLI_COMMAND:-}" ]; then
export PATH="$ORCA_WSL_CLI_DIR${PATH:+:$PATH}"
@@ -45,6 +45,40 @@ __orca_osc133_preexec() {
# which prints a warning above every command under warn_create_global.
builtin typeset -g __orca_in_command=1
}
__orca_deferred_line_init() {
builtin emulate -L zsh
(( ${+functions[__orca_deferred_init]} )) || return 0
local __orca_direct_line_init=0
[[ "${widgets[zle-line-init]:-}" == user:__orca_deferred_line_init ]] && __orca_direct_line_init=1
__orca_deferred_init
if (( __orca_direct_line_init && ${+widgets[zle-line-init]} )); then
zle zle-line-init "$@"
elif [[ "${widgets[zle-line-init]:-}" == user:__orca_prompt_mark ]]; then
local __orca_prev_line_init_fn=""
__orca_prompt_mark "$@"
fi
}
# Why: scheduled callbacks run after user prompt hooks without copying their function metadata.
__orca_deferred_sched_init() {
local __orca_prompt_status=$?
builtin emulate -L zsh
(( ${+functions[__orca_deferred_init]} )) && __orca_deferred_init
builtin unset __orca_deferred_sched_armed
builtin unfunction __orca_deferred_sched_init
return $__orca_prompt_status
}
__orca_arm_deferred_line_init() {
builtin emulate -L zsh
if [[ "${widgets[zle-line-init]:-}" != user:__orca_deferred_line_init ]]; then
if (( ${+widgets[zle-line-init]} )); then
zle -A zle-line-init __orca_saved_line_init
fi
zle -N zle-line-init __orca_deferred_line_init
fi
if (( ! $+__orca_deferred_sched_armed )) && builtin zmodload -F zsh/sched b:sched 2>/dev/null; then
builtin sched +0 __orca_deferred_sched_init && builtin typeset -g __orca_deferred_sched_armed=1
fi
}
__orca_deferred_init() {
# Why first: this body runs after the user's own config, so it would otherwise
# inherit whatever options that config left set. Under NO_UNSET an unset
@@ -54,8 +88,17 @@ __orca_deferred_init() {
(( $+_orca_deferred_init_done )) && return 0
builtin typeset -g _orca_deferred_init_done=1
builtin typeset -g precmd_functions
if (( ${+widgets[__orca_saved_line_init]} )); then
if [[ "${widgets[zle-line-init]:-}" == user:__orca_deferred_line_init ]]; then
zle -A __orca_saved_line_init zle-line-init
fi
zle -D __orca_saved_line_init
elif [[ "${widgets[zle-line-init]:-}" == user:__orca_deferred_line_init ]]; then
zle -D zle-line-init
fi
if __orca_has_feature markers; then
precmd_functions=(${precmd_functions:/__orca_deferred_init/__orca_osc133_precmd})
(( ${precmd_functions[(Ie)__orca_osc133_precmd]} )) || precmd_functions+=(__orca_osc133_precmd)
preexec_functions=(__orca_osc133_preexec ${preexec_functions[@]})
else
precmd_functions=(${precmd_functions:#__orca_deferred_init})
@@ -67,6 +110,12 @@ __orca_deferred_init() {
printf 'Orca CLI unavailable: cannot run %s. Check WSL Windows-drive mount options.\n' "$ORCA_WSL_CLI_DIR/${ORCA_CLI_COMMAND:-}" >&2
fi
fi
if [ -n "${ORCA_CLI_BIN_DIR:-}" ]; then
case "${PATH:-}" in
"$ORCA_CLI_BIN_DIR"|"$ORCA_CLI_BIN_DIR":*) ;;
*) export PATH="$ORCA_CLI_BIN_DIR${PATH:+:$PATH}" ;;
esac
fi
if __orca_has_feature overlay; then
# Why: ~/.zshrc can export the user's default OpenCode config after spawn.
__orca_restore_agent_teams_path() {
@@ -219,13 +268,25 @@ __orca_deferred_init() {
# the permanent hook has not run yet and the first prompt would lose its mark.
__orca_has_feature markers && __orca_osc133_precmd
builtin unset _orca_shell_features _orca_histfile
builtin unfunction __orca_deferred_init __orca_has_feature
(( $+__orca_deferred_sched_armed )) || builtin unfunction __orca_deferred_sched_init
local __orca_widget __orca_line_init_bound=0
for __orca_widget in "${(v)widgets[@]}"; do
if [[ "$__orca_widget" == user:__orca_deferred_line_init ]]; then
__orca_line_init_bound=1
break
fi
done
(( __orca_line_init_bound )) || builtin unfunction __orca_deferred_line_init
builtin unfunction __orca_deferred_init __orca_has_feature __orca_arm_deferred_line_init
}
{
builtin typeset _orca_user_zshenv="${ZDOTDIR-$HOME}/.zshenv"
[[ ! -r "$_orca_user_zshenv" ]] || builtin source -- "$_orca_user_zshenv"
} always {
builtin unset _orca_user_zshenv
builtin typeset -ag precmd_functions
(( ${precmd_functions[(Ie)__orca_deferred_init]} )) || precmd_functions+=(__orca_deferred_init)
if (( ! $+_orca_deferred_init_done )); then
builtin typeset -ag precmd_functions
(( ${precmd_functions[(Ie)__orca_deferred_init]} )) || precmd_functions+=(__orca_deferred_init)
__orca_arm_deferred_line_init
fi
}
@@ -26,6 +26,12 @@ fi
[[ -n "${ORCA_OPENCODE_CONFIG_DIR:-}" ]] && export OPENCODE_CONFIG_DIR="${ORCA_OPENCODE_CONFIG_DIR}"
[[ -n "${ORCA_MIMOCODE_HOME:-}" ]] && export MIMOCODE_HOME="${ORCA_MIMOCODE_HOME}"
[[ -n "${ORCA_REMOTE_CLI_BIN_DIR:-}" ]] && case ":$PATH:" in *:"${ORCA_REMOTE_CLI_BIN_DIR}":*) ;; *) export PATH="${ORCA_REMOTE_CLI_BIN_DIR}:$PATH" ;; esac
if [ -n "${ORCA_CLI_BIN_DIR:-}" ]; then
case "${PATH:-}" in
"$ORCA_CLI_BIN_DIR"|"$ORCA_CLI_BIN_DIR":*) ;;
*) export PATH="$ORCA_CLI_BIN_DIR${PATH:+:$PATH}" ;;
esac
fi
# Why: OMP does not auto-load Orca's managed status extension; wrap only
# interactive launch invocations so subcommands such as `omp config` keep
# their normal argv shape.
@@ -31,6 +31,40 @@ builtin typeset -g _orca_histfile="${ORCA_HISTFILE:-}"
builtin unset ORCA_HISTFILE
__orca_has_feature() { (( ${_orca_shell_features[(Ie)$1]} )) }
__orca_has_feature identity && printf "\033]777;orca-shell-start:%s\007" "$$"
__orca_deferred_line_init() {
builtin emulate -L zsh
(( ${+functions[__orca_deferred_init]} )) || return 0
local __orca_direct_line_init=0
[[ "${widgets[zle-line-init]:-}" == user:__orca_deferred_line_init ]] && __orca_direct_line_init=1
__orca_deferred_init
if (( __orca_direct_line_init && ${+widgets[zle-line-init]} )); then
zle zle-line-init "$@"
elif [[ "${widgets[zle-line-init]:-}" == user:__orca_prompt_mark ]]; then
local __orca_prev_line_init_fn=""
__orca_prompt_mark "$@"
fi
}
# Why: scheduled callbacks run after user prompt hooks without copying their function metadata.
__orca_deferred_sched_init() {
local __orca_prompt_status=$?
builtin emulate -L zsh
(( ${+functions[__orca_deferred_init]} )) && __orca_deferred_init
builtin unset __orca_deferred_sched_armed
builtin unfunction __orca_deferred_sched_init
return $__orca_prompt_status
}
__orca_arm_deferred_line_init() {
builtin emulate -L zsh
if [[ "${widgets[zle-line-init]:-}" != user:__orca_deferred_line_init ]]; then
if (( ${+widgets[zle-line-init]} )); then
zle -A zle-line-init __orca_saved_line_init
fi
zle -N zle-line-init __orca_deferred_line_init
fi
if (( ! $+__orca_deferred_sched_armed )) && builtin zmodload -F zsh/sched b:sched 2>/dev/null; then
builtin sched +0 __orca_deferred_sched_init && builtin typeset -g __orca_deferred_sched_armed=1
fi
}
__orca_deferred_init() {
# Why first: this body runs after the user's own config, so it would otherwise
# inherit whatever options that config left set. Under NO_UNSET an unset
@@ -40,7 +74,21 @@ __orca_deferred_init() {
(( $+_orca_deferred_init_done )) && return 0
builtin typeset -g _orca_deferred_init_done=1
builtin typeset -g precmd_functions
if (( ${+widgets[__orca_saved_line_init]} )); then
if [[ "${widgets[zle-line-init]:-}" == user:__orca_deferred_line_init ]]; then
zle -A __orca_saved_line_init zle-line-init
fi
zle -D __orca_saved_line_init
elif [[ "${widgets[zle-line-init]:-}" == user:__orca_deferred_line_init ]]; then
zle -D zle-line-init
fi
precmd_functions=(${precmd_functions:#__orca_deferred_init})
if [ -n "${ORCA_CLI_BIN_DIR:-}" ]; then
case "${PATH:-}" in
"$ORCA_CLI_BIN_DIR"|"$ORCA_CLI_BIN_DIR":*) ;;
*) export PATH="$ORCA_CLI_BIN_DIR${PATH:+:$PATH}" ;;
esac
fi
if __orca_has_feature overlay; then
# Why: remote startup files can re-export user defaults after relay spawn.
[[ -n "${ORCA_OPENCODE_CONFIG_DIR:-}" ]] && export OPENCODE_CONFIG_DIR="${ORCA_OPENCODE_CONFIG_DIR}"
@@ -170,13 +218,25 @@ __orca_deferred_init() {
zle -N zle-line-init __orca_prompt_mark
fi
builtin unset _orca_shell_features _orca_histfile
builtin unfunction __orca_deferred_init __orca_has_feature
(( $+__orca_deferred_sched_armed )) || builtin unfunction __orca_deferred_sched_init
local __orca_widget __orca_line_init_bound=0
for __orca_widget in "${(v)widgets[@]}"; do
if [[ "$__orca_widget" == user:__orca_deferred_line_init ]]; then
__orca_line_init_bound=1
break
fi
done
(( __orca_line_init_bound )) || builtin unfunction __orca_deferred_line_init
builtin unfunction __orca_deferred_init __orca_has_feature __orca_arm_deferred_line_init
}
{
builtin typeset _orca_user_zshenv="${ZDOTDIR-$HOME}/.zshenv"
[[ ! -r "$_orca_user_zshenv" ]] || builtin source -- "$_orca_user_zshenv"
} always {
builtin unset _orca_user_zshenv
builtin typeset -ag precmd_functions
(( ${precmd_functions[(Ie)__orca_deferred_init]} )) || precmd_functions+=(__orca_deferred_init)
if (( ! $+_orca_deferred_init_done )); then
builtin typeset -ag precmd_functions
(( ${precmd_functions[(Ie)__orca_deferred_init]} )) || precmd_functions+=(__orca_deferred_init)
__orca_arm_deferred_line_init
fi
}
+14 -2
View File
@@ -27,6 +27,7 @@ describe('prependOrcaCliDirToChildPath', () => {
platform: 'linux'
})
expect(env.PATH).toBe(`${SHIM_DIR}:/usr/local/bin:/usr/bin`)
expect(env.ORCA_CLI_BIN_DIR).toBe(SHIM_DIR)
expect(shim.ensureLinuxTerminalOrcaCliShimDir).toHaveBeenCalledWith({
userDataPath: USER_DATA
})
@@ -44,13 +45,14 @@ describe('prependOrcaCliDirToChildPath', () => {
it('leaves packaged Linux PATH untouched when no shim could be written', () => {
shim.ensureLinuxTerminalOrcaCliShimDir.mockReturnValue(null)
const env: Record<string, string> = { PATH: '/usr/bin' }
const env: Record<string, string> = { PATH: '/usr/bin', ORCA_CLI_BIN_DIR: '/old-host/cli' }
prependOrcaCliDirToChildPath(env, {
isPackaged: true,
userDataPath: USER_DATA,
platform: 'linux'
})
expect(env.PATH).toBe('/usr/bin')
expect(env.ORCA_CLI_BIN_DIR).toBeUndefined()
})
it('leads packaged macOS PATH with the bundled CLI dir', () => {
@@ -62,11 +64,16 @@ describe('prependOrcaCliDirToChildPath', () => {
platform: 'darwin'
})
expect(env.PATH).toBe(`${join(RESOURCES, 'bin')}:/usr/bin`)
expect(env.ORCA_CLI_BIN_DIR).toBe(join(RESOURCES, 'bin'))
expect(shim.ensureLinuxTerminalOrcaCliShimDir).not.toHaveBeenCalled()
})
it('leads packaged Windows PATH with the bundled CLI dir under the env block spelling', () => {
const env: Record<string, string> = { Path: 'C:\\Windows\\System32' }
const env: Record<string, string> = {
Path: 'C:\\Windows\\System32',
ORCA_CLI_BIN_DIR: '/parent-host/cli',
ORCA_WSL_CLI_DIR: '/guest/orca/bin'
}
prependOrcaCliDirToChildPath(env, {
isPackaged: true,
userDataPath: USER_DATA,
@@ -75,6 +82,8 @@ describe('prependOrcaCliDirToChildPath', () => {
})
expect(env.Path).toBe(`${join(RESOURCES, 'bin')};C:\\Windows\\System32`)
expect(env.PATH).toBeUndefined()
expect(env.ORCA_CLI_BIN_DIR).toBeUndefined()
expect(env.ORCA_WSL_CLI_DIR).toBe('/guest/orca/bin')
})
it('leaves a packaged darwin/win32 PATH alone with no resources root', () => {
@@ -101,6 +110,9 @@ describe('prependOrcaCliDirToChildPath', () => {
platform
})
expect(env.PATH).toBe(`${join(USER_DATA, 'cli', 'bin')}${pathDelimiter}/usr/bin`)
expect(env.ORCA_CLI_BIN_DIR).toBe(
platform === 'win32' ? undefined : join(USER_DATA, 'cli', 'bin')
)
expect(shim.ensureLinuxTerminalOrcaCliShimDir).not.toHaveBeenCalled()
})
+8
View File
@@ -39,12 +39,16 @@ export function prependOrcaCliDirToChildPath(
opts: OrcaCliChildPathOptions
): string | null {
const platform = opts.platform ?? process.platform
delete env.ORCA_CLI_BIN_DIR
// Why: matches node:path's `delimiter` for the running platform, but stays correct when a test
// drives a foreign platform through the seam.
const pathDelimiter = platform === 'win32' ? ';' : delimiter
// Why: dev mode needs the launcher PATH override so `orca` resolves to the dev build instead of the production binary at /usr/local/bin/orca.
if (!opts.isPackaged) {
const devCliBin = join(opts.userDataPath, 'cli', 'bin')
if (platform !== 'win32') {
env.ORCA_CLI_BIN_DIR = devCliBin
}
const inheritedPath = readInheritedPath(env, platform)
// Why: an empty PATH segment resolves as `.` in some shells (commands run from cwd); avoid a trailing delimiter.
env[resolvePathEnvKey(env, platform)] = inheritedPath
@@ -55,6 +59,7 @@ export function prependOrcaCliDirToChildPath(
// Why: bare-`orca` shim scoped to Orca PTYs — Linux CLI installs as `orca-ide` to avoid shadowing GNOME's /usr/bin/orca screen reader (stablyai/orca#7904).
const shimDir = ensureLinuxTerminalOrcaCliShimDir({ userDataPath: opts.userDataPath })
if (shimDir) {
env.ORCA_CLI_BIN_DIR = shimDir
const inheritedEntries = readInheritedPath(env, platform)
.split(pathDelimiter)
.filter((entry) => entry.length > 0 && entry !== shimDir)
@@ -64,6 +69,9 @@ export function prependOrcaCliDirToChildPath(
} else if (opts.resourcesPath && (platform === 'darwin' || platform === 'win32')) {
// Why: global CLI registration is optional, but agents in Orca-managed PTYs must always reach this app's bundled CLI.
const bundledCliBin = join(opts.resourcesPath, 'bin')
if (platform === 'darwin') {
env.ORCA_CLI_BIN_DIR = bundledCliBin
}
const inheritedPath = readInheritedPath(env, platform)
env[resolvePathEnvKey(env, platform)] = inheritedPath
? `${bundledCliBin}${pathDelimiter}${inheritedPath}`
@@ -10,6 +10,7 @@ import {
} from '../runtime/structured-worker-identity'
const DEV_CLI_BIN_FIRST = /^[^:;]*[\\/]cli[\\/]bin[:;]/
const DEV_CLI_BIN_DIR = /^[^:;]*[\\/]cli[\\/]bin$/
// The dev launcher by absolute path: a login shell's profile cannot reorder it behind a global.
const DEV_CLI_LAUNCHER = /^[^:;]*[\\/]cli[\\/]bin[\\/]orca-dev$/
@@ -23,7 +24,11 @@ describe('buildCodexStructuredChildEnvironment', () => {
cwd: '/worktree',
codexHome: '/pinned/home',
resumeThreadId: null,
env: { EXAMPLE_GATEWAY_TOKEN: 'shell-exported', CODEX_HOME: '/shell/home' }
env: {
EXAMPLE_GATEWAY_TOKEN: 'shell-exported',
CODEX_HOME: '/shell/home',
ORCA_CLI_BIN_DIR: '/inherited/unowned-cli'
}
},
'spawn-token',
'session-not-a-worker'
@@ -35,6 +40,9 @@ describe('buildCodexStructuredChildEnvironment', () => {
ORCA_AGENT_SESSION_ID: 'session-not-a-worker',
ORCA_STRUCTURED_SESSION: '1',
ORCA_CLI_COMMAND: expect.stringMatching(DEV_CLI_LAUNCHER),
...(process.platform !== 'win32'
? { ORCA_CLI_BIN_DIR: expect.stringMatching(DEV_CLI_BIN_DIR) }
: {}),
ORCA_USER_DATA_PATH: expect.any(String),
// The test host is unpackaged, so this app's CLI is the dev launcher dir, first on PATH.
PATH: expect.stringMatching(DEV_CLI_BIN_FIRST)
@@ -57,6 +65,9 @@ describe('buildCodexStructuredChildEnvironment', () => {
ORCA_AGENT_SESSION_ID: sessionId,
ORCA_STRUCTURED_SESSION: '1',
ORCA_CLI_COMMAND: expect.stringMatching(DEV_CLI_LAUNCHER),
...(process.platform !== 'win32'
? { ORCA_CLI_BIN_DIR: expect.stringMatching(DEV_CLI_BIN_DIR) }
: {}),
ORCA_USER_DATA_PATH: expect.any(String),
PATH: expect.stringMatching(DEV_CLI_BIN_FIRST)
})
@@ -97,6 +108,7 @@ const ENV_REPORTING_APP_SERVER = String.raw`
result: {
sessionId: process.env.ORCA_AGENT_SESSION_ID ?? null,
cliCommand: process.env.ORCA_CLI_COMMAND ?? null,
cliBinDir: process.env.ORCA_CLI_BIN_DIR ?? null,
path: process.env.PATH ?? process.env.Path ?? null
}
})
@@ -135,6 +147,7 @@ describe('the spawned Codex child', () => {
await expect(connection.request('test/env')).resolves.toEqual({
sessionId,
cliCommand: expect.stringMatching(DEV_CLI_LAUNCHER),
cliBinDir: process.platform === 'win32' ? null : expect.stringMatching(DEV_CLI_BIN_DIR),
path: expect.stringMatching(DEV_CLI_BIN_FIRST)
})
} finally {
@@ -38,6 +38,9 @@ describe('CodexStructuredSessionAdapter.acquire', () => {
ORCA_AGENT_SESSION_ID: 'session-1',
ORCA_STRUCTURED_SESSION: '1',
ORCA_CLI_COMMAND: expect.stringMatching(/^[^:;]*[\\/]cli[\\/]bin[\\/]orca-dev$/),
...(process.platform !== 'win32'
? { ORCA_CLI_BIN_DIR: expect.stringMatching(/^[^:;]*[\\/]cli[\\/]bin$/) }
: {}),
ORCA_USER_DATA_PATH: expect.any(String),
// The test host is unpackaged, so this app's CLI is the dev launcher dir, first on PATH.
PATH: expect.stringMatching(/^[^:;]*[\\/]cli[\\/]bin[:;]/)
@@ -1,4 +1,5 @@
import { getPosixOmpShellWrapper } from '../pty/omp-shell-wrapper'
import { ORCA_CLI_POSIX_PATH_RESTORE } from '../../shared/orca-cli-shell-path'
import { MANAGED_DATA_ACCOUNT_POSIX_RESTORE } from '../../shared/managed-data-account-shell'
import { getPosixCodexShellLaunchPreflight } from '../../shared/codex-shell-function'
import { BASH_PROMPT_COMMAND_COMPOSITION_BLOCK } from '../bash-prompt-command-composition'
@@ -35,6 +36,7 @@ __orca_restore_agent_teams_path() {
export PATH="\${ORCA_AGENT_TEAMS_SHIM_DIR}:$PATH"
}
__orca_restore_agent_teams_path
${ORCA_CLI_POSIX_PATH_RESTORE}
# Why: user startup files may set the default OpenCode config after Orca's
# spawn env; restore the Orca-managed config dir before the first prompt.
[[ -n "\${ORCA_OPENCODE_CONFIG_DIR:-}" ]] && export OPENCODE_CONFIG_DIR="\${ORCA_OPENCODE_CONFIG_DIR}"
@@ -2,8 +2,11 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { spawnSync } from 'node:child_process'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
import { chmodSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
import type * as DaemonBashRcfileModule from './daemon-bash-shell-ready-rcfile'
import { getBashShellReadyRcfileContent } from '../providers/local-pty-shell-ready-bash-rcfile'
import { getDaemonBashShellReadyRcfileContent } from './daemon-bash-shell-ready-rcfile'
import { prependOrcaCliDirToChildPath } from '../cli/orca-cli-child-path'
import {
OVERLAY_ONLY_FEATURES,
STARTUP_COMMAND_FEATURES
@@ -57,6 +60,45 @@ describePosix('daemon shell-ready bash wrapper', () => {
vi.restoreAllMocks()
})
itWithBash.each([
['daemon', getDaemonBashShellReadyRcfileContent],
['local', getBashShellReadyRcfileContent]
] as const)('keeps this app CLI first after %s Bash profiles reset PATH', (_lane, content) => {
const cliBin = join(userDataPath, 'cli', 'bin')
const ambientBin = join(userDataPath, 'ambient-bin')
mkdirSync(cliBin, { recursive: true })
mkdirSync(ambientBin)
for (const bin of [cliBin, ambientBin]) {
const launcher = join(bin, 'orca-dev')
writeFileSync(launcher, '#!/bin/sh\nexit 0\n')
chmodSync(launcher, 0o755)
}
const env: Record<string, string> = {
HOME: userDataPath,
USERPROFILE: userDataPath,
PATH: `${ambientBin}:/usr/bin:/bin`,
ORCA_BACKGROUND_LAUNCH: '1'
}
const expectedLauncher = prependOrcaCliDirToChildPath(env, {
isPackaged: false,
userDataPath
})
writeFileSync(
join(userDataPath, '.bash_profile'),
'export PATH="$HOME/ambient-bin:/usr/bin:/bin:$HOME/cli/bin"\n'
)
const rcfile = join(userDataPath, 'cli-path-rcfile')
writeFileSync(rcfile, content())
const result = spawnSync('bash', ['-c', '. "$1"; command -v orca-dev', 'bash', rcfile], {
env,
encoding: 'utf8',
timeout: 5000
})
expect(result.error).toBeUndefined()
expect(result.status).toBe(0)
expect(result.stdout.split('\x1b]133;C\x07').join('').trim()).toBe(expectedLauncher)
})
// Why: regression guard for issue #2422 — bash wrapper must emit OSC 133 C/D so SSH sessions clear stale 'working' agent rows.
it('emits OSC 133 C/D markers in the daemon bash wrapper', async () => {
const { getShellReadyLaunchConfig } = await importFreshShellReady()
@@ -11,6 +11,7 @@ import { wslHookRelayManager } from '../agent-hooks/wsl-hook-relay-manager'
import { registerPtyHandlers, buildPtyHostEnv, clearProviderPtyState } from './pty'
import { buildJcodeRuntimeDir, shouldInjectJcodeRuntimeDir } from '../../shared/jcode-runtime-dir'
import { makePaneKey } from '../../shared/stable-pane-id'
import { selectShellStartupFeatures } from '../shell-startup-features'
vi.mock('electron', () => import('./pty-ipc-mock-registry').then((m) => m.electronModuleMock()))
vi.mock('fs', () => import('./pty-ipc-mock-registry').then((m) => m.fsModuleMock()))
@@ -60,6 +61,41 @@ describe('registerPtyHandlers', () => {
const { handlers, mainWindow, spawnAndGetEnv, withBundledCli } = setupPtyIpcSuite()
describe('spawn environment', () => {
it.each(['/bin/bash', '/bin/zsh'])(
'does not wrap a bare %s pane merely to expose this app CLI',
(shellPath) => {
const originalPlatform = process.platform
Object.defineProperty(process, 'platform', { configurable: true, value: 'darwin' })
try {
const env = buildPtyHostEnv(
'bare-cli-pane',
{},
{
isPackaged: false,
userDataPath: '/tmp/orca-user-data',
selectedCodexHomePath: null,
agentStatusHooksEnabled: false
}
)
expect(env.ORCA_CLI_BIN_DIR).toBe('/tmp/orca-user-data/cli/bin')
expect(
selectShellStartupFeatures({
shellPath,
env,
hasStartupCommand: false,
waitsForShellReady: false,
emitsStartupIdentity: false
})
).toEqual([])
} finally {
Object.defineProperty(process, 'platform', {
configurable: true,
value: originalPlatform
})
}
}
)
it('does not install managed Pi extensions when Pi is disabled', () => {
piBuildPtyEnvMock.mockClear()
@@ -5,6 +5,7 @@
* startup-file chain, OSC 133 hooks, and the shell-ready marker all live here.
*/
import { BASH_PROMPT_COMMAND_COMPOSITION_BLOCK } from '../bash-prompt-command-composition'
import { ORCA_CLI_POSIX_PATH_RESTORE } from '../../shared/orca-cli-shell-path'
import { MANAGED_DATA_ACCOUNT_POSIX_RESTORE } from '../../shared/managed-data-account-shell'
import { WSL_MANAGED_CLI_PATH_RESTORE } from '../wsl-managed-cli-path-restore'
import { getPosixOmpShellWrapper } from '../pty/omp-shell-wrapper'
@@ -47,6 +48,7 @@ __orca_restore_agent_teams_path() {
export PATH="\${ORCA_AGENT_TEAMS_SHIM_DIR}:$PATH"
}
__orca_restore_agent_teams_path
${ORCA_CLI_POSIX_PATH_RESTORE}
${WSL_MANAGED_CLI_PATH_RESTORE}
# Why: user startup files may set the default OpenCode config after Orca's
# spawn env; restore the Orca-managed config dir before the first prompt.
@@ -460,6 +460,7 @@ describe('spawn', () => {
PATH: '/home/user/.orca-relay/bin:/usr/bin',
ORCA_TERMINAL_HANDLE: 'term_ssh',
[POWERLEVEL10K_WIZARD_DISABLE_ENV]: 'true',
ORCA_CLI_BIN_DIR: '/home/user/.orca-relay/bin',
ORCA_REMOTE_CLI_BIN_DIR: '/home/user/.orca-relay/bin',
ORCA_RELAY_DIR: '/home/user/.orca-relay/relay-v1',
ORCA_RELAY_NODE_PATH: '/usr/bin/node',
@@ -490,6 +491,7 @@ describe('spawn', () => {
env: {
ORCA_TERMINAL_HANDLE: 'term_ssh',
[POWERLEVEL10K_WIZARD_DISABLE_ENV]: 'true',
ORCA_CLI_BIN_DIR: '/home/user/.orca-relay/bin',
ORCA_REMOTE_CLI_BIN_DIR: '/home/user/.orca-relay/bin',
ORCA_RELAY_DIR: '/home/user/.orca-relay/relay-v1',
ORCA_RELAY_NODE_PATH: '/usr/bin/node',
@@ -0,0 +1,43 @@
import { describe, expect, it } from 'vitest'
import { buildSshPtySpawnEnv } from './ssh-pty-spawn-env'
import type { RemoteCliBridgeEnv } from './ssh-pty-provider-contract'
const bridge: RemoteCliBridgeEnv = {
binDir: '/remote/orca/bin',
relayDir: '/remote/orca',
nodePath: '/remote/node',
sockPath: '/remote/orca/socket'
}
describe('SSH CLI path ownership', () => {
it('replaces the client restore directory with the remote bridge', () => {
const env = { PATH: '/usr/bin', ORCA_CLI_BIN_DIR: '/client/orca/bin' }
const result = buildSshPtySpawnEnv({ env, remoteCliBridgeEnv: bridge })
expect(result.ORCA_CLI_BIN_DIR).toBe(bridge.binDir)
expect(result.PATH).toBe(`${bridge.binDir}:/usr/bin`)
expect(env.ORCA_CLI_BIN_DIR).toBe('/client/orca/bin')
})
it('clears a client path when no remote bridge is available', () => {
const result = buildSshPtySpawnEnv({ env: { ORCA_CLI_BIN_DIR: '/client/orca/bin' } })
expect(result.ORCA_CLI_BIN_DIR).toBeUndefined()
})
it('does not give a POSIX wrapper a Windows bridge directory', () => {
const result = buildSshPtySpawnEnv({
env: { Path: 'C:\\Windows', ORCA_CLI_BIN_DIR: '/client/orca/bin' },
remoteCliBridgeEnv: { ...bridge, binDir: 'C:\\Orca\\bin', pathDelimiter: ';' }
})
expect(result.ORCA_CLI_BIN_DIR).toBeUndefined()
expect(result.Path).toBe('C:\\Orca\\bin;C:\\Windows')
})
it('preserves an explicitly deleted restore key', () => {
const result = buildSshPtySpawnEnv({
env: { PATH: '/usr/bin' },
remoteCliBridgeEnv: bridge,
envToDelete: ['ORCA_CLI_BIN_DIR']
})
expect(result.ORCA_CLI_BIN_DIR).toBeUndefined()
})
})
+5
View File
@@ -7,8 +7,13 @@ export function buildSshPtySpawnEnv(args: {
remoteCliBridgeEnv?: RemoteCliBridgeEnv
}): Record<string, string> {
const merged = { ...args.env }
// The client CLI path cannot be restored on the execution host.
delete merged.ORCA_CLI_BIN_DIR
if (args.remoteCliBridgeEnv) {
const pathDelimiter = args.remoteCliBridgeEnv.pathDelimiter ?? ':'
if (pathDelimiter === ':') {
merged.ORCA_CLI_BIN_DIR = args.remoteCliBridgeEnv.binDir
}
const pathKey = merged.PATH !== undefined ? 'PATH' : merged.Path !== undefined ? 'Path' : null
if (pathKey) {
const pathValue = merged[pathKey] ?? ''
@@ -70,6 +70,7 @@ describe('structuredSessionChildIdentityEnv', () => {
// For a CLI that predates the id, which refuses on it instead of guessing a sibling.
ORCA_STRUCTURED_SESSION: '1',
ORCA_CLI_COMMAND: join(SHIM_DIR, 'orca'),
ORCA_CLI_BIN_DIR: SHIM_DIR,
// The instance that minted the id, so any current CLI dials it rather than the default.
ORCA_USER_DATA_PATH: USER_DATA
})
@@ -131,6 +132,7 @@ describe('structuredSessionChildIdentityEnv', () => {
expect(env.PATH).toBeUndefined()
// The native launcher: `orca.cmd` refuses message bodies cmd.exe would mangle.
expect(env.ORCA_CLI_COMMAND).toBe(join(RESOURCES, 'bin', 'orca.exe'))
expect(env.ORCA_CLI_BIN_DIR).toBeUndefined()
})
it('unpackaged, through the dev launcher dir', () => {
@@ -17,6 +17,7 @@ import { join } from 'node:path'
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
import { POSIX_SHELL_STARTUP_COMMAND_ENV } from './pty/posix-shell-startup-command'
import { selectShellStartupFeatures } from './shell-startup-features'
import { prependOrcaCliDirToChildPath } from './cli/orca-cli-child-path'
import { runZshPty } from './zsh-startup-hook-pty-harness'
import { ZSH_WRAPPER_DIR_MARKER_FILE } from './shell-templates'
import {
@@ -119,6 +120,46 @@ describePosix('zsh launch config', () => {
rmSync(userDataPath, { recursive: true, force: true })
})
itWithZsh.each([false, true])(
'restores this app CLI after zsh startup resets PATH and replaces prompt hooks %s',
async (replacePromptHooks) => {
const cliBin = join(userDataPath, 'cli', 'bin')
const ambientBin = join(userDataPath, 'ambient-bin')
mkdirSync(cliBin, { recursive: true })
mkdirSync(ambientBin)
for (const bin of [cliBin, ambientBin]) {
const launcher = join(bin, 'orca-dev')
writeFileSync(launcher, '#!/bin/sh\nexit 0\n')
chmodSync(launcher, 0o755)
}
writeFileSync(
join(userDataPath, '.zshrc'),
`export PATH="$HOME/ambient-bin:/usr/bin:/bin:$HOME/cli/bin"\n${replacePromptHooks ? 'precmd_functions=()\n' : ''}`
)
const env: Record<string, string> = {
...process.env,
HOME: userDataPath,
USERPROFILE: userDataPath,
PATH: `${ambientBin}:/usr/bin:/bin`
}
const launcher = prependOrcaCliDirToChildPath(env, { isPackaged: false, userDataPath })
const features = selectShellStartupFeatures({
shellPath: ZSH_PATH,
env,
...PLAIN_PANE,
hasStartupCommand: true
})
const { getShellLaunchConfig } = await importFreshLocalPtyShellReady()
const config = getShellLaunchConfig(ZSH_PATH, features)
const result = await runZshPty({
env: { ...env, ...config.env },
commands: ['ORCA_LOOKUP=$(command -v orca-dev)'],
report: ['ORCA_LOOKUP']
})
expect(result.values.ORCA_LOOKUP).toBe(launcher)
}
)
it('publishes exactly the selected features, whatever process.env holds', async () => {
process.env.ORCA_SHELL_FEATURES = 'overlay,markers,ready,identity'
const { getShellLaunchConfig } = await importFreshLocalPtyShellReady()
+5 -2
View File
@@ -102,8 +102,11 @@ export const ZSH_USER_ZSHENV_SOURCE_BLOCK = `{
[[ ! -r "$_orca_user_zshenv" ]] || builtin source -- "$_orca_user_zshenv"
} always {
builtin unset _orca_user_zshenv
builtin typeset -ag precmd_functions
(( \${precmd_functions[(Ie)__orca_deferred_init]} )) || precmd_functions+=(__orca_deferred_init)
if (( ! $+_orca_deferred_init_done )); then
builtin typeset -ag precmd_functions
(( \${precmd_functions[(Ie)__orca_deferred_init]} )) || precmd_functions+=(__orca_deferred_init)
__orca_arm_deferred_line_init
fi
}`
// Why: daemon, local, and relay wrappers must preserve one Bash prompt-hook contract.
@@ -0,0 +1,281 @@
import { chmodSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, describe, expect, it } from 'vitest'
import { prependOrcaCliDirToChildPath } from './cli/orca-cli-child-path'
import { POSIX_SHELL_STARTUP_COMMAND_ENV } from './pty/posix-shell-startup-command'
import { getZshShellReadyWrapperFile } from './providers/local-pty-shell-ready-wrapper-generation'
import { encodeShellStartupFeatures, selectShellStartupFeatures } from './shell-startup-features'
import { ZSH_WRAPPER_DIR_MARKER_FILE } from './shell-templates'
import { hasZsh, MARKERS, runZshPty, ZSH_PATH } from './zsh-startup-hook-pty-harness'
const itWithZsh = hasZsh ? it : it.skip
const USER_WIDGET = `orca_test_line_init() {
O_UC=$((\${O_UC:-0}+1))
O_UN="$WIDGET"
builtin printf 'ORCA_TEST_USER_WIDGET_CALL\\n'
return 1
}
zle -N zle-line-init orca_test_line_init
`
const USER_REDRAW = `orca_test_redraw() {
O_RC=$((\${O_RC:-0}+1))
O_RN="$WIDGET"
return 1
}
zle -N zle-line-pre-redraw orca_test_redraw
`
const USER_PRECMD = `precmd() {
O_PCALLS=$((\${O_PCALLS:-0}+1))
O_PN="$0"
O_PO="\${O_PO:-\${options[ksharrays]}:\${options[nounset]}}"
return 1
}
`
describe('zsh deferred startup after prompt-hook replacement', () => {
const roots: string[] = []
afterEach(() => {
for (const root of roots.splice(0)) {
rmSync(root, { recursive: true, force: true })
}
})
itWithZsh.each([
'history',
'startup',
'chained-startup',
'stock-history',
'stock-startup',
'stock-chained-redraw',
'stock-nonzero-precmd',
'ordered-startup',
'replaced-precmd-startup',
'status-startup',
'sticky-startup',
'scheduled-startup',
'unavailable-startup',
'repeat-history',
'repeat-startup'
] as const)(
'restores CLI precedence and preserves the user widget in a %s pane',
async (intent) => {
const historyOnly = intent.endsWith('history')
const repeatSource = intent.startsWith('repeat-')
const stockBinding = intent.startsWith('stock-')
const chainedLineInit = intent === 'chained-startup'
const chainedRedraw = intent === 'stock-chained-redraw'
const nonzeroPrecmd = intent === 'stock-nonzero-precmd'
const orderedPrecmd = intent === 'ordered-startup'
const replacedPrecmd = intent === 'replaced-precmd-startup'
const statusPrecmd = intent === 'status-startup'
const stickyPrecmd = intent === 'sticky-startup'
const scheduledPrecmd = intent === 'scheduled-startup'
const unavailableSched = intent === 'unavailable-startup'
const scheduleCleanup = scheduledPrecmd || unavailableSched || orderedPrecmd
const home = mkdtempSync(join(tmpdir(), 'orca-deferred-line-init-'))
roots.push(home)
const cliBin = join(home, 'cli', 'bin')
const ambientBin = join(home, 'ambient-bin')
const wrapperDir = join(home, 'wrapper')
for (const bin of [cliBin, ambientBin, wrapperDir]) {
mkdirSync(bin, { recursive: true })
}
for (const bin of [cliBin, ambientBin]) {
writeFileSync(join(bin, 'orca-dev'), '#!/bin/sh\nexit 0\n')
chmodSync(join(bin, 'orca-dev'), 0o755)
}
writeFileSync(
join(home, '.zshenv'),
(chainedLineInit || chainedRedraw || repeatSource
? ''
: stockBinding
? USER_REDRAW
: USER_WIDGET) +
(statusPrecmd
? 'precmd() { O_FIRST_IN=${O_FIRST_IN:-$?}; }\n'
: stickyPrecmd
? `emulate sh -c 'precmd() { O_FIRST_IN="\${O_FIRST_IN:-$?:\${options[shwordsplit]}:\${options[ksharrays]}}"; return 1; }'\n`
: scheduledPrecmd
? 'zmodload zsh/sched\nO_EVENT() { O_EO=${_orca_deferred_init_done:-0}; }\nsched +0 O_EVENT\nsched +3600 O_EVENT\n'
: unavailableSched
? 'zmodload zsh/zleparameter zsh/terminfo\nO_MP=("${module_path[@]}"); module_path=()\n'
: nonzeroPrecmd || replacedPrecmd
? USER_PRECMD
: orderedPrecmd
? USER_PRECMD.replace('return 1', 'return 0')
: '')
)
if (statusPrecmd || stickyPrecmd) {
writeFileSync(
join(home, '.zlogin'),
'orca_test_status() { return 42; }; orca_test_status\n'
)
}
// Replay Ubuntu's later widget binding before the user's own startup changes.
const stockWidget =
stockBinding || stickyPrecmd || scheduledPrecmd
? USER_WIDGET.replaceAll('orca_test_line_init', 'zle-line-init')
: ''
writeFileSync(
join(home, '.zshrc'),
`${statusPrecmd || stickyPrecmd ? 'PS1="ORCA_FIRST_PROMPT:%? "\n' : ''}${repeatSource ? USER_WIDGET : ''}${unavailableSched ? 'module_path=("${O_MP[@]}")\n' : ''}${stockWidget}export PATH="$HOME/ambient-bin:/usr/bin:/bin:$HOME/cli/bin"\n${orderedPrecmd ? '' : 'precmd_functions=()\n'}${
chainedLineInit
? `${USER_WIDGET.replace('zle -N zle-line-init orca_test_line_init', 'zle -N orca_test_line_init')}autoload -Uz add-zle-hook-widget\nadd-zle-hook-widget line-init orca_test_line_init\n`
: chainedRedraw
? `${USER_REDRAW.replace('zle -N zle-line-pre-redraw orca_test_redraw', 'zle -N orca_test_redraw')}autoload -Uz add-zle-hook-widget\nadd-zle-hook-widget line-pre-redraw orca_test_redraw\n`
: ''
}${nonzeroPrecmd ? 'orca_test_array() { O_AC=called; }\nprecmd_functions=(orca_test_array)\nsetopt KSH_ARRAYS NO_UNSET\n' : orderedPrecmd ? 'orca_test_array() { O_AO=${O_AO:-${_orca_deferred_init_done:-0}}; }\nprecmd_functions=(orca_test_array "${precmd_functions[@]}")\n' : replacedPrecmd ? 'precmd() { O_NPC=$((${O_NPC:-0}+1)); O_NPN="$0"; }\n' : ''}`
)
writeFileSync(join(wrapperDir, '.zshenv'), getZshShellReadyWrapperFile())
writeFileSync(join(wrapperDir, ZSH_WRAPPER_DIR_MARKER_FILE), '')
const env: Record<string, string> = {
...process.env,
HOME: home,
USERPROFILE: home,
PATH: `${ambientBin}:/usr/bin:/bin`,
// Stock cases replay this replacement after the fixture installs its own widgets.
DEBIAN_PREVENT_KEYBOARD_CHANGES: '1',
ZDOTDIR: wrapperDir,
ORCA_HISTFILE: join(home, 'scoped-history')
}
const launcher = prependOrcaCliDirToChildPath(env, { isPackaged: false, userDataPath: home })
const features = selectShellStartupFeatures({
shellPath: ZSH_PATH,
env,
hasStartupCommand: !historyOnly,
waitsForShellReady: !historyOnly,
emitsStartupIdentity: false
})
env.ORCA_SHELL_FEATURES = encodeShellStartupFeatures(features)
if (!historyOnly) {
env[POSIX_SHELL_STARTUP_COMMAND_ENV] = 'O_SU=$((${O_SU:-0}+1))'
}
if (statusPrecmd || stickyPrecmd) {
const baseline = await runZshPty({ env: { ...env, ZDOTDIR: home }, report: ['O_FIRST_IN'] })
expect(baseline.values.O_FIRST_IN).toBe(stickyPrecmd ? '42:on:on' : '42')
}
const result = await runZshPty({
env,
commands: [
...(repeatSource
? ['source -- "$HOME/wrapper/.zshenv"', 'source -- "$HOME/wrapper/.zshenv"']
: []),
'O_LK=$(command -v orca-dev)',
'O_IR=${+functions[__orca_deferred_line_init]}',
'O_SR=${+widgets[__orca_saved_line_init]}',
...(scheduleCleanup
? [
'O_SC=${+functions[__orca_deferred_sched_init]}',
'O_SE=${zsh_scheduled_events[*]:-UNSET}'
]
: []),
...(scheduledPrecmd ? ['O_EV=${#zsh_scheduled_events}'] : []),
...(stockBinding ? ['O_RW=${widgets[zle-line-pre-redraw]:-none}'] : []),
'O_LI=${widgets[zle-line-init]:-none}',
'O_PC="${precmd_functions[*]}"',
...(nonzeroPrecmd ? ['precmd; O_PS=$?'] : [])
],
report: [
'O_LK',
'O_UC',
'O_UN',
'O_IR',
'O_SR',
...(scheduleCleanup ? ['O_SC', 'O_SE'] : []),
...(scheduledPrecmd ? ['O_EV', 'O_EO'] : []),
...(stockBinding ? ['O_RW', 'O_RC', 'O_RN'] : []),
...(nonzeroPrecmd ? ['O_PCALLS', 'O_PN', 'O_PO', 'O_AC', 'O_PS'] : []),
...(orderedPrecmd ? ['O_PCALLS', 'O_PN', 'O_AO'] : []),
...(replacedPrecmd ? ['O_PCALLS', 'O_NPC', 'O_NPN'] : []),
...(statusPrecmd || stickyPrecmd ? ['O_FIRST_IN'] : []),
'O_LI',
'O_PC',
'O_SU',
'HISTFILE'
]
})
expect(result.values.O_LK).toBe(launcher)
expect(Number(result.values.O_UC)).toBeGreaterThan(0)
if (!chainedLineInit) {
expect(result.values.O_UN).toBe('zle-line-init')
}
if (!chainedLineInit && !chainedRedraw && !repeatSource) {
expect(result.values.O_IR).toBe('0')
}
expect(result.values.O_SR).toBe('0')
if (repeatSource) {
expect(result.output).not.toContain('job table full or recursion limit exceeded')
expect(result.values.O_PC).not.toContain('__orca_deferred_init')
}
if (scheduleCleanup) {
expect(result.values.O_SC).toBe('0')
expect(result.values.O_SE).not.toContain('orca')
}
if (unavailableSched) {
// Stock completion modules can fail before the fixture restores module_path.
expect(result.output).not.toContain('zsh/sched')
expect(result.output).not.toContain('__orca_arm_deferred_line_init:')
expect(result.values.O_SE).toBe('UNSET')
}
if (scheduledPrecmd) {
expect(result.values.O_EV).toBe('1')
expect(result.values.O_EO).toBe('0')
expect(result.values.O_SE).toContain('O_EVENT')
}
if (stockBinding) {
expect(Number(result.values.O_RC)).toBeGreaterThan(0)
expect(result.values.O_RN).toBe(chainedRedraw ? 'orca_test_redraw' : 'zle-line-pre-redraw')
if (!chainedRedraw) {
expect(result.values.O_RW).toBe('user:orca_test_redraw')
}
}
expect(result.values.HISTFILE).toBe(join(home, 'scoped-history'))
if (nonzeroPrecmd) {
expect(Number(result.values.O_PCALLS)).toBeGreaterThan(0)
expect(result.values.O_PN).toBe('precmd')
expect(result.values.O_PO).toBe('on:on')
expect(result.values.O_AC).toBe('called')
expect(result.values.O_PS).toBe('1')
}
if (orderedPrecmd) {
expect(Number(result.values.O_PCALLS)).toBeGreaterThan(0)
expect(result.values.O_PN).toBe('precmd')
expect(result.values.O_AO).toBe('0')
}
if (replacedPrecmd) {
expect(result.values.O_PCALLS).toBe('UNSET')
expect(Number(result.values.O_NPC)).toBeGreaterThan(0)
expect(result.values.O_NPN).toBe('precmd')
}
if (statusPrecmd || stickyPrecmd) {
expect(result.values.O_FIRST_IN).toBe(stickyPrecmd ? '42:on:on' : '42')
expect(result.output).toContain('ORCA_FIRST_PROMPT:42 ')
}
if (historyOnly) {
expect(result.output).not.toContain('\x1b]133;')
expect(result.values.O_LI).toBe(
stockBinding ? 'user:zle-line-init' : 'user:orca_test_line_init'
)
expect(result.values.O_PC).not.toContain('orca')
expect(result.values.O_SU).toBe('UNSET')
} else {
expect(result.output).toContain(MARKERS.ready)
expect(result.values.O_LI).toBe('user:__orca_prompt_mark')
expect(result.values.O_PC).toBe(
nonzeroPrecmd || orderedPrecmd
? 'orca_test_array __orca_osc133_precmd'
: '__orca_osc133_precmd'
)
expect(result.values.O_SU).toBe('1')
expect(result.output.split('ORCA_TEST_USER_WIDGET_CALL\r\n').length - 1).toBe(
result.output.split(MARKERS.ready).length - 1
)
}
}
)
})
@@ -0,0 +1,56 @@
// Why: stock zshrc can replace line-init after the user clears the prompt-hook array.
export const ZSH_DEFERRED_LINE_INIT_BLOCK = `__orca_deferred_line_init() {
builtin emulate -L zsh
(( \${+functions[__orca_deferred_init]} )) || return 0
local __orca_direct_line_init=0
[[ "\${widgets[zle-line-init]:-}" == user:__orca_deferred_line_init ]] && __orca_direct_line_init=1
__orca_deferred_init
if (( __orca_direct_line_init && \${+widgets[zle-line-init]} )); then
zle zle-line-init "$@"
elif [[ "\${widgets[zle-line-init]:-}" == user:__orca_prompt_mark ]]; then
local __orca_prev_line_init_fn=""
__orca_prompt_mark "$@"
fi
}
# Why: scheduled callbacks run after user prompt hooks without copying their function metadata.
__orca_deferred_sched_init() {
local __orca_prompt_status=$?
builtin emulate -L zsh
(( \${+functions[__orca_deferred_init]} )) && __orca_deferred_init
builtin unset __orca_deferred_sched_armed
builtin unfunction __orca_deferred_sched_init
return $__orca_prompt_status
}
__orca_arm_deferred_line_init() {
builtin emulate -L zsh
if [[ "\${widgets[zle-line-init]:-}" != user:__orca_deferred_line_init ]]; then
if (( \${+widgets[zle-line-init]} )); then
zle -A zle-line-init __orca_saved_line_init
fi
zle -N zle-line-init __orca_deferred_line_init
fi
if (( ! $+__orca_deferred_sched_armed )) && builtin zmodload -F zsh/sched b:sched 2>/dev/null; then
builtin sched +0 __orca_deferred_sched_init && builtin typeset -g __orca_deferred_sched_armed=1
fi
}`
// Why: restore the exact prior widget before the existing readiness hook captures it.
export const ZSH_DEFERRED_LINE_INIT_RETIRE_BLOCK = ` if (( \${+widgets[__orca_saved_line_init]} )); then
if [[ "\${widgets[zle-line-init]:-}" == user:__orca_deferred_line_init ]]; then
zle -A __orca_saved_line_init zle-line-init
fi
zle -D __orca_saved_line_init
elif [[ "\${widgets[zle-line-init]:-}" == user:__orca_deferred_line_init ]]; then
zle -D zle-line-init
fi`
// Why: add-zle-hook-widget can keep an alias of the bootstrap in its own chain.
export const ZSH_DEFERRED_LINE_INIT_CLEANUP_BLOCK = ` (( $+__orca_deferred_sched_armed )) || builtin unfunction __orca_deferred_sched_init
local __orca_widget __orca_line_init_bound=0
for __orca_widget in "\${(v)widgets[@]}"; do
if [[ "$__orca_widget" == user:__orca_deferred_line_init ]]; then
__orca_line_init_bound=1
break
fi
done
(( __orca_line_init_bound )) || builtin unfunction __orca_deferred_line_init`
@@ -333,25 +333,14 @@ describe.skipIf(process.platform === 'win32')(
})
itWithZsh(
'degrades to an unwrapped pane, leaking nothing, when a config drops precmd_functions',
'still scopes history without leaking it when a config drops precmd_functions',
withHome({ ...USER_FILES, '.zshrc': 'precmd_functions=()\n' }, async (home) => {
const scoped = join(home, 'orca-history', 'zsh_history')
const { env, launch } = launchPane(home, scoped)
const report = ['HISTFILE', 'ORCA_HISTFILE', 'ZDOTDIR']
const { values } = await runZshPty({ env, report })
// Why compared against an unwrapped run rather than asserted to differ
// from the scoped path: whether the scoped value survives at all is the
// host's call, not Orca's. macOS /etc/zshrc overwrites HISTFILE, so it
// does not; a host with no such assignment keeps whatever the spawn env
// set. The contract on both is the same — this pane is the pane the user
// would have had unwrapped.
const unwrapped = await runZshPty({
env: { PATH: '/usr/bin:/bin', HOME: home, HISTFILE: scoped },
report
})
expect(values.HISTFILE).toBe(unwrapped.values.HISTFILE)
expect(values.HISTFILE).toBe(scoped)
expect(values.HISTFILE).not.toContain(launch.env.ZDOTDIR)
// ORCA_HISTFILE was consumed in .zshenv precisely so a dropped hook
// leaks nothing to the pane's children.
+14 -2
View File
@@ -1,3 +1,4 @@
import { ORCA_CLI_POSIX_PATH_RESTORE } from '../shared/orca-cli-shell-path'
import { MANAGED_DATA_ACCOUNT_POSIX_RESTORE } from '../shared/managed-data-account-shell'
/**
* The single `.zshenv` Orca writes for every transport: local PTY, daemon/SSH,
@@ -14,7 +15,8 @@ import { MANAGED_DATA_ACCOUNT_POSIX_RESTORE } from '../shared/managed-data-accou
* dir shared by two installed builds could mix files from both.
*
* This shape gives ZDOTDIR back before anything else can observe it, then defers
* Orca's work to a `precmd` hook that runs at the first prompt — after
* Orca's work to a `precmd` hook, with a one-shot line-editor fallback if the
* user's config replaces its hook array. Both run at the first prompt — after
* `.zprofile`, `/etc/zshrc`, `.zshrc` and `.zlogin`, all of which zsh now reads
* from the user's own directory exactly as in an unwrapped shell. #11044 becomes
* unreachable rather than repaired, and the emulation and mixed-build classes
@@ -30,6 +32,11 @@ import { MANAGED_DATA_ACCOUNT_POSIX_RESTORE } from '../shared/managed-data-accou
import { getPosixOmpShellWrapper } from './pty/omp-shell-wrapper'
import { WSL_MANAGED_CLI_PATH_RESTORE } from './wsl-managed-cli-path-restore'
import { getPosixCodexShellLaunchPreflight } from '../shared/codex-shell-function'
import {
ZSH_DEFERRED_LINE_INIT_BLOCK,
ZSH_DEFERRED_LINE_INIT_CLEANUP_BLOCK,
ZSH_DEFERRED_LINE_INIT_RETIRE_BLOCK
} from './zsh-deferred-startup-line-init'
import {
getZshShellReadyMarkerRegistrationBlock,
SHELL_STARTUP_IDENTITY_MARKER_BLOCK,
@@ -146,6 +153,7 @@ function buildDeferredInit(spec: ZshStartupHookSpec): string {
const permanentPrecmd = spec.osc133CommandMarkers
? ` if __orca_has_feature markers; then
precmd_functions=(\${precmd_functions:/__orca_deferred_init/__orca_osc133_precmd})
(( \${precmd_functions[(Ie)__orca_osc133_precmd]} )) || precmd_functions+=(__orca_osc133_precmd)
preexec_functions=(__orca_osc133_preexec \${preexec_functions[@]})
else
precmd_functions=(\${precmd_functions:#__orca_deferred_init})
@@ -170,9 +178,11 @@ ${indentBlock(getZshShellReadyMarkerRegistrationBlock(spec.readyMarkerEscaped, t
(( $+_orca_deferred_init_done )) && return 0
builtin typeset -g _orca_deferred_init_done=1
builtin typeset -g precmd_functions
${ZSH_DEFERRED_LINE_INIT_RETIRE_BLOCK}
${permanentPrecmd}
${joinBlocks([
spec.restores.managedWslCli ? indentBlock(WSL_MANAGED_CLI_PATH_RESTORE, ' ') : null,
indentBlock(ORCA_CLI_POSIX_PATH_RESTORE, ' ').replace(/\n$/, ''),
featureGuard('overlay', getOverlayRestoreBlocks(spec)),
// Why outside the overlay guard: a system-default Codex home carries no overlay key.
indentBlock(getPosixCodexShellLaunchPreflight(), ' ').replace(/\n$/, ''),
@@ -190,7 +200,8 @@ ${
__orca_has_feature markers && __orca_osc133_precmd\n`
: ''
} builtin unset _orca_shell_features _orca_histfile
builtin unfunction __orca_deferred_init __orca_has_feature
${ZSH_DEFERRED_LINE_INIT_CLEANUP_BLOCK}
builtin unfunction __orca_deferred_init __orca_has_feature __orca_arm_deferred_line_init
}`
}
@@ -201,6 +212,7 @@ export function buildZshStartupHook(spec: ZshStartupHookSpec): string {
ZSH_FEATURE_CHANNEL_BLOCK,
SHELL_STARTUP_IDENTITY_MARKER_BLOCK,
spec.osc133CommandMarkers ? ZSH_OSC133_FUNCTION_BLOCK : null,
ZSH_DEFERRED_LINE_INIT_BLOCK,
buildDeferredInit(spec),
ZSH_USER_ZSHENV_SOURCE_BLOCK
])}\n`
+2
View File
@@ -1,6 +1,7 @@
import { readFileSync, statSync } from 'node:fs'
import { join } from 'node:path'
import { getPosixOmpShellWrapper } from '../main/pty/omp-shell-wrapper'
import { ORCA_CLI_POSIX_PATH_RESTORE } from '../shared/orca-cli-shell-path'
import { getPosixCodexShellLaunchPreflight } from '../shared/codex-shell-function'
import {
BASH_FEATURE_CHANNEL_BLOCK,
@@ -73,6 +74,7 @@ fi
[[ -n "\${ORCA_OPENCODE_CONFIG_DIR:-}" ]] && export OPENCODE_CONFIG_DIR="\${ORCA_OPENCODE_CONFIG_DIR}"
[[ -n "\${ORCA_MIMOCODE_HOME:-}" ]] && export MIMOCODE_HOME="\${ORCA_MIMOCODE_HOME}"
[[ -n "\${ORCA_REMOTE_CLI_BIN_DIR:-}" ]] && case ":$PATH:" in *:"\${ORCA_REMOTE_CLI_BIN_DIR}":*) ;; *) export PATH="\${ORCA_REMOTE_CLI_BIN_DIR}:$PATH" ;; esac
${ORCA_CLI_POSIX_PATH_RESTORE}
${getPosixOmpShellWrapper()}
${getPosixCodexShellLaunchPreflight()}${BASH_HISTFILE_RESTORE_BLOCK}
# Why: SSH bash sessions need the same command lifecycle markers as local
+6
View File
@@ -0,0 +1,6 @@
export const ORCA_CLI_POSIX_PATH_RESTORE = `if [ -n "\${ORCA_CLI_BIN_DIR:-}" ]; then
case "\${PATH:-}" in
"$ORCA_CLI_BIN_DIR"|"$ORCA_CLI_BIN_DIR":*) ;;
*) export PATH="$ORCA_CLI_BIN_DIR\${PATH:+:$PATH}" ;;
esac
fi`