From e1eef1f162b92343755596c193863f60398c2e5f Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Sun, 18 Oct 2026 00:15:22 -0700 Subject: [PATCH] refactor(terminal): one isRemoteExecutionHostPtyId, on prefix semantics (#30232) The shared copy parsed remote: ids fully, so a malformed or newer-format id read as local to pty-running-work-probe. The renderer copy already used the prefix check; it is now the only definition, in src/shared. --- .../terminal-pane/agent-process-inspection-cost.ts | 2 +- .../parked-terminal-command-status.ts | 2 +- .../confirmed-process-exit-row-retire.ts | 2 +- .../pty-connection/hidden-output-restore-drain.ts | 2 +- .../pty-connection/pane-agent-identity.ts | 2 +- .../pty-connection/terminal-keydown-fit.ts | 2 +- .../terminal-pane/remote-execution-host-pty.ts | 14 -------------- .../terminal-pane/terminal-hidden-view-parking.ts | 2 +- .../terminal-hidden-worktree-retention.ts | 2 +- .../terminal-pane/terminal-parked-tab-watchers.ts | 2 +- .../remote-execution-host-pty-id.test.ts} | 12 +++++++++--- src/shared/remote-execution-host-pty-id.ts | 14 ++++++++------ 12 files changed, 26 insertions(+), 32 deletions(-) delete mode 100644 src/renderer/src/components/terminal-pane/remote-execution-host-pty.ts rename src/{renderer/src/components/terminal-pane/remote-execution-host-pty.test.ts => shared/remote-execution-host-pty-id.test.ts} (72%) diff --git a/src/renderer/src/components/terminal-pane/agent-process-inspection-cost.ts b/src/renderer/src/components/terminal-pane/agent-process-inspection-cost.ts index aeeea606bc0..5a3f2912b13 100644 --- a/src/renderer/src/components/terminal-pane/agent-process-inspection-cost.ts +++ b/src/renderer/src/components/terminal-pane/agent-process-inspection-cost.ts @@ -1,4 +1,4 @@ -import { isRemoteExecutionHostPtyId } from './remote-execution-host-pty' +import { isRemoteExecutionHostPtyId } from '../../../../shared/remote-execution-host-pty-id' /** * Whether one cadence process inspection for this pane is expensive enough that diff --git a/src/renderer/src/components/terminal-pane/parked-terminal-command-status.ts b/src/renderer/src/components/terminal-pane/parked-terminal-command-status.ts index 6a732c8e43d..180dc95984b 100644 --- a/src/renderer/src/components/terminal-pane/parked-terminal-command-status.ts +++ b/src/renderer/src/components/terminal-pane/parked-terminal-command-status.ts @@ -19,7 +19,7 @@ import { setCommandCodeDoneSettleExecutor } from './command-code-done-settle' import { canCommandCodeOutputOwnPane } from './command-code-output-ownership' -import { isRemoteExecutionHostPtyId } from './remote-execution-host-pty' +import { isRemoteExecutionHostPtyId } from '../../../../shared/remote-execution-host-pty-id' export type ParkedTerminalCommandStatusPolicy = { onCommandFinished: (bestEffortExitCode: number | null) => void diff --git a/src/renderer/src/components/terminal-pane/pty-connection/confirmed-process-exit-row-retire.ts b/src/renderer/src/components/terminal-pane/pty-connection/confirmed-process-exit-row-retire.ts index 47c0efafe8c..f5b6945327f 100644 --- a/src/renderer/src/components/terminal-pane/pty-connection/confirmed-process-exit-row-retire.ts +++ b/src/renderer/src/components/terminal-pane/pty-connection/confirmed-process-exit-row-retire.ts @@ -1,7 +1,7 @@ import { useAppStore } from '@/store' import type { RecognizedAgentProcess } from '../../../../../shared/agent-process-recognition' import { resolveCompatibleAgentTypeForOwner } from '../../../../../shared/agent-title-owner' -import { isRemoteExecutionHostPtyId } from '../remote-execution-host-pty' +import { isRemoteExecutionHostPtyId } from '../../../../../shared/remote-execution-host-pty-id' import type { ConnectPanePtySession } from './connect-pane-pty-session' diff --git a/src/renderer/src/components/terminal-pane/pty-connection/hidden-output-restore-drain.ts b/src/renderer/src/components/terminal-pane/pty-connection/hidden-output-restore-drain.ts index b5b5c4e9992..6d12fdb6e8c 100644 --- a/src/renderer/src/components/terminal-pane/pty-connection/hidden-output-restore-drain.ts +++ b/src/renderer/src/components/terminal-pane/pty-connection/hidden-output-restore-drain.ts @@ -2,7 +2,7 @@ import { isRemoteRuntimePtyId } from '../../../../../shared/remote-runtime-pty-i import { recordTerminalFreezeBreadcrumb } from '../terminal-freeze-breadcrumbs' import { redactPtyIdForDiagnostics } from '../../../../../shared/pty-delivery-diagnostics' import { cancelScheduledHiddenOutputRestore } from '../hidden-output-restore-scheduler' -import { isRemoteExecutionHostPtyId } from '../remote-execution-host-pty' +import { isRemoteExecutionHostPtyId } from '../../../../../shared/remote-execution-host-pty-id' import { HIDDEN_OUTPUT_RESTORE_FOREGROUND_TIMEOUT_MS, diff --git a/src/renderer/src/components/terminal-pane/pty-connection/pane-agent-identity.ts b/src/renderer/src/components/terminal-pane/pty-connection/pane-agent-identity.ts index 88ea0b68472..ece1684313b 100644 --- a/src/renderer/src/components/terminal-pane/pty-connection/pane-agent-identity.ts +++ b/src/renderer/src/components/terminal-pane/pty-connection/pane-agent-identity.ts @@ -8,7 +8,7 @@ import { } from '@/lib/pane-manager/windows-pty-compatibility' import { createTerminalCommandLifecycle } from '../terminal-command-lifecycle' import { createPaneForegroundAgentTracker } from '../pane-foreground-agent-tracker' -import { isRemoteExecutionHostPtyId } from '../remote-execution-host-pty' +import { isRemoteExecutionHostPtyId } from '../../../../../shared/remote-execution-host-pty-id' import { inspectRuntimeTerminalProcess } from '@/runtime/runtime-terminal-inspection' import { parseAppSshPtyId } from '../../../../../shared/ssh-pty-id' import { dispatchTerminalCommandFinishedEvent } from '@/hooks/terminal-command-finished-event' diff --git a/src/renderer/src/components/terminal-pane/pty-connection/terminal-keydown-fit.ts b/src/renderer/src/components/terminal-pane/pty-connection/terminal-keydown-fit.ts index c2777500770..371a29b2142 100644 --- a/src/renderer/src/components/terminal-pane/pty-connection/terminal-keydown-fit.ts +++ b/src/renderer/src/components/terminal-pane/pty-connection/terminal-keydown-fit.ts @@ -13,7 +13,7 @@ import { registerTerminalSideEffectFactConsumer } from '../terminal-side-effect- import { isAgentTaskCompleteTrackingEnabled } from './agent-task-complete-settings' import { isAgentProcessInspectionCostly } from '../agent-process-inspection-cost' -import { isRemoteExecutionHostPtyId } from '../remote-execution-host-pty' +import { isRemoteExecutionHostPtyId } from '../../../../../shared/remote-execution-host-pty-id' import { retireLiveAgentRowOnConfirmedProcessExit } from './confirmed-process-exit-row-retire' import type { ConnectPanePtySession } from './connect-pane-pty-session' diff --git a/src/renderer/src/components/terminal-pane/remote-execution-host-pty.ts b/src/renderer/src/components/terminal-pane/remote-execution-host-pty.ts deleted file mode 100644 index 16c8147a4f9..00000000000 --- a/src/renderer/src/components/terminal-pane/remote-execution-host-pty.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { isRemoteRuntimePtyId } from '../../../../shared/remote-runtime-pty-id' - -import { parseAppSshPtyId } from '../../../../shared/ssh-pty-id' - -// Why: both remote shapes are answered across a link — paired runtimes -// ("remote:", host-owned buffer) and direct SSH ("ssh:@@", main's -// relay-fed model, whose serialize can block on host RPCs). Silence from either -// is `unverifiable`, never proof of death (docs/reference/ssh-execution-boundary.md). -export function isRemoteExecutionHostPtyId(ptyId: string | null | undefined): boolean { - if (typeof ptyId !== 'string') { - return false - } - return isRemoteRuntimePtyId(ptyId) || parseAppSshPtyId(ptyId) !== null -} diff --git a/src/renderer/src/components/terminal-pane/terminal-hidden-view-parking.ts b/src/renderer/src/components/terminal-pane/terminal-hidden-view-parking.ts index 8ad4ee5d19f..4227380c8bf 100644 --- a/src/renderer/src/components/terminal-pane/terminal-hidden-view-parking.ts +++ b/src/renderer/src/components/terminal-pane/terminal-hidden-view-parking.ts @@ -1,5 +1,5 @@ import { isRemoteRuntimePtyId } from '../../../../shared/remote-runtime-pty-id' -import { isRemoteExecutionHostPtyId } from './remote-execution-host-pty' +import { isRemoteExecutionHostPtyId } from '../../../../shared/remote-execution-host-pty-id' import { getRemoteRuntimePtyEnvironmentId } from '@/runtime/runtime-terminal-stream' import { PTY_SESSION_ID_SEPARATOR } from '../../../../shared/pty-session-id-format' import { parseAppSshPtyId } from '../../../../shared/ssh-pty-id' diff --git a/src/renderer/src/components/terminal-pane/terminal-hidden-worktree-retention.ts b/src/renderer/src/components/terminal-pane/terminal-hidden-worktree-retention.ts index 6bdf2d43607..66bc996cc47 100644 --- a/src/renderer/src/components/terminal-pane/terminal-hidden-worktree-retention.ts +++ b/src/renderer/src/components/terminal-pane/terminal-hidden-worktree-retention.ts @@ -1,5 +1,5 @@ import { isRemoteRuntimePtyId } from '../../../../shared/remote-runtime-pty-id' -import { isRemoteExecutionHostPtyId } from './remote-execution-host-pty' +import { isRemoteExecutionHostPtyId } from '../../../../shared/remote-execution-host-pty-id' import { PTY_SESSION_ID_SEPARATOR } from '../../../../shared/pty-session-id-format' import { terminalProviderHasAuthoritativeSnapshot } from '../terminal/terminal-provider-snapshot-capability' import { diff --git a/src/renderer/src/components/terminal-pane/terminal-parked-tab-watchers.ts b/src/renderer/src/components/terminal-pane/terminal-parked-tab-watchers.ts index d088fbe41ad..ac92c786601 100644 --- a/src/renderer/src/components/terminal-pane/terminal-parked-tab-watchers.ts +++ b/src/renderer/src/components/terminal-pane/terminal-parked-tab-watchers.ts @@ -7,7 +7,7 @@ */ import { isTerminalLeafId } from '../../../../shared/stable-pane-id' import { useAppStore } from '@/store' -import { isRemoteExecutionHostPtyId } from './remote-execution-host-pty' +import { isRemoteExecutionHostPtyId } from '../../../../shared/remote-execution-host-pty-id' import { discardPreHandlerPtyState } from './pty-pre-handler-buffer' import { terminalProviderHasAuthoritativeSnapshot } from '../terminal/terminal-provider-snapshot-capability' import { diff --git a/src/renderer/src/components/terminal-pane/remote-execution-host-pty.test.ts b/src/shared/remote-execution-host-pty-id.test.ts similarity index 72% rename from src/renderer/src/components/terminal-pane/remote-execution-host-pty.test.ts rename to src/shared/remote-execution-host-pty-id.test.ts index f87129c88f9..594dcb0f5da 100644 --- a/src/renderer/src/components/terminal-pane/remote-execution-host-pty.test.ts +++ b/src/shared/remote-execution-host-pty-id.test.ts @@ -1,9 +1,9 @@ import { describe, expect, it } from 'vitest' -import { toRemoteRuntimePtyId } from '../../../../shared/remote-runtime-pty-id' -import { toAppSshPtyId } from '../../../../shared/ssh-pty-id' +import { toRemoteRuntimePtyId } from './remote-runtime-pty-id' +import { toAppSshPtyId } from './ssh-pty-id' -import { isRemoteExecutionHostPtyId } from './remote-execution-host-pty' +import { isRemoteExecutionHostPtyId } from './remote-execution-host-pty-id' describe('isRemoteExecutionHostPtyId', () => { it('accepts paired-runtime ptys with and without an owner environment', () => { @@ -11,6 +11,12 @@ describe('isRemoteExecutionHostPtyId', () => { expect(isRemoteExecutionHostPtyId(toRemoteRuntimePtyId('term_1'))).toBe(true) }) + // Why: decoding fails on a malformed owner, but the id still names a remote pty. + it('keeps a malformed or newer-format remote id remote', () => { + expect(isRemoteExecutionHostPtyId('remote:%E0%A4%A@@term_1')).toBe(true) + expect(isRemoteExecutionHostPtyId('remote:v2|env|term_1')).toBe(true) + }) + it('accepts direct-SSH app pty ids', () => { expect(isRemoteExecutionHostPtyId(toAppSshPtyId('target-1', 'pty-1'))).toBe(true) }) diff --git a/src/shared/remote-execution-host-pty-id.ts b/src/shared/remote-execution-host-pty-id.ts index c76ada39e03..e0c0aeed737 100644 --- a/src/shared/remote-execution-host-pty-id.ts +++ b/src/shared/remote-execution-host-pty-id.ts @@ -1,14 +1,16 @@ -import { parseRemoteRuntimePtyId } from './remote-runtime-pty-id' +import { isRemoteRuntimePtyId } from './remote-runtime-pty-id' import { parseAppSshPtyId } from './ssh-pty-id' /** * Whether the process behind this pty runs on an execution host other than this machine — * a paired runtime environment or an app SSH target. * - * Deliberately broader than the inspection module's private remote check, which only counts a - * `remote:` id that carries an owner environment id. An owner-less `remote:` still runs - * somewhere else, and treating it as local is how remote work becomes invisible to a guard. + * Why prefix-only for `remote:`: a malformed, owner-less or newer-format id still runs off + * this host, so silence from it is `unverifiable`, never local (docs/reference/ssh-execution-boundary.md). */ -export function isRemoteExecutionHostPtyId(ptyId: string): boolean { - return parseRemoteRuntimePtyId(ptyId) !== null || parseAppSshPtyId(ptyId) !== null +export function isRemoteExecutionHostPtyId(ptyId: string | null | undefined): boolean { + if (typeof ptyId !== 'string') { + return false + } + return isRemoteRuntimePtyId(ptyId) || parseAppSshPtyId(ptyId) !== null }