mirror of
https://github.com/stablyai/orca.git
synced 2026-10-27 00:02:48 +00:00
* fix(terminal): extend the shell-ready startup barrier to fish (STA-3417) Fish never emitted the OSC 777 shell-ready marker, so agent launch commands were written into the PTY while fish/Starship were still initializing: the daemon path wrote them synchronously at session create and the local path blind-wrote ~30ms after the first output byte. The command was echoed by the kernel but never executed. - shell-templates: shared fish --init-command that emits the marker once on the first fish_prompt event (the earliest point fish's own reader owns the PTY, mirroring zsh's zle-line-init marker) - daemon shell-ready: fish joins the startup barrier so the launch command queues until the marker (timeout fallback unchanged) - local-pty-shell-ready: fish launch config gains the marker wrapper - codex-startup-delivery/tui-agent-startup: omp/pi/opencode plans now request shell-ready delivery (codex parity) so the SSH renderer path also waits for the prompt; plain payload-free codex stays on the markerless fast path * fix(terminal): answer DA1 past the shell-ready barrier The barrier queues all inbound input until the ready marker, including the renderer's DA1 reply. A shell that withholds its first prompt until DA1 is answered — fish waits 10s — therefore never emits the marker that would release the reply it is waiting for. Measured: 10.37s to launch an agent, versus 0.35s once the reply lands. Answer DA1 from the daemon while the barrier holds, writing straight to the subprocess so the reply bypasses the queue, and consume the query so the renderer's xterm cannot also reply. Released on ready, timeout, or dispose, handing DA1 back to the renderer for steady state. Consolidates the identical DA1 handler the ConPTY override already used. * fix(terminal): prevent duplicate startup DA1 replies
188 lines
7.7 KiB
TypeScript
188 lines
7.7 KiB
TypeScript
// Why: local PTYs and the daemon/SSH path must use identical ZDOTDIR discovery;
|
|
// small drift here breaks different terminal transports in different ways.
|
|
|
|
function quotePosixSingle(value: string): string {
|
|
return `'${value.replace(/'/g, `'\\''`)}'`
|
|
}
|
|
|
|
export function getZshEnvTemplate(zshDir: string, headerPrefix = ''): string {
|
|
const header = headerPrefix
|
|
? `Orca ${headerPrefix} zsh shell-ready wrapper`
|
|
: 'Orca zsh shell-ready wrapper'
|
|
return `# ${header}
|
|
# Why: capture the runtime wrapper dir before it is unset below. On WSL this
|
|
# file is generated with a Windows path but sourced via /mnt/c, so the baked
|
|
# literal is unusable there and ZDOTDIR must be restored from this value.
|
|
# Derive it from the file being sourced (%x, zsh's internal script name) rather
|
|
# than the env-imported $ZDOTDIR: zsh corrupts environment values whose UTF-8
|
|
# bytes fall in its 0x84-0x9D token range (e.g. a non-ASCII Windows username
|
|
# such as a Korean login), which would make the self-check below fail and fall
|
|
# back to the unusable baked literal, so the user's .zshrc never loads (#8003).
|
|
# %x is not subject to that corruption; keep $ZDOTDIR as a fallback for the
|
|
# rare shell where %x prompt expansion yields nothing.
|
|
_orca_wrapper_zdotdir_self="\${\${(%):-%x}:h}"
|
|
if [[ -z "\${_orca_wrapper_zdotdir_self:-}" ]]; then
|
|
_orca_wrapper_zdotdir_self="\${ZDOTDIR:-}"
|
|
fi
|
|
while [[ "\${_orca_wrapper_zdotdir_self:-}" == */ ]]; do
|
|
_orca_wrapper_zdotdir_self="\${_orca_wrapper_zdotdir_self%/}"
|
|
done
|
|
_orca_spawn_orig_zdotdir="\${ORCA_ORIG_ZDOTDIR:-}"
|
|
_orca_user_zdotdir="\${_orca_spawn_orig_zdotdir:-$HOME}"
|
|
_orca_zshenv_source_dir="\${ORCA_ZSHENV_SOURCE_DIR:-$HOME}"
|
|
_orca_zshenv_path=""
|
|
unset ORCA_ZSHENV_SOURCE_DIR
|
|
|
|
# Normalize fallback and source roots before reading user .zshenv so nested
|
|
# Orca PTYs never source another Orca wrapper recursively.
|
|
while [[ "\${_orca_user_zdotdir}" == */ ]]; do
|
|
_orca_user_zdotdir="\${_orca_user_zdotdir%/}"
|
|
done
|
|
case "\${_orca_user_zdotdir}" in
|
|
""|*/shell-ready/zsh) _orca_user_zdotdir="$HOME" ;;
|
|
esac
|
|
while [[ "\${_orca_zshenv_source_dir}" == */ ]]; do
|
|
_orca_zshenv_source_dir="\${_orca_zshenv_source_dir%/}"
|
|
done
|
|
case "\${_orca_zshenv_source_dir}" in
|
|
""|*/shell-ready/zsh) _orca_zshenv_source_dir="$HOME" ;;
|
|
esac
|
|
|
|
# Why: source at wrapper top level, not in a function/subshell, so .zshenv
|
|
# exports, functions, path/fpath typesets, and zsh options keep normal scope.
|
|
unset ZDOTDIR
|
|
if [[ -n "\${_orca_zshenv_source_dir:-}" && -f "\${_orca_zshenv_source_dir}/.zshenv" ]]; then
|
|
_orca_zshenv_path="\${_orca_zshenv_source_dir}/.zshenv"
|
|
fi
|
|
if [[ -n "\${_orca_zshenv_path:-}" ]]; then
|
|
source "\${_orca_zshenv_path}"
|
|
fi
|
|
|
|
_orca_discovered_zdotdir="\${ZDOTDIR:-}"
|
|
|
|
while [[ "\${_orca_discovered_zdotdir}" == */ ]]; do
|
|
_orca_discovered_zdotdir="\${_orca_discovered_zdotdir%/}"
|
|
done
|
|
|
|
case "\${_orca_discovered_zdotdir}" in
|
|
*[![:space:]]*) ;;
|
|
*) _orca_discovered_zdotdir="" ;;
|
|
esac
|
|
|
|
if [[ -n "\${_orca_discovered_zdotdir}" && ! -d "\${_orca_discovered_zdotdir}" ]]; then
|
|
[[ "\${ORCA_DEBUG:-0}" == "1" ]] && echo "[orca-shell-ready] Discovered ZDOTDIR '\${_orca_discovered_zdotdir}' does not exist, falling back" >&2
|
|
_orca_discovered_zdotdir=""
|
|
fi
|
|
|
|
export ORCA_ORIG_ZDOTDIR="\${_orca_discovered_zdotdir:-\${_orca_user_zdotdir:-$HOME}}"
|
|
|
|
while [[ "\${ORCA_ORIG_ZDOTDIR}" == */ ]]; do
|
|
ORCA_ORIG_ZDOTDIR="\${ORCA_ORIG_ZDOTDIR%/}"
|
|
done
|
|
|
|
case "\${ORCA_ORIG_ZDOTDIR}" in
|
|
""|*/shell-ready/zsh) export ORCA_ORIG_ZDOTDIR="$HOME" ;;
|
|
esac
|
|
|
|
# Why: use :- after user .zshenv — a pathological unset under set -u must not
|
|
# abort the wrapper; empty falls through to the baked-literal branch.
|
|
if [[ -n "\${_orca_wrapper_zdotdir_self:-}" && -f "\${_orca_wrapper_zdotdir_self:-}/.zshenv" ]]; then
|
|
export ZDOTDIR="\${_orca_wrapper_zdotdir_self:-}"
|
|
else
|
|
export ZDOTDIR=${quotePosixSingle(zshDir)}
|
|
fi
|
|
unset _orca_spawn_orig_zdotdir _orca_user_zdotdir _orca_zshenv_source_dir _orca_zshenv_path _orca_discovered_zdotdir _orca_wrapper_zdotdir_self
|
|
`
|
|
}
|
|
|
|
export function getZshStartupFileSourceBlock(options: {
|
|
fileName: '.zprofile' | '.zshrc' | '.zlogin'
|
|
homeExpression?: string
|
|
interactiveOnly?: boolean
|
|
skipWhenHomeIsCurrentZdotdir?: boolean
|
|
}): string {
|
|
const homeExpression = options.homeExpression ?? '"${ORCA_ORIG_ZDOTDIR:-$HOME}"'
|
|
const checks = [
|
|
options.skipWhenHomeIsCurrentZdotdir ? '"$_orca_home" != "$ZDOTDIR"' : null,
|
|
options.interactiveOnly ? '-o interactive' : null,
|
|
`-f "$_orca_home/${options.fileName}"`
|
|
].filter(Boolean)
|
|
|
|
return `_orca_home=${homeExpression}
|
|
case "\${_orca_home%/}" in
|
|
*/shell-ready/zsh) _orca_home="$HOME" ;;
|
|
esac
|
|
if [[ ${checks.join(' && ')} ]]; then
|
|
_orca_wrapper_zdotdir="$ZDOTDIR"
|
|
# Why: user startup files resolve plugin/config paths from their own ZDOTDIR;
|
|
# Orca restores its wrapper dir afterward so zsh still loads wrapper files.
|
|
export ZDOTDIR="$_orca_home"
|
|
source "$_orca_home/${options.fileName}"
|
|
export ZDOTDIR="$_orca_wrapper_zdotdir"
|
|
unset _orca_wrapper_zdotdir
|
|
fi
|
|
`
|
|
}
|
|
|
|
// Why: zsh precmd fires before zle switches the PTY into line-editing mode,
|
|
// so the marker must be emitted from zle-line-init. Registering it through
|
|
// add-zle-hook-widget is unsafe: the azhw dispatcher aborts its hook chain
|
|
// when an earlier hook exits non-zero, and a pre-existing raw user widget
|
|
// (e.g. oh-my-zsh vi-mode without VI_MODE_SET_CURSOR) is preserved as the
|
|
// first hook and fails — silently suppressing the marker and stalling every
|
|
// startup command on the pre-ready timeout. Instead, own zle-line-init: emit
|
|
// the marker first, then chain to whatever widget was installed before.
|
|
export function getZshShellReadyMarkerRegistrationBlock(escapedMarker: string): string {
|
|
return `if [[ "\${ORCA_SHELL_READY_MARKER:-0}" == "1" ]]; then
|
|
# Why: capture the prior zle-line-init so the marker chains to it. On a
|
|
# re-source we are already the bound widget, so keep the function captured
|
|
# the first time instead of clobbering it to empty (which would silently
|
|
# drop the user's widget on every prompt after the second source). Only
|
|
# user-defined widgets are chainable as plain functions; builtin/completion
|
|
# forms (rare for zle-line-init) are left unchained.
|
|
if [[ "\${widgets[zle-line-init]:-}" == "user:__orca_prompt_mark" ]]; then
|
|
:
|
|
elif (( \${+widgets[zle-line-init]} )) && [[ "\${widgets[zle-line-init]}" == user:* ]]; then
|
|
__orca_prev_line_init_fn="\${widgets[zle-line-init]#user:}"
|
|
else
|
|
__orca_prev_line_init_fn=""
|
|
fi
|
|
__orca_prompt_mark() {
|
|
printf "${escapedMarker}"
|
|
# Why: call the prior hook as a plain function, not an aliased widget, so
|
|
# $WIDGET stays zle-line-init for add-zle-hook-widget dispatchers.
|
|
if [[ -n "\${__orca_prev_line_init_fn:-}" ]]; then
|
|
"\${__orca_prev_line_init_fn}" "$@"
|
|
fi
|
|
}
|
|
zle -N zle-line-init __orca_prompt_mark
|
|
fi
|
|
`
|
|
}
|
|
|
|
// Why: fish has no ZDOTDIR-style wrapper dir, so the marker rides `--init-command`
|
|
// and fires on fish_prompt — the earliest event fish exposes (STA-3417). Unlike zsh's
|
|
// zle-line-init this lands just *before* fish arms `?2004h`, which PostReadyFlushGate
|
|
// absorbs. `builtin printf` so a user-defined printf can't silently swallow the marker
|
|
// and send every launch to the ready timeout.
|
|
export function getFishShellReadyInitCommand(escapedMarker: string): string {
|
|
return `if test "$ORCA_SHELL_READY_MARKER" = 1
|
|
function __orca_shell_ready_marker --on-event fish_prompt
|
|
builtin printf "${escapedMarker}"
|
|
functions -e __orca_shell_ready_marker
|
|
end
|
|
end`
|
|
}
|
|
|
|
export function getZshFinalZdotdirRestoreBlock(homeExpression = '"${ORCA_ORIG_ZDOTDIR:-$HOME}"') {
|
|
return `_orca_home=${homeExpression}
|
|
case "\${_orca_home%/}" in
|
|
*/shell-ready/zsh) _orca_home="$HOME" ;;
|
|
esac
|
|
# Why: after Orca's last wrapper file has loaded, the interactive shell should
|
|
# expose the same ZDOTDIR a normal zsh startup would expose.
|
|
export ZDOTDIR="$_orca_home"
|
|
unset _orca_home
|
|
`
|
|
}
|