mirror of
https://github.com/stablyai/orca.git
synced 2026-10-24 16:03:45 +00:00
A plain zsh pane — no startup command, no agent overlay — was never wrapped, so
Orca's HISTFILE repair never ran in it. macOS `/etc/zshrc` assigns
`HISTFILE=${ZDOTDIR:-$HOME}/.zsh_history` with no check-before-set and runs
before any file Orca controls, so per-worktree history was a silent no-op for
every ordinary pane on the primary platform.
Wrapping those panes needs a way to say which wrapper features a shell should
turn on. That channel is one exported variable, ORCA_SHELL_FEATURES, carrying a
comma-separated positive allowlist from a closed set (history, markers, ready,
identity, overlay). The wrapper .zshenv reads it into a plain, non-exported
array and unsets it in its first executable lines, before the user's own
.zshenv — so the selection survives .zshenv -> .zprofile -> .zshrc -> .zlogin in
this process but physically cannot reach a child. There is no negative or
suppression variable anywhere; an absent or inherited value can only ever mean
fewer features. ORCA_HISTFILE is consumed and destroyed the same way, which
removes the root cause of #11146 instead of patching it.
All order-sensitive wrapper work now lives in one `__orca_shell_epilogue`
defined in .zshenv and invoked exactly once, from .zshrc for a non-login shell
and .zlogin for a login shell, with each feature an independent guard.
Selection is a pure function of spawn env and launch intent, so a pane wrapped
only for history gets no OSC 133 and is observably identical to the unwrapped
pane it used to be.
Generation is now fail-closed: wrapper files are written to a temp name and
renamed, every required path is verified non-empty, and ZDOTDIR is only set when
that holds. Previously a failed write still pointed ZDOTDIR at an empty dir and
the user silently lost their entire zsh config.
Orca also recognised only its own `*/shell-ready/zsh` dir shape when deciding
what the user's ZDOTDIR was, so being launched from any other terminal that had
hijacked ZDOTDIR captured that as the user's config dir. Ownership is now
established positively — a stamped marker file, or Orca's own dir shape for
wrappers written by older builds — and an inherited ZDOTDIR holding no zsh
startup file is ignored. No vendor is detected by name.
101 lines
3.9 KiB
TypeScript
101 lines
3.9 KiB
TypeScript
/**
|
|
* Byte-for-byte snapshots of every shell wrapper file Orca generates, for all
|
|
* three transports (local PTY, daemon/SSH, relay overlay).
|
|
*
|
|
* Why: the zsh generators were unified behind one builder; these fixtures were
|
|
* captured from the pre-unification code so any drift shows up as a diff.
|
|
*/
|
|
import { mkdtempSync, readFileSync, rmSync } from 'node:fs'
|
|
import { tmpdir } from 'node:os'
|
|
import { join } from 'node:path'
|
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
|
import { ensureShellReadyWrappersAt } from './providers/local-pty-shell-ready-wrapper-generation'
|
|
import { getShellLaunchConfig as getDaemonShellLaunchConfig } from './daemon/shell-ready'
|
|
import { ensureOverlayRestoreWrappers } from '../relay/pty-shell-overlay-wrappers'
|
|
import { getShellLaunchConfig as getLocalShellLaunchConfig } from './providers/local-pty-shell-ready'
|
|
import { selectShellStartupFeatures } from './shell-startup-features'
|
|
|
|
// Why the real selector: the snapshots then pin what a startup-command pane
|
|
// actually launches with, not a hand-written feature list.
|
|
const STARTUP_COMMAND_FEATURES = selectShellStartupFeatures({
|
|
shellPath: 'zsh',
|
|
env: {},
|
|
hasStartupCommand: true,
|
|
waitsForShellReady: true,
|
|
emitsStartupIdentity: true
|
|
})
|
|
|
|
const WRAPPER_FILES = [
|
|
['zsh-zshenv', join('zsh', '.zshenv')],
|
|
['zsh-zprofile', join('zsh', '.zprofile')],
|
|
['zsh-zshrc', join('zsh', '.zshrc')],
|
|
['zsh-zlogin', join('zsh', '.zlogin')],
|
|
['bash-rcfile', join('bash', 'rcfile')]
|
|
] as const
|
|
|
|
const SNAPSHOT_DIR = join(__dirname, 'shell-wrapper-snapshots')
|
|
|
|
// Why: the wrapper root is a temp dir per run, and the baked ZDOTDIR literal is
|
|
// the only path-dependent byte in the output; pin it to a stable placeholder.
|
|
function withStableRoot(content: string, root: string): string {
|
|
return content.split(root).join('<WRAPPER_ROOT>')
|
|
}
|
|
|
|
function snapshotPath(transport: string, label: string): string {
|
|
return join(SNAPSHOT_DIR, `${transport}-${label}.txt`)
|
|
}
|
|
|
|
async function expectWrapperFiles(transport: string, root: string): Promise<void> {
|
|
for (const [label, relativePath] of WRAPPER_FILES) {
|
|
const content = readFileSync(join(root, relativePath), 'utf8')
|
|
await expect(withStableRoot(content, root)).toMatchFileSnapshot(snapshotPath(transport, label))
|
|
}
|
|
}
|
|
|
|
// Why: all three generators are POSIX-only (the launch configs skip wrapping on
|
|
// win32), and native Windows path separators would make the fixtures unstable.
|
|
const describePosix = process.platform === 'win32' ? describe.skip : describe
|
|
|
|
describePosix('generated shell wrapper files', () => {
|
|
let root = ''
|
|
let previousUserDataPath: string | undefined
|
|
|
|
beforeEach(() => {
|
|
root = mkdtempSync(join(tmpdir(), 'orca-wrapper-snapshot-'))
|
|
previousUserDataPath = process.env.ORCA_USER_DATA_PATH
|
|
})
|
|
|
|
afterEach(() => {
|
|
if (previousUserDataPath === undefined) {
|
|
delete process.env.ORCA_USER_DATA_PATH
|
|
} else {
|
|
process.env.ORCA_USER_DATA_PATH = previousUserDataPath
|
|
}
|
|
rmSync(root, { recursive: true, force: true })
|
|
})
|
|
|
|
it('local PTY wrappers', async () => {
|
|
ensureShellReadyWrappersAt(root)
|
|
await expectWrapperFiles('local', root)
|
|
})
|
|
|
|
it('daemon wrappers', async () => {
|
|
process.env.ORCA_USER_DATA_PATH = root
|
|
getDaemonShellLaunchConfig('/bin/zsh', STARTUP_COMMAND_FEATURES)
|
|
await expectWrapperFiles('daemon', join(root, 'shell-ready'))
|
|
})
|
|
|
|
it('relay overlay wrappers', async () => {
|
|
ensureOverlayRestoreWrappers(root)
|
|
await expectWrapperFiles('relay', root)
|
|
})
|
|
|
|
it('fish shell-ready init commands', async () => {
|
|
process.env.ORCA_USER_DATA_PATH = root
|
|
const local = getLocalShellLaunchConfig('/usr/bin/fish', STARTUP_COMMAND_FEATURES)
|
|
const daemon = getDaemonShellLaunchConfig('/usr/bin/fish', STARTUP_COMMAND_FEATURES)
|
|
await expect(local.args?.[2]).toMatchFileSnapshot(snapshotPath('local', 'fish-init'))
|
|
await expect(daemon.args?.[2]).toMatchFileSnapshot(snapshotPath('daemon', 'fish-init'))
|
|
})
|
|
})
|