fix(remote): resolve the spawn cwd, the node manager dir, the vault host and the scrollback seed (#17952)

* fix(remote): resolve workspace cwd, mise Node, host scope, and TUI scrollback honestly

#15296 relay: a folder workspace id (`folder:<uuid>`) carries no path, so the
worktree-id split yielded nothing and $HOME silently won. Resolve the spawn cwd
through worktreeId -> ORCA_WORKSPACE_ROOT -> host default, and refuse an agent
spawn outright when a folder workspace names a root this host cannot resolve.

#11733 ssh: generalize the NVM dotfile scrape into `orca_dotfile_dirs` and drive
mise off `MISE_DATA_DIR` / `XDG_DATA_HOME` instead of a hardcoded
`$HOME/.local/share/mise`.

#13713 ai-vault: an unresolvable workspace host is `unverifiable`, not local.
Widen the default scope to every host rather than scanning the client's own
history and reporting "No agent sessions found".

#6106 terminal: hydration asked the renderer for `scrollback: 0` while an
alt-screen TUI was up, which drops the normal buffer's shell history rather than
the TUI bytes. Drop the flag; readers already split the two buffers apart.

* fix(remote): stop the relay answering host questions for a guest execution host

Three findings from review of the spawn-cwd resolver, all the same shape: a path
question answered against the wrong host, or with the wrong key.

- resolveRelaySpawnCwd refused an agent launch whenever a folder workspace named
  a root that did not stat on the relay. But relayHostDirectoryExists stats the
  relay's *own* filesystem, and the relay supports WSL shells, so a folder
  workspace on a Windows relay launching into WSL now threw where it previously
  spawned -- contradicting the function's own doc comment, which says an absent
  path for that exact host pair is a miss, not a refusal. Thread the shell's
  execution host in and demote the refusal to a miss when the spawn does not run
  on the relay's filesystem.

- requireRelaySpawnCwd's doc claims both call sites route through one resolver
  so the fence can never be keyed on a directory the spawn won't use, but the
  fence key was still computed with the non-stripping splitWorktreeId while the
  cwd used splitWorktreeIdForFilesystem. For a `::workspace:<uuid>` id those
  disagree by construction, in adjacent lines: the removal fence guarded a path
  no spawn ever enters. Same defect in shutdownForWorktreePath and the revive
  path; all three now use the filesystem split.

- The remote Node probe expanded `$HOME` and `~/` prefixes out of a dotfile
  assignment but not `$XDG_DATA_HOME`, so `MISE_DATA_DIR=$XDG_DATA_HOME/...`
  was used as a literal directory name. Add the case arm, defaulting to the
  POSIX `$HOME/.local/share` the seed value already uses -- sshd's exec channel
  usually has no XDG_DATA_HOME at all.
This commit is contained in:
Neil
2026-09-02 21:33:41 -07:00
committed by GitHub
parent cc66d6e900
commit 57681ecd09
25 changed files with 719 additions and 102 deletions
@@ -0,0 +1,70 @@
// POSIX `sh` probe listing every plausible remote Node binary, one per line.
// Kept out of the resolver so the shell text can grow without pushing that file
// past its line budget.
// Why the dotfile scrape: sshd's exec channel runs without the user's profile, so
// MISE_DATA_DIR / NVM_DIR set in ~/.zshrc are not in this environment. Reading the
// assignment out of the dotfiles is the only way to see a relocated data dir.
export const REMOTE_NODE_PATH_PROBE_SCRIPT = `
command -v node 2>/dev/null
orca_dotfile_dirs() {
orca_var_name=$1
orca_dirs=$2
for orca_file in "$HOME/.profile" "$HOME/.bash_profile" "$HOME/.bashrc" "$HOME/.zprofile" "$HOME/.zshrc"
do
[ -r "$orca_file" ] || continue
orca_dir_from_file=$(sed -n "s/^[[:space:]]*export[[:space:]][[:space:]]*$orca_var_name[[:space:]]*=[[:space:]]*//p; s/^[[:space:]]*$orca_var_name[[:space:]]*=[[:space:]]*//p" "$orca_file" | tail -n 1)
case "$orca_dir_from_file" in
\\"*\\") orca_dir_from_file=\${orca_dir_from_file#\\"}; orca_dir_from_file=\${orca_dir_from_file%%\\"*} ;;
\\'*\\') orca_dir_from_file=\${orca_dir_from_file#\\'}; orca_dir_from_file=\${orca_dir_from_file%%\\'*} ;;
*) orca_dir_from_file=\${orca_dir_from_file%%[[:space:]]*} ;;
esac
case "$orca_dir_from_file" in
'$XDG_DATA_HOME'*) orca_dir_from_file="\${XDG_DATA_HOME:-$HOME/.local/share}\${orca_dir_from_file#'$XDG_DATA_HOME'}" ;;
'$HOME'*) orca_dir_from_file="$HOME\${orca_dir_from_file#'$HOME'}" ;;
"~/"*) orca_dir_from_file="$HOME/\${orca_dir_from_file#\\~/}" ;;
esac
[ -n "$orca_dir_from_file" ] && orca_dirs="$orca_dirs
$orca_dir_from_file"
done
printf '%s\\n' "$orca_dirs"
}
nvm_dirs=\${NVM_DIR:-"$HOME/.nvm"}
nvm_dirs=$(orca_dotfile_dirs NVM_DIR "$nvm_dirs")
printf '%s\\n' "$nvm_dirs" | while IFS= read -r nvm_dir
do
[ -n "$nvm_dir" ] || continue
for candidate in "$nvm_dir"/versions/node/*/bin/node
do
[ -x "$candidate" ] && printf '%s\\n' "$candidate"
done
done
mise_dirs=\${MISE_DATA_DIR:-\${XDG_DATA_HOME:-$HOME/.local/share}/mise}
mise_dirs=$(orca_dotfile_dirs MISE_DATA_DIR "$mise_dirs")
printf '%s\\n' "$mise_dirs" | while IFS= read -r mise_dir
do
[ -n "$mise_dir" ] || continue
[ -x "$mise_dir/shims/node" ] && printf '%s\\n' "$mise_dir/shims/node"
for candidate in "$mise_dir"/installs/node/*/bin/node
do
[ -x "$candidate" ] && printf '%s\\n' "$candidate"
done
done
for candidate in \\
/usr/local/bin/node \\
/opt/homebrew/bin/node \\
"$HOME/.local/bin/node" \\
"$HOME/.fnm/aliases/default/bin/node" \\
"$HOME/.fnm/node-versions"/*/installation/bin/node \\
"$HOME/.local/share/fnm/node-versions"/*/installation/bin/node \\
"$HOME/.local/share/mise/shims/node" \\
"$HOME/.local/share/mise/installs/node"/*/bin/node \\
"$HOME/.asdf/shims/node" \\
"$HOME/.asdf/installs/nodejs"/*/bin/node \\
"$HOME/.volta/bin/node" \\
/usr/local/n/versions/node/*/bin/node
do
[ -x "$candidate" ] && printf '%s\\n' "$candidate"
done
true
`