Files
orca/src/main/zsh-startup-wrapper-builder.ts
T
Neil 1f20a53d22 Fix duplicate Codex startup command echo
Deliver local POSIX Codex startup commands through the shell wrapper at shell initialization, preventing duplicate PTY echo.
2026-08-30 18:13:36 -07:00

203 lines
8.9 KiB
TypeScript

/**
* The single `.zshenv` Orca writes for every transport: local PTY, daemon/SSH,
* and relay.
*
* Orca needs to run code AFTER the user's own zsh startup files. The old shape
* bought that by keeping ZDOTDIR pointed at Orca's wrapper dir for the whole of
* startup and sourcing each user file by hand — four generated files, and a
* fake ZDOTDIR live while `/etc/zshrc` ran. That one decision was the root of a
* whole bug family: `/etc/zshrc` assigns `HISTFILE=${ZDOTDIR:-$HOME}/.zsh_history`
* unconditionally, so history landed inside Orca's own dir (#11044); zsh's
* `sourcehome()` ignores ZDOTDIR once the shell enters sh/ksh emulation, so a
* user file ending in `emulate sh` hid every later wrapper file; and one wrapper
* 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
* `.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
* stop existing.
*
* ORDER IS LOAD-BEARING: every function is defined ABOVE the `source` of the
* user's `.zshenv`. A user `.zshenv` ending in `emulate sh` puts the rest of
* this file under sh parsing rules, and zsh-only syntax then fails to parse,
* taking the whole wrapper with it. Function bodies are parsed at definition
* time, so defining them first makes them immune; `emulate -L zsh` inside the
* hook restores zsh option semantics for the body at call time.
*/
import { getPosixOmpShellWrapper } from './pty/omp-shell-wrapper'
import { getPosixCodexShellLaunchPreflight } from './pty/codex-shell-launch-preflight'
import {
getZshShellReadyMarkerRegistrationBlock,
SHELL_STARTUP_IDENTITY_MARKER_BLOCK,
ZSH_FEATURE_CHANNEL_BLOCK,
ZSH_USER_ZSHENV_SOURCE_BLOCK,
ZSH_ZDOTDIR_HANDBACK_BLOCK
} from './shell-templates'
/** Runtime values the hook re-exports after the user's own startup files ran. */
export type ZshWrapperRestoreSpec = {
/** Orca's agent-teams shim dir back onto PATH. */
agentTeamsPath: boolean
/** Remote CLI bin dir onto PATH — relay hosts only. */
remoteCliBinDir: boolean
/** Orca's runtime CODEX_HOME. */
codexHome: boolean
/** The `codex()` wrapper that runs Orca's launch preflight. */
codexLaunchPreflight: boolean
}
export type ZshStartupHookSpec = {
/** First line of the generated file, e.g. `# Orca zsh shell-ready wrapper`. */
headerLabel: string
readyMarkerEscaped: string
/** OSC 133 command-lifecycle hooks (behind the `markers` feature). */
osc133CommandMarkers: boolean
/** Local-only command delivery from the first zle line editor. */
startupCommandDelivery: boolean
/** Comment heading the overlay restores inside the hook. */
overlayRestoreComment: string
restores: ZshWrapperRestoreSpec
}
const AGENT_TEAMS_PATH_RESTORE_BLOCK = `__orca_restore_agent_teams_path() {
[[ -n "\${ORCA_AGENT_TEAMS_SHIM_DIR:-}" ]] || return 0
case "$PATH" in
"\${ORCA_AGENT_TEAMS_SHIM_DIR}"|"\${ORCA_AGENT_TEAMS_SHIM_DIR}:"*) return 0 ;;
esac
export PATH="\${ORCA_AGENT_TEAMS_SHIM_DIR}:$PATH"
}
__orca_restore_agent_teams_path`
const OPENCODE_CONFIG_DIR_RESTORE = `[[ -n "\${ORCA_OPENCODE_CONFIG_DIR:-}" ]] && export OPENCODE_CONFIG_DIR="\${ORCA_OPENCODE_CONFIG_DIR}"`
const MIMOCODE_HOME_RESTORE = `[[ -n "\${ORCA_MIMOCODE_HOME:-}" ]] && export MIMOCODE_HOME="\${ORCA_MIMOCODE_HOME}"`
const REMOTE_CLI_BIN_DIR_RESTORE = `[[ -n "\${ORCA_REMOTE_CLI_BIN_DIR:-}" ]] && case ":$PATH:" in *:"\${ORCA_REMOTE_CLI_BIN_DIR}":*) ;; *) export PATH="\${ORCA_REMOTE_CLI_BIN_DIR}:$PATH" ;; esac`
const CODEX_HOME_RESTORE = `# Why: Codex must keep using Orca's runtime CODEX_HOME after rc files.
[[ -n "\${ORCA_CODEX_HOME:-}" ]] && export CODEX_HOME="\${ORCA_CODEX_HOME}"`
/**
* The OSC 133 hooks, defined at top level so their bodies are parsed before the
* user's `.zshenv` can change the parsing mode.
*/
const ZSH_OSC133_FUNCTION_BLOCK = `__orca_osc133_precmd() {
local exit_code=$?
if [[ -n "\${__orca_in_command:-}" ]]; then
builtin printf "\\033]133;D;%s\\007" "$exit_code"
builtin unset __orca_in_command
fi
builtin printf "\\033]133;A\\007"
}
__orca_osc133_preexec() {
builtin printf "\\033]133;C\\007"
# Why typeset -g: a plain assignment here creates a global inside a function,
# which prints a warning above every command under warn_create_global.
builtin typeset -g __orca_in_command=1
}`
function joinBlocks(blocks: (string | null)[]): string {
return blocks.filter((block): block is string => block !== null).join('\n')
}
function indentBlock(block: string, indent: string): string {
return block
.split('\n')
.map((line) => (line.length > 0 ? `${indent}${line}` : line))
.join('\n')
}
/** One hook feature: `if __orca_has_feature <name>; then ... fi`. */
function featureGuard(name: string, body: (string | null)[]): string | null {
const blocks = body.filter((block): block is string => block !== null)
if (blocks.length === 0) {
return null
}
return ` if __orca_has_feature ${name}; then\n${indentBlock(joinBlocks(blocks).replace(/\n$/, ''), ' ')}\n fi`
}
/** The env/PATH restores that must outlast the user's own startup files. */
function getOverlayRestoreBlocks(spec: ZshStartupHookSpec): (string | null)[] {
return [
spec.overlayRestoreComment,
spec.restores.agentTeamsPath ? AGENT_TEAMS_PATH_RESTORE_BLOCK : null,
OPENCODE_CONFIG_DIR_RESTORE,
MIMOCODE_HOME_RESTORE,
spec.restores.remoteCliBinDir ? REMOTE_CLI_BIN_DIR_RESTORE : null,
getPosixOmpShellWrapper(),
spec.restores.codexHome ? CODEX_HOME_RESTORE : null,
spec.restores.codexLaunchPreflight ? getPosixCodexShellLaunchPreflight() : null
]
}
/**
* Everything Orca owns that must run after the user's config, in one function
* invoked from the first prompt's precmd sweep and then retired.
*/
function buildDeferredInit(spec: ZshStartupHookSpec): string {
// Why substitute in the markers case and remove otherwise: OSC 133 needs a
// permanent precmd, so swapping this hook for it keeps the array position the
// user's own hooks were registered around. With no permanent hook to leave
// behind, removing is what keeps a history-only pane observably identical to
// the unwrapped pane it was — no stray Orca name in `precmd_functions`.
// Verified on zsh 5.9 that self-removal mid-sweep skips no later hook, from
// the head, the middle and the tail of the array.
const permanentPrecmd = spec.osc133CommandMarkers
? ` if __orca_has_feature markers; then
precmd_functions=(\${precmd_functions:/__orca_deferred_init/__orca_osc133_precmd})
preexec_functions=(__orca_osc133_preexec \${preexec_functions[@]})
else
precmd_functions=(\${precmd_functions:#__orca_deferred_init})
fi`
: ` precmd_functions=(\${precmd_functions:#__orca_deferred_init})`
const lineInitRegistration = spec.startupCommandDelivery
? ` if __orca_has_feature ready || __orca_has_feature startup; then
__orca_emit_ready_marker=""
__orca_has_feature ready && __orca_emit_ready_marker=1
${indentBlock(getZshShellReadyMarkerRegistrationBlock(spec.readyMarkerEscaped, true), ' ')}
fi`
: featureGuard('ready', [
indentBlock(getZshShellReadyMarkerRegistrationBlock(spec.readyMarkerEscaped), '')
])
return `__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
# precmd_functions is fatal, and KSH_ARRAYS makes the 1-based feature lookup
# drop whichever feature is listed first.
builtin emulate -L zsh
(( $+_orca_deferred_init_done )) && return 0
builtin typeset -g _orca_deferred_init_done=1
builtin typeset -g precmd_functions
${permanentPrecmd}
${joinBlocks([
featureGuard('overlay', getOverlayRestoreBlocks(spec)),
// Why no /etc/zshrc repair branch: ZDOTDIR was handed back before that file
// ran, so the value it derives is the user's own path. #11044 is unreachable.
` if [[ -n "\${_orca_histfile:-}" ]]; then
HISTFILE="$_orca_histfile"
fi`,
lineInitRegistration
])}
${
spec.osc133CommandMarkers
? ` # Why called here: we were appended during this prompt's own precmd sweep, so
# the permanent hook has not run yet and the first prompt would lose its mark.
__orca_has_feature markers && __orca_osc133_precmd\n`
: ''
} builtin unset _orca_shell_features _orca_histfile
builtin unfunction __orca_deferred_init __orca_has_feature
}`
}
export function buildZshStartupHook(spec: ZshStartupHookSpec): string {
return `${joinBlocks([
`# ${spec.headerLabel}`,
ZSH_ZDOTDIR_HANDBACK_BLOCK,
ZSH_FEATURE_CHANNEL_BLOCK,
SHELL_STARTUP_IDENTITY_MARKER_BLOCK,
spec.osc133CommandMarkers ? ZSH_OSC133_FUNCTION_BLOCK : null,
buildDeferredInit(spec),
ZSH_USER_ZSHENV_SOURCE_BLOCK
])}\n`
}