* reland: portable startup-shell dialect, with the two revert causes fixed
Relands #14863 (reverted by #14975) with fixes for both regressions the
revert cited.
1. History GC deleted folder-workspace shell history. The live set was built
from `getAllWorktreeMeta()` alone, but a folder workspace's PTY carries
`folder:<id>` as its worktree id, so every live folder workspace looked
orphaned. `getKnownWorktreeIdsForHistoryGc` now unions in
`getFolderWorkspaces()`. Both consumers — the history-directory prune and
the fish-history sweep — read that one set, so the fix covers bash, zsh and
fish history alike. The directory prune had this gap since #1524; #14863
only widened its blast radius to fish files.
2. A copied Codex resume command aborted under `set -u`. Its leading clear
statement has to test `$fish_pid`, and that unbound expansion takes the
whole line — including the agent launch — down with it. Copied text runs in
a shell Orca never spawned, so nothing can seed that variable first. The
removal now rides on the agent itself as `env -u`, which needs no shell
syntax and no expansion. Verified byte-identical under `set -u` in sh,
bash, zsh, dash, ksh and fish.
`env` cannot run the `cd` builtin, and a child `cd` would not move the
agent, so the prefix is placed on the agent rather than on the whole
`cd … && agent` chain. cmd and PowerShell have no nounset hazard and keep
their clear ahead of the `cd`, which preserves `cd … && agent` — a failed
`cd` still cannot launch the agent in the wrong directory.
* fix(history-gc): stop three more paths from deleting live shell history
Found by adversarial review of the reland. All three are the same class as
the bug that caused the revert: a live set that is missing a category of
real workspace, so the GC reads it as orphaned.
1. Profiles. The history root is `userData/terminal-history`, which has no
profile segment, but the Store the GC consults is per-profile. So after a
profile switch the live set condemned every other profile's history — and
fish history, which lands in the user's own fish data dir, is shared by
every profile on the machine. The live set now unions in the inactive
profiles' worktrees and folder workspaces, read from their data files. A
profile whose ids cannot be read reports the empty set rather than one
that condemns real history.
2. No empty-set guard on the tree scan. `sweepOrphanedFishHistoryFiles`
refuses an empty live set because it cannot be told apart from a store
that failed to hydrate; the directory scan, which deletes more, had no
such guard. A store that fell back to default state would have taken
every worktree's bash and zsh history with it, across all roots including
WSL. Four existing tests passed `new Set()` and relied on "empty means
everything is orphaned" — exactly the behavior being removed — so they
now pass a real live set.
3. Relay fish history. The relay isolates its history tree under its own
root but wrote fish history into the shared fish data dir under the
desktop naming, keyed by the CLIENT's worktree ids. On a machine running
both Orca and a relay host, the desktop sweep deleted remote sessions'
history once it went stale. Relay files are now `orca_relay_<hash>`,
which the sweep's pattern deliberately does not match; the relay still
deletes them by exact name when the worktree goes away.
* fix(resume): enforce the env-removal invariants instead of documenting them
Both found by adversarial review; both were unreachable from today's callers
and silent if reached, which is exactly how they would survive to a caller
that does reach them.
- A pinned CODEX_HOME and the removal named the same variable, and `env -u`
strips what the assignment just set — so the agent would have resumed
against the real home and not found the session. The removal list now
excludes any name the prefix pins, keeping the assignment authoritative as
the old `clear…; CODEX_HOME=x agent` ordering did. Same fix in the git-bash
twin. The PowerShell branch already clears before it assigns, so it was
never affected.
- Placement was keyed on the platform while the grammar it selects is keyed
on the shell, so `platform: 'linux'` with `shell: 'powershell'` emitted
POSIX `env -u` into a PowerShell line. PowerShell now routes to the
PowerShell builder whatever the host, and the POSIX/cmd split below asks
the shell rather than the platform.