import os from 'node:os' import type { Page } from '@stablyai/playwright-test' import { test, expect } from './helpers/orca-app' import { ensureTerminalVisible, waitForActiveWorktree, waitForSessionReady } from './helpers/store' import { UUID_RE, execInTerminal, waitForActivePanePtyId, waitForActiveTerminalManager, waitForTerminalOutput } from './helpers/terminal' type LocalhostSshTarget = { label: string host: string port: number username: string configHost?: string identityFile?: string } const RUN_LOCALHOST_SSH = process.env.ORCA_E2E_SSH_LOCALHOST === '1' const RUN_REMOTE_HOOKS = process.env.ORCA_FEATURE_REMOTE_AGENT_HOOKS === undefined || (process.env.ORCA_FEATURE_REMOTE_AGENT_HOOKS.trim() !== '' && process.env.ORCA_FEATURE_REMOTE_AGENT_HOOKS.trim() !== '0') function parsePort(value: string | undefined): number { const parsed = Number(value ?? '22') if (Number.isInteger(parsed) && parsed > 0 && parsed <= 65535) { return parsed } throw new Error(`Invalid ORCA_E2E_SSH_PORT: ${value}`) } function currentUsername(): string { return ( process.env.ORCA_E2E_SSH_USER ?? process.env.USER ?? process.env.USERNAME ?? os.userInfo().username ) } function readLocalhostSshTarget(): LocalhostSshTarget { const configHost = process.env.ORCA_E2E_SSH_CONFIG_HOST?.trim() const host = process.env.ORCA_E2E_SSH_HOST?.trim() ?? (configHost ? '' : '127.0.0.1') const identityFile = process.env.ORCA_E2E_SSH_IDENTITY_FILE?.trim() return { label: `Localhost SSH E2E ${Date.now()}`, host, port: parsePort(process.env.ORCA_E2E_SSH_PORT), username: currentUsername(), ...(configHost ? { configHost } : {}), ...(identityFile ? { identityFile } : {}) } } function shellQuote(value: string): string { return `'${value.replaceAll("'", "'\\''")}'` } function marker(name: string): string { return `__ORCA_${name}_${Date.now()}__` } function emitMarkerCommand(value: string): string { const midpoint = Math.floor(value.length / 2) return `printf '%s%s\\n' ${shellQuote(value.slice(0, midpoint))} ${shellQuote( value.slice(midpoint) )}` } async function focusTerminal(page: Page): Promise { await page.evaluate(() => { const store = window.__store if (!store) { throw new Error('Store unavailable') } const state = store.getState() const worktreeId = state.activeWorktreeId if (!worktreeId) { throw new Error('No active worktree') } const tabId = state.activeTabType === 'terminal' ? state.activeTabId : (state.activeTabIdByWorktree?.[worktreeId] ?? null) if (!tabId) { throw new Error('No active terminal tab') } const manager = window.__paneManagers?.get(tabId) const pane = manager?.getActivePane?.() ?? manager?.getPanes?.()[0] if (!pane) { throw new Error('No active terminal pane') } pane.terminal.focus() }) } async function postCodexHook( page: Page, ptyId: string, payload: Record, markerName: string ): Promise { const hookPostedMarker = marker(markerName) // Why: a foreground curl command emits the shell's command-finished marker // immediately after the hook, which correctly clears a same-turn agent row. // Post from a delayed background subshell so this test observes hook routing. await execInTerminal( page, ptyId, [ 'if [ -z "$ORCA_AGENT_HOOK_PORT" ] || [ -z "$ORCA_AGENT_HOOK_TOKEN" ] || [ -z "$ORCA_PANE_KEY" ]; then', ' echo __ORCA_AGENT_HOOK_ENV_MISSING__', 'else', ` hook_payload=${shellQuote(JSON.stringify(payload))}`, ' (', ' sleep 0.1', ' if curl -sS -X POST "http://127.0.0.1:${ORCA_AGENT_HOOK_PORT}/hook/codex" \\', ' -H "Content-Type: application/x-www-form-urlencoded" \\', ' -H "X-Orca-Agent-Hook-Token: ${ORCA_AGENT_HOOK_TOKEN}" \\', ' --data-urlencode "paneKey=${ORCA_PANE_KEY}" \\', ' --data-urlencode "tabId=${ORCA_TAB_ID}" \\', ' --data-urlencode "worktreeId=${ORCA_WORKTREE_ID}" \\', ' --data-urlencode "env=${ORCA_AGENT_HOOK_ENV}" \\', ' --data-urlencode "version=${ORCA_AGENT_HOOK_VERSION}" \\', ' --data-urlencode "payload=${hook_payload}" >/dev/null; then', ` ${emitMarkerCommand(hookPostedMarker)}`, ' fi', ' ) &', 'fi' ].join('\n') ) await waitForTerminalOutput(page, hookPostedMarker, 20_000) } test.describe('Localhost SSH', () => { test.skip( !RUN_LOCALHOST_SSH, 'Set ORCA_E2E_SSH_LOCALHOST=1 to run this local-machine-only SSH E2E test.' ) test.skip( !RUN_REMOTE_HOOKS, 'Unset ORCA_FEATURE_REMOTE_AGENT_HOOKS or set it to 1 so remote PTYs keep pane identity and forward hook events.' ) test.skip(process.platform === 'win32', 'Localhost SSH hook E2E uses POSIX hook scripts.') test('routes a terminal and agent-hook status over localhost SSH', async ({ orcaPage, testRepoPath }) => { test.slow() await waitForSessionReady(orcaPage) await waitForActiveWorktree(orcaPage) const target = readLocalhostSshTarget() const remote = await orcaPage.evaluate( async ({ remotePath, target }) => { const store = window.__store if (!store) { throw new Error('Store unavailable') } const credentialUnsub = window.api.ssh.onCredentialRequest((request) => { void window.api.ssh.submitCredential({ requestId: request.requestId, value: null }) }) try { const { target: createdTarget, repoReadoptions } = await window.api.ssh.addTarget({ target: { ...target, // Why: local-only E2E should not leave a long-lived relay process // behind if the Electron app is killed between cleanup hooks. relayGracePeriodSeconds: 1 } }) store.getState().recordSshRepoReadoptions(repoReadoptions) let state try { state = await window.api.ssh.connect({ targetId: createdTarget.id }) } catch (err) { const message = err instanceof Error ? err.message : String(err) throw new Error( `Failed to connect to localhost SSH target ${target.username}@${target.host || target.configHost}:${target.port}. ` + `Ensure sshd is running and key/agent auth is non-interactive. ${message}` ) } if (!state || state.status !== 'connected') { throw new Error(`SSH target did not reach connected state: ${JSON.stringify(state)}`) } store.getState().setSshConnectionState(createdTarget.id, state) const labels = new Map(store.getState().sshTargetLabels) labels.set(createdTarget.id, createdTarget.label) store.getState().setSshTargetLabels(labels) const result = await window.api.repos.addRemote({ connectionId: createdTarget.id, remotePath, displayName: 'Localhost SSH E2E' }) if ('error' in result) { throw new Error(result.error) } await store.getState().fetchRepos() await store.getState().fetchWorktrees(result.repo.id) const worktrees = store.getState().worktreesByRepo[result.repo.id] ?? [] const worktree = worktrees.find((candidate) => candidate.path === result.repo.path) ?? worktrees[0] if (!worktree) { throw new Error(`No remote worktree found for ${result.repo.path}`) } store.getState().setActiveWorktree(worktree.id) if ((store.getState().tabsByWorktree[worktree.id] ?? []).length === 0) { store.getState().createTab(worktree.id) } store.getState().setActiveTabType('terminal') return { targetId: createdTarget.id, repoId: result.repo.id, worktreeId: worktree.id } } finally { credentialUnsub() } }, { remotePath: testRepoPath, target } ) await expect(remote.targetId).toBeTruthy() await ensureTerminalVisible(orcaPage, 30_000) await waitForActiveTerminalManager(orcaPage, 45_000) const ptyId = await waitForActivePanePtyId(orcaPage, 45_000) const paneKey = await orcaPage.evaluate(() => { const store = window.__store if (!store) { throw new Error('Store unavailable') } const state = store.getState() const worktreeId = state.activeWorktreeId if (!worktreeId) { throw new Error('No active worktree') } const tabs = state.tabsByWorktree[worktreeId] ?? [] const tabId = state.activeTabType === 'terminal' ? state.activeTabId : (state.activeTabIdByWorktree?.[worktreeId] ?? tabs[0]?.id) if (!tabId) { throw new Error('No active terminal tab') } const manager = window.__paneManagers?.get(tabId) const pane = manager?.getActivePane?.() ?? manager?.getPanes?.()[0] if (!pane) { throw new Error('No active terminal pane') } return `${tabId}:${pane.leafId}` }) const paneKeyLeafId = paneKey.slice(paneKey.indexOf(':') + 1) expect(paneKeyLeafId).toMatch(UUID_RE) await orcaPage.evaluate(() => { const state = window as unknown as { __sshAgentStatusEvents?: unknown[] __sshAgentStatusUnsubscribe?: () => void } state.__sshAgentStatusEvents = [] state.__sshAgentStatusUnsubscribe?.() state.__sshAgentStatusUnsubscribe = window.api.agentStatus.onSet((event) => { state.__sshAgentStatusEvents?.push(event) }) }) const terminalMarker = marker('LOCALHOST_SSH') await execInTerminal(orcaPage, ptyId, emitMarkerCommand(terminalMarker)) await waitForTerminalOutput(orcaPage, terminalMarker, 20_000) const envMarker = marker('AGENT_HOOK_ENV_OK') const envFailedMarker = marker('AGENT_HOOK_ENV_BAD') await execInTerminal( orcaPage, ptyId, [ `if [ "$ORCA_PANE_KEY" = ${shellQuote(paneKey)} ] && [ -n "$ORCA_AGENT_HOOK_PORT" ] && [ -n "$ORCA_AGENT_HOOK_TOKEN" ] && /bin/sh -c 'test -n "$ORCA_PANE_KEY" && test -n "$ORCA_AGENT_HOOK_PORT" && test -n "$ORCA_AGENT_HOOK_TOKEN"'; then`, ` ${emitMarkerCommand(envMarker)}`, 'else', ' token_state=${ORCA_AGENT_HOOK_TOKEN:+set}', ` printf '%s pane=%s port=%s token=%s endpoint=%s\\n' ${shellQuote(envFailedMarker)} "$ORCA_PANE_KEY" "$ORCA_AGENT_HOOK_PORT" "$token_state" "$ORCA_AGENT_HOOK_ENDPOINT"`, 'fi' ].join('\n') ) await waitForTerminalOutput(orcaPage, envMarker, 20_000) const pluginOverlayMarker = marker('AGENT_PLUGIN_OVERLAYS_OK') const pluginOverlayFailedMarker = marker('AGENT_PLUGIN_OVERLAYS_BAD') await execInTerminal( orcaPage, ptyId, [ 'opencode_status_file="$OPENCODE_CONFIG_DIR/plugins/orca-opencode-status.js"', 'pi_status_file="$HOME/.pi/agent/extensions/orca-agent-status.ts"', 'if [ -n "$OPENCODE_CONFIG_DIR" ] && [ -f "$opencode_status_file" ] && [ -f "$pi_status_file" ]; then', ` ${emitMarkerCommand(pluginOverlayMarker)}`, 'else', ` printf '%s opencode=%s opencode_file=%s pi_file=%s\\n' ${shellQuote(pluginOverlayFailedMarker)} "$OPENCODE_CONFIG_DIR" "$opencode_status_file" "$pi_status_file"`, 'fi' ].join('\n') ) await waitForTerminalOutput(orcaPage, pluginOverlayMarker, 20_000) const prompt = `orca ssh e2e prompt ${Date.now()}` await postCodexHook( orcaPage, ptyId, { hook_event_name: 'UserPromptSubmit', prompt }, 'AGENT_HOOK_POSTED' ) await expect .poll( async () => orcaPage.evaluate( ({ paneKey, prompt, targetId, worktreeId }) => { const state = window.__store?.getState() const entries = Object.values(state?.agentStatusByPaneKey ?? {}) return entries.some( (entry) => entry.paneKey === paneKey && entry.prompt === prompt && entry.agentType === 'codex' && entry.state === 'working' && state?.repos.some((repo) => repo.connectionId === targetId) === true && Object.values(state?.worktreesByRepo ?? {}) .flat() .some((worktree) => worktree.id === worktreeId) ) }, { paneKey, prompt, targetId: remote.targetId, worktreeId: remote.worktreeId } ), { timeout: 20_000, message: 'Remote Codex hook status did not reach the renderer agent-status store' } ) .toBe(true) const ctrlPrompt = `orca ssh ctrl-c interrupt ${Date.now()}` await postCodexHook( orcaPage, ptyId, { hook_event_name: 'UserPromptSubmit', prompt: ctrlPrompt }, 'AGENT_HOOK_CTRL_WORKING' ) await focusTerminal(orcaPage) await orcaPage.keyboard.press('Control+C') await orcaPage.waitForTimeout(750) expect( await orcaPage.evaluate( ({ paneKey, prompt, targetId, worktreeId }) => { const state = window.__store?.getState() const entry = state?.agentStatusByPaneKey[paneKey] const events = ( window as unknown as { __sshAgentStatusEvents?: { prompt?: string connectionId?: string | null worktreeId?: string }[] } ).__sshAgentStatusEvents ?? [] return { state: entry?.state, interrupted: entry?.interrupted, prompt: entry?.prompt, eventMatched: events.some( (event) => event.prompt === prompt && event.connectionId === targetId && event.worktreeId === worktreeId ) } }, { paneKey, prompt: ctrlPrompt, targetId: remote.targetId, worktreeId: remote.worktreeId } ) ).toEqual({ state: 'working', interrupted: undefined, prompt: ctrlPrompt, eventMatched: true }) await postCodexHook( orcaPage, ptyId, { hook_event_name: 'PreToolUse', tool_name: 'exec_command', tool_input: { cmd: '/bin/sleep 90' } }, 'AGENT_HOOK_LATE_WORKING' ) await expect .poll( () => orcaPage.evaluate( ({ paneKey }) => { const entry = window.__store?.getState().agentStatusByPaneKey[paneKey] return { state: entry?.state, interrupted: entry?.interrupted, prompt: entry?.prompt } }, { paneKey } ), { timeout: 5_000, message: 'Late remote working hook did not remain working' } ) .toEqual({ state: 'working', interrupted: undefined, prompt: ctrlPrompt }) const escapePrompt = `orca ssh escape interrupt ${Date.now()}` await postCodexHook( orcaPage, ptyId, { hook_event_name: 'UserPromptSubmit', prompt: escapePrompt }, 'AGENT_HOOK_ESCAPE_WORKING' ) await focusTerminal(orcaPage) await orcaPage.keyboard.press('Escape') await orcaPage.waitForTimeout(750) expect( await orcaPage.evaluate( ({ paneKey }) => { const entry = window.__store?.getState().agentStatusByPaneKey[paneKey] return { state: entry?.state, interrupted: entry?.interrupted, prompt: entry?.prompt } }, { paneKey } ) ).toEqual({ state: 'working', interrupted: undefined, prompt: escapePrompt }) }) })