mirror of
https://github.com/stablyai/orca.git
synced 2026-10-07 00:02:29 +00:00
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:
co-authored by
Codex
Orca maintenance
Orca campaign
parent
c4e8735f45
commit
d9173ffbdb
@@ -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 \
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
})
|
||||
@@ -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()
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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`
|
||||
Reference in New Issue
Block a user