refactor(pty): one remote-runtime PTY id predicate (#26930)

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
This commit is contained in:
OrcaWin
2026-10-09 11:46:12 -07:00
committed by GitHub
co-authored by m4air
parent 54b623b6a3
commit fbcf0edc60
50 changed files with 108 additions and 93 deletions
+2 -1
View File
@@ -1,3 +1,4 @@
import { isRemoteRuntimePtyId } from '../../../../shared/remote-runtime-pty-id'
import { getPtyIpc } from '../../pty-host-bindings'
import { parseAppSshPtyId } from '../../../providers/ssh-pty-id'
import {
@@ -34,7 +35,7 @@ async function findLocalPaneRootPid(deps: Deps, id: unknown): Promise<number | n
// Why local only: SSH and WSL panes run Codex on another host, which must answer for itself.
if (
typeof id !== 'string' ||
id.startsWith('remote:') ||
isRemoteRuntimePtyId(id) ||
parseAppSshPtyId(id) ||
(ptyOwnership.get(id) ?? null) !== null
) {
+5 -4
View File
@@ -1,3 +1,4 @@
import { isRemoteRuntimePtyId } from '../../../../shared/remote-runtime-pty-id'
import { getPtyIpc } from '../../pty-host-bindings'
import { parseAppSshPtyId } from '../../../providers/ssh-pty-id'
import { inspectPtyProviderProcessForRenderer } from '../../../providers/pty-process-inspection'
@@ -104,7 +105,7 @@ export function installPtyInspectIpcHandlers(deps: {
typeof value !== 'string' ||
value.length === 0 ||
value.length > 512 ||
value.startsWith('remote:') ||
isRemoteRuntimePtyId(value) ||
parseAppSshPtyId(value)
) {
return false
@@ -144,7 +145,7 @@ export function installPtyInspectIpcHandlers(deps: {
)
ipcMain.handle('pty:hasPty', async (_event, args: { id: string }): Promise<boolean | null> => {
if (typeof args?.id !== 'string' || args.id.startsWith('remote:')) {
if (typeof args?.id !== 'string' || isRemoteRuntimePtyId(args.id)) {
// Why: same routing hazard pty:kill guards against — ptyOwnership never holds
// a runtime terminal handle and parseAppSshPtyId ignores it, so the lookup
// falls through to the local provider and its "not in my table" reads as an
@@ -203,7 +204,7 @@ export function installPtyInspectIpcHandlers(deps: {
}
) => {
// Why: same routing hazard as pty:hasPty — an unroutable id must read as client-only unverifiable, not as a local-provider answer or a raised IPC error.
if (typeof args?.id !== 'string' || !args.id || args.id.startsWith('remote:')) {
if (typeof args?.id !== 'string' || !args.id || isRemoteRuntimePtyId(args.id)) {
return clientOnlyUnverifiableInspection('terminal_gone')
}
// Why: the pre-swap LocalPtyProvider does not own restored daemon ids, so
@@ -309,7 +310,7 @@ export function installPtyInspectIpcHandlers(deps: {
async (_event, args: { ptyId?: unknown }): Promise<void> => {
if (
typeof args?.ptyId !== 'string' ||
!args.ptyId.startsWith('remote:') ||
!isRemoteRuntimePtyId(args.ptyId) ||
args.ptyId.length > 512
) {
return
+2 -1
View File
@@ -1,3 +1,4 @@
import { isRemoteRuntimePtyId } from '../../../../shared/remote-runtime-pty-id'
import { getPtyIpc } from '../../pty-host-bindings'
import type { Store } from '../../../persistence'
import type { OrcaRuntimeService } from '../../../runtime/orca-runtime'
@@ -52,7 +53,7 @@ async function stopRendererOwnedPtyAs(
args: { id: string; keepHistory?: boolean },
intentionalStop: TerminalIntentionalStopKind | null
): Promise<void> {
if (typeof args?.id !== 'string' || !args.id || args.id.startsWith('remote:')) {
if (typeof args?.id !== 'string' || !args.id || isRemoteRuntimePtyId(args.id)) {
// Why: runtime terminal handles belong to terminal.close; unowned PTY routing could target the local provider.
throw new Error('Invalid PTY provider id')
}
+3 -2
View File
@@ -1,3 +1,4 @@
import { isRemoteRuntimePtyId } from '../../../../shared/remote-runtime-pty-id'
import type { IPtyProvider } from '../../../providers/types'
import { LocalPtyProvider } from '../../../providers/local-pty-provider'
import { parseAppSshPtyId } from '../../../providers/ssh-pty-id'
@@ -88,7 +89,7 @@ export async function probePtyLivenessFromRuntimeController(
try {
// Why: no locally routed provider can authoritatively answer for a
// remote host's PTY, so remote-scoped ids stay unknown, never absent.
if (ptyId.startsWith('remote:')) {
if (isRemoteRuntimePtyId(ptyId)) {
return null
}
const connectionId = ptyOwnership.get(ptyId) ?? parseAppSshPtyId(ptyId)?.connectionId
@@ -235,7 +236,7 @@ export function hasPtyFromRuntimeController(
try {
// Why: no locally routed provider can authoritatively answer for a
// remote host's PTY, so remote-scoped ids stay unknown, never absent.
if (ptyId.startsWith('remote:')) {
if (isRemoteRuntimePtyId(ptyId)) {
return null
}
const connectionId = ptyOwnership.get(ptyId) ?? parseAppSshPtyId(ptyId)?.connectionId
@@ -1,4 +1,5 @@
// @ts-nocheck -- mechanically split from OrcaRuntimeService; behavior is covered by AST equivalence and characterization tests.
import { isRemoteRuntimePtyId } from '../../shared/remote-runtime-pty-id'
import { OrcaRuntimeWithRefreshFloatingWorkspacePtyLiveness } from './orca-runtime-refresh-floating-workspace-pty-liveness'
import { writeOrchestrationPointerWithSettlement } from './orchestration/mailbox-pointer-pty-write'
import type { WriteSettlement } from '../../shared/pty-write-settlement'
@@ -127,7 +128,7 @@ export class OrcaRuntimeWithWriteOrchestrationPointerPty extends OrcaRuntimeWith
provenLivePtyIds !== null &&
leaf.ptyId !== null &&
!provenLivePtyIds.has(leaf.ptyId) &&
!leaf.ptyId.startsWith('remote:') &&
!isRemoteRuntimePtyId(leaf.ptyId) &&
parseAppSshPtyId(leaf.ptyId) === null &&
this.ptyController?.hasPty?.(leaf.ptyId) !== true
return {
@@ -1,5 +1,5 @@
import { isRemoteRuntimePtyId } from '../../../../shared/remote-runtime-pty-id'
import type { PtyTransport } from './pty-transport-types'
import { isRemoteRuntimePtyId } from './pty-connection/paired-parked-terminal-restore'
/**
* Releases a pane's transport for a restart and returns the PTY its replacement spawn must name.
@@ -1,3 +1,4 @@
import { isRemoteRuntimePtyId } from '../../../../../shared/remote-runtime-pty-id'
import { waitForTerminalReplayWritesParsed } from '../replay-guard'
import {
POST_REPLAY_MODE_RESET,
@@ -12,7 +13,6 @@ import {
shouldSkipAltFrameForWidthMismatch
} from '../terminal-snapshot-replay-paint'
import { isRemoteRuntimePtyId } from './paired-parked-terminal-restore'
import { restoredSnapshotPaintsPrintableContent } from '../restored-snapshot-coverage'
import { resolveSshReconnectModelPaint } from './resolve-ssh-reconnect-model-paint'
import { fitReattachedPaneToGrid, noteReattachAltFrameSkip } from './reattach-grid-fit'
@@ -1,9 +1,9 @@
import { isRemoteRuntimePtyId } from '../../../../../shared/remote-runtime-pty-id'
import { useAppStore } from '@/store'
import { isRuntimeOwnedSshTargetId } from '../../../../../shared/execution-host'
import { resolveSshPaneConnectGate } from '../ssh-pane-connect-gate'
import { waitForUserInitiatedSshConnect, waitForSshConnection } from './ssh-session-connect'
import { isRemoteRuntimePtyId } from './paired-parked-terminal-restore'
import { isSshSessionGoneError } from './pty-connect-limits'
import { toProcessExitStartup } from './process-exit-startup'
@@ -1,3 +1,4 @@
import { isRemoteRuntimePtyId } from '../../../../../shared/remote-runtime-pty-id'
import { scheduleRuntimeGraphSync } from '@/runtime/sync-runtime-graph'
import { useAppStore } from '@/store'
import { isWebTerminalSurfaceTabId } from '@/runtime/web-terminal-surface-id'
@@ -9,7 +10,6 @@ import {
recordPtyConnectDiagnostic
} from './pty-connect-limits'
import {
isRemoteRuntimePtyId,
canRestorePairedParkedTerminal,
isSessionOwnedByWorktree
} from './paired-parked-terminal-restore'
@@ -1,6 +1,6 @@
import { isRemoteRuntimePtyId } from '../../../../../shared/remote-runtime-pty-id'
import { warnTerminalLifecycleAnomaly } from '../terminal-lifecycle-diagnostics'
import { isSshSessionGoneError, recordPtyConnectDiagnostic } from './pty-connect-limits'
import { isRemoteRuntimePtyId } from './paired-parked-terminal-restore'
import { toProcessExitStartup } from './process-exit-startup'
import { recoverUnverifiableDirectSshReattach } from './direct-ssh-reattach-recovery'
import type { ConnectPanePtySession } from './connect-pane-pty-session'
@@ -1,3 +1,4 @@
import { isRemoteRuntimePtyId } from '../../../../../shared/remote-runtime-pty-id'
import { reportWorkerTerminalUserInput } from '@/lib/worker-terminal-takeover-report'
import { useAppStore } from '@/store'
import { getLocalProjectExecutionRuntimeContext } from '@/lib/local-preflight-context'
@@ -28,7 +29,6 @@ import {
} from '../renderer-owned-agent-status-registry'
import { DIRECT_SSH_PANE_RETRY_SETTLEMENT_TIMEOUT_MS } from './pty-connect-limits'
import { isRemoteRuntimePtyId } from './paired-parked-terminal-restore'
import { resolveLatestAgentDoneStartedAt } from './agent-done-started-at'
import { rendererAgentStatusObservations } from '@/lib/renderer-agent-status-observations'
@@ -1,6 +1,6 @@
import { isRemoteRuntimePtyId } from '../../../../../shared/remote-runtime-pty-id'
import type { SleepingAgentSessionRecord } from '../../../../../shared/agent-session-resume'
import type { ColdRestoreAgentResumeStartup } from './fresh-spawn-types'
import { isRemoteRuntimePtyId } from './paired-parked-terminal-restore'
// Why not activationTreatsNoteAsFinished: a live+done note is a finished turn's idle anchor for a running pane, not sleep.
export function isHibernationDoneRecord(record: SleepingAgentSessionRecord): boolean {
@@ -1,3 +1,4 @@
import { isRemoteRuntimePtyId } from '../../../../../shared/remote-runtime-pty-id'
import { recordTerminalFreezeBreadcrumb } from '../terminal-freeze-breadcrumbs'
import { redactPtyIdForDiagnostics } from '../../../../../shared/pty-delivery-diagnostics'
import {
@@ -26,7 +27,6 @@ import {
shouldWritePtyOutputForeground,
consumeInactiveForegroundImmediateBudget
} from './foreground-output-scan'
import { isRemoteRuntimePtyId } from './paired-parked-terminal-restore'
import type { ConnectPanePtySession } from './connect-pane-pty-session'
@@ -1,3 +1,4 @@
import { isRemoteRuntimePtyId } from '../../../../../shared/remote-runtime-pty-id'
import { useAppStore } from '@/store'
import { PROCESS_BOUNDARY_GROUND } from '../../../../../shared/terminal-mode-reset-profiles'
import { hasPtySerializer } from '../pty-buffer-serializer'
@@ -7,7 +8,6 @@ import { settleSpawnThatLeftPaneUnbound } from './unbound-pane-spawn-recovery'
import { STARTUP_CWD_FALLBACK_NOTICE } from './startup-cwd-fallback-notice'
import { pendingSpawnByPaneKey, pendingSpawnGenerationByPaneKey } from './pty-connect-limits'
import { shouldWritePtyOutputForeground } from './foreground-output-scan'
import { isRemoteRuntimePtyId } from './paired-parked-terminal-restore'
import { toProcessExitStartup } from './process-exit-startup'
import type {
PendingStartupCommand,
@@ -1,3 +1,4 @@
import { isRemoteRuntimePtyId } from '../../../../../shared/remote-runtime-pty-id'
import { recordTerminalFreezeBreadcrumb } from '../terminal-freeze-breadcrumbs'
import { redactPtyIdForDiagnostics } from '../../../../../shared/pty-delivery-diagnostics'
import { cancelScheduledHiddenOutputRestore } from '../hidden-output-restore-scheduler'
@@ -8,7 +9,6 @@ import {
HIDDEN_OUTPUT_RESTORE_REMOTE_REARM_MAX
} from './hidden-output-restore-limits'
import { shouldWritePtyOutputForeground } from './foreground-output-scan'
import { isRemoteRuntimePtyId } from './paired-parked-terminal-restore'
import type { ConnectPanePtySession } from './connect-pane-pty-session'
@@ -1,3 +1,4 @@
import { isRemoteRuntimePtyId } from '../../../../../shared/remote-runtime-pty-id'
import { safeFit, safeFitAndThen } from '@/lib/pane-manager/pane-tree-ops'
import { getFitOverrideForPty } from '@/lib/pane-manager/mobile-fit-overrides'
import { waitForTerminalReplayWritesParsed } from '../replay-guard'
@@ -24,7 +25,6 @@ import {
import { HIDDEN_OUTPUT_RESTORE_UNAVAILABLE_WARNING } from './hidden-output-restore-limits'
import { shouldWritePtyOutputForeground } from './foreground-output-scan'
import { isRemoteRuntimePtyId } from './paired-parked-terminal-restore'
import { restoredSnapshotPaintsPrintableContent } from '../restored-snapshot-coverage'
import { recordTerminalFreezeBreadcrumb } from '../terminal-freeze-breadcrumbs'
@@ -1,9 +1,9 @@
import { isRemoteRuntimePtyId } from '../../../../../shared/remote-runtime-pty-id'
import { isHostAnsweredSnapshotRetryCause } from '@/runtime/remote-runtime-terminal-multiplexer'
import { onTerminalScrollIntentFollowOutput } from '@/lib/pane-manager/terminal-scroll-intent'
import { shouldWritePtyOutputForeground } from './foreground-output-scan'
import { readE2eHiddenSnapshotOverride } from './e2e-terminal-pty-harness'
import { isRemoteRuntimePtyId } from './paired-parked-terminal-restore'
import type { PtyBufferSnapshot } from '../pty-transport'
import type { ConnectPanePtySession } from './connect-pane-pty-session'
@@ -1,16 +1,11 @@
import { useAppStore } from '@/store'
import { TERMINAL_PAIRED_PARKING_RUNTIME_CAPABILITY } from '../../../../../shared/protocol-version'
import { getRemoteRuntimePtyEnvironmentId } from '@/runtime/runtime-terminal-stream'
import { REMOTE_PTY_ID_PREFIX } from './pty-connect-limits'
import {
isRuntimeHostContactRevoked,
lastVerifiedRuntimeStatus
} from '../../../../../shared/runtime-host-status'
export function isRemoteRuntimePtyId(ptyId: string | null | undefined): boolean {
return typeof ptyId === 'string' && ptyId.startsWith(REMOTE_PTY_ID_PREFIX)
}
export function canRestorePairedParkedTerminal(ptyId: string): boolean {
const environmentId = getRemoteRuntimePtyEnvironmentId(ptyId)
if (environmentId === null) {
@@ -1,3 +1,4 @@
import { isRemoteRuntimePtyId } from '../../../../../shared/remote-runtime-pty-id'
import { captureNotificationTransportOwner } from '@/attention/notification-subject-owner'
import { scheduleRuntimeGraphSync } from '@/runtime/sync-runtime-graph'
import { useAppStore } from '@/store'
@@ -12,7 +13,6 @@ import {
isAgentTaskCompleteNotificationEnabled,
isAgentTaskCompleteTrackingEnabled
} from './agent-task-complete-settings'
import { isRemoteRuntimePtyId } from './paired-parked-terminal-restore'
import { shouldIgnoreStalePanePtyLayoutBinding } from './pane-pty-layout-binding'
import type { ConnectPanePtySession } from './connect-pane-pty-session'
@@ -1,3 +1,4 @@
import { isRemoteRuntimePtyId } from '../../../../../shared/remote-runtime-pty-id'
import { useAppStore } from '@/store'
import type { SessionRestoredBannerReason } from '../session-restored-banner-pane-state'
import { hasPtySerializer } from '../pty-buffer-serializer'
@@ -12,7 +13,6 @@ import { writeTerminalPastePtyInput } from '../terminal-pty-paste-writer'
import { createPasteReadinessTimeoutNotice } from '@/lib/launch-agent-paste-timeout-notice'
import { STARTUP_DRAFT_PASTE_QUIET_MS } from './pty-connect-limits'
import { isRemoteRuntimePtyId } from './paired-parked-terminal-restore'
import type { ConnectPanePtySession } from './connect-pane-pty-session'
@@ -1,3 +1,4 @@
import { isRemoteRuntimePtyId } from '../../../../../shared/remote-runtime-pty-id'
import { RESET_GRAPHIC_RENDITION } from '../../../../../shared/terminal-mode-reset-profiles'
import { redactPtyIdForDiagnostics } from '../../../../../shared/pty-delivery-diagnostics'
import { writeTerminalOutput } from '@/lib/pane-manager/pane-terminal-output-scheduler'
@@ -7,7 +8,6 @@ import type { PtyBufferSnapshot } from '../pty-transport'
import type { ConnectPanePtySession } from './connect-pane-pty-session'
import { PARK_REVEAL_NO_HOST_IMAGE_WARNING } from './hidden-output-restore-limits'
import type { HiddenOutputSnapshotResult } from './hidden-output-snapshot-serialize'
import { isRemoteRuntimePtyId } from './paired-parked-terminal-restore'
/** Which restore budget an unverifiable probe is charged to; null when the loop keeps its own count (legacy hosts). */
export type ParkRevealRetryLedger = 'host' | 'local' | null
@@ -24,7 +24,6 @@ export function isSshSessionGoneError(error: unknown): boolean {
}
// Why: relay requests expire at 30s; leave one second for their fallback before re-arming locally.
export const DIRECT_SSH_PANE_RETRY_SETTLEMENT_TIMEOUT_MS = 31_000
export const REMOTE_PTY_ID_PREFIX = 'remote:'
export const PTY_CONNECT_DIAG_LIMIT = 200
export const MANUAL_AGENT_COMMAND_MAX_CHARS = 4096
export const STARTUP_DRAFT_PASTE_QUIET_MS = 1500
@@ -1,3 +1,4 @@
import { isRemoteRuntimePtyId } from '../../../../../shared/remote-runtime-pty-id'
import type { ManagedPaneInternal } from '@/lib/pane-manager/pane-manager-types'
import { subscribeToTerminalInputData } from '../terminal-user-input-signal'
import { installTerminalImeCompositionRoute } from '../terminal-ime-composition-route'
@@ -20,7 +21,6 @@ import {
import { FOREGROUND_GRID_DRIFT_CHECK_MIN_MS } from './foreground-output-budgets'
import { TERMINAL_FOCUS_IN_SEQUENCE, TERMINAL_FOCUS_OUT_SEQUENCE } from './foreground-output-scan'
import { isRemoteRuntimePtyId } from './paired-parked-terminal-restore'
import { isCodexPaneStale } from './codex-pane-stale'
import { installTerminalSelectionFitGuard } from '../terminal-selection-fit-guard'
import { initializePaneGeometry, readPaneSize } from './read-pane-size'
@@ -1,3 +1,4 @@
import { isRemoteRuntimePtyId } from '../../../../../shared/remote-runtime-pty-id'
import { useAppStore } from '@/store'
import { createIpcPtyTransport } from '../pty-transport'
import { createRemoteRuntimePtyTransport } from '../remote-runtime-pty-transport'
@@ -19,7 +20,6 @@ import {
installTerminalCapabilityReplyHandlers
} from '../terminal-capability-replies'
import { isRemoteRuntimePtyId } from './paired-parked-terminal-restore'
import { TRANSPORT_CONNECT_SETTLE_GRACE_MS } from './pty-connect-limits'
import { buildPaneTransportOptions } from './pane-transport-options'
import { buffersInputOnlyForSshReattach } from './ssh-reattach-input-buffering'
@@ -1,3 +1,4 @@
import { isRemoteRuntimePtyId } from '../../../../../shared/remote-runtime-pty-id'
import type { ManagedPaneInternal } from '@/lib/pane-manager/pane-manager-types'
import { safeFit } from '@/lib/pane-manager/pane-tree-ops'
import { requestStablePaneFit } from '@/lib/pane-manager/pane-fit-resize-observer'
@@ -8,7 +9,6 @@ import { shouldClaimRemoteDesktopViewport } from '../remote-desktop-viewport-cla
import { deferTerminalGeometryMutationDuringRebuild } from '@/lib/pane-manager/terminal-scroll-intent-rebuild'
import { waitForStableStartupGrid } from '../terminal-startup-grid-settle'
import { isRemoteRuntimePtyId } from './paired-parked-terminal-restore'
import { isSetupSplitGeometryReady } from './setup-split-geometry'
import type { ConnectPanePtySession } from './connect-pane-pty-session'
@@ -1,7 +1,7 @@
import { isRemoteRuntimePtyId } from '../../../../../shared/remote-runtime-pty-id'
import { safeFitAndThen } from '@/lib/pane-manager/pane-tree-ops'
import { getFitOverrideForPty } from '@/lib/pane-manager/mobile-fit-overrides'
import { isRemoteRuntimePtyId } from './paired-parked-terminal-restore'
import type { ReattachPayloadContext } from './reattach-payload-context'
import type { ReattachPayloadSession } from './reattach-payload-session'
@@ -1,3 +1,4 @@
import { isRemoteRuntimePtyId } from '../../../../../shared/remote-runtime-pty-id'
import { scheduleRuntimeGraphSync } from '@/runtime/sync-runtime-graph'
import type { PtyBufferSnapshot, PtyConnectResult } from '../pty-transport'
import { warnTerminalLifecycleAnomaly } from '../terminal-lifecycle-diagnostics'
@@ -9,7 +10,6 @@ import { parseAppSshPtyId } from '../../../../../shared/ssh-pty-id'
import { resolveHiddenRestoreScrollbackRows } from '../terminal-hidden-restore-scrollback'
import { shouldIgnoreStalePanePtyLayoutBinding } from './pane-pty-layout-binding'
import { isRemoteRuntimePtyId } from './paired-parked-terminal-restore'
import { hasEmptyReattachRetireEvidence } from './empty-reattach-retire-evidence'
import type { ColdRestoreAgentResumeStartup } from './fresh-spawn-types'
@@ -1,3 +1,4 @@
import { isRemoteRuntimePtyId } from '../../../../../shared/remote-runtime-pty-id'
import { createTerminalZeroDimensionsMessage } from '../../../../../shared/terminal-zero-dimensions-diagnostic'
import { isWorktreeRemovalFenceError } from '../../../../../shared/worktree/removal-fence-error'
import { safeFit } from '@/lib/pane-manager/pane-tree-ops'
@@ -5,7 +6,6 @@ import { useAppStore } from '@/store'
import { createCodexBackfillErrorDetector } from '../codex-backfill-error-detector'
import { hasWorktreeSleepIntent, onWorktreeSleepIntentCleared } from '@/lib/worktree-sleep-intent'
import { isRemoteRuntimePtyId } from './paired-parked-terminal-restore'
import { recordPtyConnectDiagnostic } from './pty-connect-limits'
import { findTerminalTabForPane } from './terminal-tab-id'
@@ -10,7 +10,7 @@ import { discardTerminalOutput } from '@/lib/pane-manager/pane-terminal-output-s
import { getProviderSessionClaimKey } from '@/lib/sleeping-agent-pane-ownership'
import { releaseRendererPtyVisibilityClaim } from '../pty-renderer-delivery-claims'
import { REMOTE_PTY_ID_PREFIX } from './pty-connect-limits'
import { isRemoteRuntimePtyId } from '../../../../../shared/remote-runtime-pty-id'
import { SHIFT_ENTER_RECONFIRM_IDLE_MS } from './foreground-output-scan'
import type { PanePtyBinding } from './pane-pty-binding'
@@ -51,7 +51,7 @@ export function installSessionReconcileDispose(session: ConnectPanePtySession):
if (
!requestedPtyId ||
requestedPtyId === session.handledExitPtyId ||
requestedPtyId.startsWith(REMOTE_PTY_ID_PREFIX) ||
isRemoteRuntimePtyId(requestedPtyId) ||
session.transport.getConnectionId?.() != null
) {
return
@@ -1,3 +1,4 @@
import { isRemoteRuntimePtyId } from '../../../../../shared/remote-runtime-pty-id'
import { makePaneKey } from '../../../../../shared/stable-pane-id'
import { useAppStore } from '@/store'
import { safeFit } from '@/lib/pane-manager/pane-tree-ops'
@@ -12,7 +13,6 @@ import { registerTerminalSideEffectFactConsumer } from '../terminal-side-effect-
import { isAgentTaskCompleteTrackingEnabled } from './agent-task-complete-settings'
import { isAgentProcessInspectionCostly } from '../agent-process-inspection-cost'
import { isRemoteRuntimePtyId } from './paired-parked-terminal-restore'
import { isRemoteExecutionHostPtyId } from '../remote-execution-host-pty'
import type { ConnectPanePtySession } from './connect-pane-pty-session'
@@ -1,5 +1,6 @@
import type { ExecutionHostId } from '../../../../shared/execution-host'
import { LOCAL_EXECUTION_HOST_ID } from '../../../../shared/execution-host'
import { isRemoteRuntimePtyId } from '../../../../shared/remote-runtime-pty-id'
import { getConnectionIdFromState } from '@/lib/connection-context'
import {
getExecutionHostIdForWorktree,
@@ -9,8 +10,6 @@ import { isLocalNativeWindowsConpty } from '@/lib/pane-manager/windows-pty-compa
import type { PaneCwdMap } from './resolve-split-cwd'
import type { PtyTransport } from './pty-transport-types'
const REMOTE_RUNTIME_PTY_ID_PREFIX = 'remote:'
type TerminalTabShellState = {
tabsByWorktree: Record<
string,
@@ -39,10 +38,6 @@ type TerminalCtrlArrowConptyArgs = {
transport: TerminalCtrlArrowConptyTransport | null
}
function isRemoteRuntimePtyId(ptyId: string): boolean {
return ptyId.startsWith(REMOTE_RUNTIME_PTY_ID_PREFIX)
}
export function isLocalWindowsConptyPaneForCtrlArrow({
isWindows,
userAgent,
@@ -59,7 +54,7 @@ export function isLocalWindowsConptyPaneForCtrlArrow({
}
const ptyId = transport?.getPtyId() ?? null
if (ptyId !== null && isRemoteRuntimePtyId(ptyId)) {
if (isRemoteRuntimePtyId(ptyId)) {
return false
}
@@ -4,7 +4,7 @@
// this routes such panes through the same exit teardown an observed exit runs.
// See design-docs/terminal-dead-pane-on-bg-exit.md.
const REMOTE_PTY_ID_PREFIX = 'remote:'
import { isRemoteRuntimePtyId } from '../../../../shared/remote-runtime-pty-id'
/**
* A pane binding that exposes its bound transport identity plus a reconcile
@@ -42,7 +42,7 @@ export function shouldReconcileDeadSession(args: {
if (ptyId === null || ptyId === undefined) {
return false
}
if (ptyId.startsWith(REMOTE_PTY_ID_PREFIX)) {
if (isRemoteRuntimePtyId(ptyId)) {
return false
}
// Why: only local/daemon-backed ids (connectionId null/undefined) are
@@ -1,3 +1,4 @@
import { isRemoteRuntimePtyId } from '../../../../shared/remote-runtime-pty-id'
import type { Terminal } from '@xterm/xterm'
import type { TerminalLayoutSnapshot, TerminalTab } from '../../../../shared/terminal-tab-types'
import type { PtyPaneStartup } from './pty-connection-types'
@@ -46,7 +47,7 @@ export function reportActiveRendererPtyForPane(
): void {
for (const [paneId, transport] of paneTransports) {
const ptyId = transport.getPtyId()
if (!ptyId || ptyId.startsWith('remote:')) {
if (!ptyId || isRemoteRuntimePtyId(ptyId)) {
continue
}
window.api.pty.setActiveRendererPty?.(ptyId, activePaneId === paneId)
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest'
import { isRemoteRuntimePastePtyId, resolveTerminalPasteRuntime } from './terminal-paste-runtime'
import { resolveTerminalPasteRuntime } from './terminal-paste-runtime'
describe('terminal paste runtime', () => {
it('uses the platform-local runtime when no SSH identity is available', () => {
@@ -244,10 +244,4 @@ describe('terminal paste runtime', () => {
kind: 'remote-runtime'
})
})
it('recognizes remote runtime PTY ids', () => {
expect(isRemoteRuntimePastePtyId('remote:terminal-1')).toBe(true)
expect(isRemoteRuntimePastePtyId('pty-1')).toBe(false)
expect(isRemoteRuntimePastePtyId(null)).toBe(false)
})
})
@@ -1,7 +1,6 @@
import type { TerminalPasteRuntime } from './terminal-paste-model'
import { parseWslUncPath } from '../../../../shared/wsl-paths'
const REMOTE_PTY_ID_PREFIX = 'remote:'
import { isRemoteRuntimePtyId } from '../../../../shared/remote-runtime-pty-id'
type TerminalPasteRuntimeTransport = {
getConnectionId?: () => string | null | undefined
@@ -34,7 +33,7 @@ export function resolveTerminalPasteRuntime({
}: ResolveTerminalPasteRuntimeArgs): TerminalPasteRuntime {
const windowsConpty = isWindowsConpty === undefined ? {} : { isWindowsConpty }
if (isRemoteRuntimePastePtyId(ptyId)) {
if (isRemoteRuntimePtyId(ptyId)) {
return { platform, runtimeKey: `remote:${ptyId}`, kind: 'remote-runtime', ...windowsConpty }
}
@@ -62,10 +61,6 @@ export function resolveTerminalPasteRuntime({
return { platform, runtimeKey: `local:${platform}`, kind: 'local', ...windowsConpty }
}
export function isRemoteRuntimePastePtyId(ptyId: string | null | undefined): boolean {
return typeof ptyId === 'string' && ptyId.startsWith(REMOTE_PTY_ID_PREFIX)
}
function resolveWslRuntimeKey(
metadata:
| {
@@ -1,3 +1,4 @@
import { isRemoteRuntimePtyId } from '../../../../shared/remote-runtime-pty-id'
import { useEffect, useRef } from 'react'
import {
FOCUS_TERMINAL_PANE_EVENT,
@@ -52,7 +53,7 @@ function reportRendererPtyVisibility(
): void {
for (const transport of paneTransports.values()) {
const ptyId = transport.getPtyId()
if (!ptyId || ptyId.startsWith('remote:')) {
if (!ptyId || isRemoteRuntimePtyId(ptyId)) {
// Why: remote-runtime PTYs use a relay path outside main's local
// renderer-visibility registry, so reporting them here is misleading.
continue
@@ -190,7 +191,7 @@ export function useTerminalPaneGlobalEffects({
useEffect(() => {
const ptyId = isActive && isVisible && isWorktreeActive ? activeLeafPtyId : null
if (!ptyId || ptyId.startsWith('remote:')) {
if (!ptyId || isRemoteRuntimePtyId(ptyId)) {
return
}
// Why: main uses this as a scheduler hint only, so the foreground pane's
@@ -1,3 +1,4 @@
import { isRemoteRuntimePtyId } from '../../../shared/remote-runtime-pty-id'
import { parseAppSshPtyId } from '../../../shared/ssh-pty-id'
import { parsePaneKey } from '../../../shared/stable-pane-id'
import { parseRemoteRuntimePtyId } from '@/runtime/runtime-terminal-stream'
@@ -51,7 +52,7 @@ export function resolveAgentStatusConnectionRouting(args: {
}
return { connectionId: null }
}
if (ptyId.startsWith('remote:')) {
if (isRemoteRuntimePtyId(ptyId)) {
return undefined
}
@@ -1,3 +1,4 @@
import { isRemoteRuntimePtyId } from '../../../shared/remote-runtime-pty-id'
import { useAppStore } from '@/store'
import type { AppState } from '@/store/types'
import type { TerminalTab } from '../../../shared/terminal-tab-types'
@@ -113,7 +114,7 @@ export function createAutomationTerminalOwnership(
consumed = true
unsubscribe()
observeTakeover()
if (args.runtimeKind !== 'desktop' || args.ptyId.startsWith('remote:') || userTookOver) {
if (args.runtimeKind !== 'desktop' || isRemoteRuntimePtyId(args.ptyId) || userTookOver) {
return false
}
const state = args.store.getState()
@@ -24,15 +24,12 @@ import type { TerminalInputKind } from '../../../shared/terminal-input-kind'
export type RuntimeTerminalProcessInspection = TerminalProcessInspection
const REMOTE_PTY_ID_PREFIX = 'remote:'
const DESKTOP_RUNTIME_CLIENT = { id: 'orca-desktop', type: 'desktop' } as const
function isRuntimePtyInputTooLarge(data: string): boolean | Promise<boolean> {
return isTerminalInputTooLargeWithDeferredMeasurement(data)
}
export function isRemoteRuntimePtyId(ptyId: string): boolean {
return ptyId.startsWith(REMOTE_PTY_ID_PREFIX)
}
export { isRemoteRuntimePtyId } from '../../../shared/remote-runtime-pty-id'
function isRemoteInspectionPtyId(ptyId: string): boolean {
return getRemoteRuntimePtyEnvironmentId(ptyId) !== null || parseAppSshPtyId(ptyId) !== null
@@ -4,7 +4,7 @@ import { isClaudeManagementTitle } from '../../../../shared/agent-detection'
import { isTerminalLeafId, makePaneKey } from '../../../../shared/stable-pane-id'
import { isWebTerminalSurfaceTabId } from '../../../../shared/terminal-surface-id'
import type { Tab } from '../../../../shared/tab-types'
import { parseRemoteRuntimePtyId } from '../runtime-terminal-stream'
import { isRemoteRuntimePtyId } from '../../../../shared/remote-runtime-pty-id'
import {
isNativeChatTabWideFallbackSafe,
nativeChatLaunchAgentForLeaf,
@@ -16,10 +16,6 @@ export function mobileTerminalSurfaceId(parentTabId: string, leafId: string): st
return `${parentTabId}::${leafId}`
}
export function isRemoteRuntimePtyId(ptyId: string | null | undefined): boolean {
return typeof ptyId === 'string' && parseRemoteRuntimePtyId(ptyId) !== null
}
export function isWebOnlyMirroredTerminalTab(
tab: Pick<NonNullable<AppState['tabsByWorktree'][string]>[number], 'id' | 'ptyId'>,
layout: AppState['terminalLayoutsByTabId'][string] | undefined
+2 -1
View File
@@ -1,3 +1,4 @@
import { isRemoteRuntimePtyId } from '../../../../shared/remote-runtime-pty-id'
import type { StateCreator } from 'zustand'
import { toast } from 'sonner'
import type { AppState } from '../types'
@@ -151,7 +152,7 @@ export function createRepoRemovalActions(
for (const tab of tabs) {
killedTabIds.add(tab.id)
for (const ptyId of get().ptyIdsByTabId[tab.id] ?? []) {
if (!ptyId.startsWith('remote:')) {
if (!isRemoteRuntimePtyId(ptyId)) {
window.api.pty.kill(ptyId)
}
}
@@ -1,3 +1,4 @@
import { isRemoteRuntimePtyId } from '../../../../shared/remote-runtime-pty-id'
import type { SleepingAgentSessionRecord } from '../../../../shared/agent-session-resume'
import type { AppState } from '../types'
import {
@@ -228,7 +229,7 @@ export function buildTerminalTabRetirementPlans(
environmentId: remote.environmentId?.trim() || null,
handle: remote.handle
})
} else if (ptyId.startsWith('remote:')) {
} else if (isRemoteRuntimePtyId(ptyId)) {
unroutablePtyIds.push(ptyId)
} else if (providerOwnership.kind !== 'local-or-ssh') {
// Why: HUB-native wake hints are not paired-client PTY ids; wait for pane resolution instead of killing the same-looking local id.
@@ -1,3 +1,4 @@
import { isRemoteRuntimePtyId } from '../../../../shared/remote-runtime-pty-id'
import { parsePaneKey } from '../../../../shared/stable-pane-id'
import {
restorePtyDataHandlersAfterFailedShutdown,
@@ -154,7 +155,7 @@ export function createTerminalPaneHibernationActions(
for (const snapshot of unregisterPtyDataHandlers(rendererShutdownPtyIds) ?? []) {
snapshot.commit?.()
}
} else if (!opts.ptyId.startsWith('remote:')) {
} else if (!isRemoteRuntimePtyId(opts.ptyId)) {
// Why: pty.kill can flush final data before exit; unregister first so stale handlers can't fire phantom notifications during hibernation.
const handlerSnapshots = unregisterPtyDataHandlers(rendererShutdownPtyIds) ?? []
try {
@@ -1,3 +1,4 @@
import { isRemoteRuntimePtyId } from '../../../../shared/remote-runtime-pty-id'
import { parseAppSshPtyId } from '../../../../shared/ssh-pty-id'
import { parseRemoteRuntimePtyId, toRemoteRuntimePtyId } from '@/runtime/runtime-terminal-stream'
import { isTerminalTabPresent } from '../slices/terminal-tab-retirement'
@@ -5,8 +6,7 @@ import type { TerminalSlice, TerminalStoreGet, TerminalStoreSet } from './termin
import {
consumePendingActivationSpawn,
getPendingActivationSpawnCount,
isCurrentDirectSshAuthority,
isRemoteRuntimePtyId
isCurrentDirectSshAuthority
} from './terminal-pty-identities'
import { omitUnverifiedPtyLossTabIds } from './terminal-unverified-pty-loss'
import { clearWorktreeSleepIntent } from '@/lib/worktree-sleep-intent'
@@ -2,13 +2,8 @@ import type { AppState } from '../types'
import type { TerminalLayoutSnapshot, TerminalTab } from '../../../../shared/terminal-tab-types'
import type { DirectSshAuthority } from '../../../../shared/ssh-types'
import { parseLegacyNumericPaneKey, parsePaneKey } from '../../../../shared/stable-pane-id'
import { parseRemoteRuntimePtyId } from '@/runtime/runtime-terminal-stream'
import { resolveDirectSshTerminalWorkspaceKeys } from '../slices/direct-ssh-terminal-workspace-scope'
export function isRemoteRuntimePtyId(ptyId: string | null | undefined): boolean {
return typeof ptyId === 'string' && parseRemoteRuntimePtyId(ptyId) !== null
}
export function isCurrentDirectSshAuthority(
state: AppState,
authority: DirectSshAuthority
@@ -1,11 +1,11 @@
import { isRemoteRuntimePtyId } from '../../../../shared/remote-runtime-pty-id'
import { parseAppSshPtyId } from '../../../../shared/ssh-pty-id'
import { hasWorktreeSleepIntent } from '@/lib/worktree-sleep-intent'
import type { TerminalSlice, TerminalStoreGet, TerminalStoreSet } from './terminal-state'
import {
consumePendingActivationSpawn,
getPendingActivationSpawnCount,
isCurrentDirectSshAuthority,
isRemoteRuntimePtyId
isCurrentDirectSshAuthority
} from './terminal-pty-identities'
export function createTerminalPtyReleaseActions(
@@ -1,3 +1,4 @@
import { isRemoteRuntimePtyId } from '../../../../shared/remote-runtime-pty-id'
import type { TerminalTab } from '../../../../shared/terminal-tab-types'
import {
restorePtyDataHandlersAfterFailedShutdown,
@@ -134,13 +135,13 @@ export function createTerminalShutdownGuardController({
stoppedPtyIds: string[]
failure?: PromiseRejectedResult
}> => {
const localPtyIds = rendererShutdownPtyIds.filter((ptyId) => !ptyId.startsWith('remote:'))
const localPtyIds = rendererShutdownPtyIds.filter((ptyId) => !isRemoteRuntimePtyId(ptyId))
const results = await Promise.allSettled(
localPtyIds.map((ptyId) => window.api.pty.kill(ptyId, { keepHistory: keepIdentifiers }))
)
const stoppedPtyIds = [
...(runtimeEnvironmentId
? rendererShutdownPtyIds.filter((ptyId) => ptyId.startsWith('remote:'))
? rendererShutdownPtyIds.filter((ptyId) => isRemoteRuntimePtyId(ptyId))
: []),
...localPtyIds.filter((_, index) => results[index]?.status === 'fulfilled')
]
+31
View File
@@ -0,0 +1,31 @@
import { describe, expect, it } from 'vitest'
import {
isRemoteRuntimePtyId,
parseRemoteRuntimePtyId,
toRemoteRuntimePtyId
} from './remote-runtime-pty-id'
import { getPtyExecutionHost } from './terminal-execution-host'
describe('isRemoteRuntimePtyId', () => {
it('recognizes minted paired-runtime ids, with and without an owner', () => {
expect(isRemoteRuntimePtyId(toRemoteRuntimePtyId('h1', 'env 1'))).toBe(true)
expect(isRemoteRuntimePtyId(toRemoteRuntimePtyId('h1'))).toBe(true)
})
it('rejects local, SSH and missing ids', () => {
expect(isRemoteRuntimePtyId('pty-1')).toBe(false)
expect(isRemoteRuntimePtyId('ssh:conn@@pty-1')).toBe(false)
expect(isRemoteRuntimePtyId('')).toBe(false)
expect(isRemoteRuntimePtyId(null)).toBe(false)
expect(isRemoteRuntimePtyId(undefined)).toBe(false)
})
// Pins the merge of the prefix-only and parse-based copies: an unparseable `remote:` id still
// runs off this host, matching getPtyExecutionHost's 'foreign' verdict.
it('treats an unparseable remote id as remote, never local', () => {
const malformed = 'remote:%E0%A4%A@@handle'
expect(parseRemoteRuntimePtyId(malformed)).toBeNull()
expect(isRemoteRuntimePtyId(malformed)).toBe(true)
expect(getPtyExecutionHost(malformed)).toBe('foreign')
})
})
+6 -1
View File
@@ -16,8 +16,13 @@ export function toRemoteRuntimePtyId(handle: string, environmentId?: string | nu
return `${REMOTE_PTY_ID_PREFIX}${encodeURIComponent(owner)}${REMOTE_PTY_OWNER_SEPARATOR}${encodeURIComponent(handle)}`
}
// Why prefix-only: a `remote:` id that fails to parse still runs off this host, never locally.
export function isRemoteRuntimePtyId(ptyId: string | null | undefined): boolean {
return typeof ptyId === 'string' && ptyId.startsWith(REMOTE_PTY_ID_PREFIX)
}
export function parseRemoteRuntimePtyId(ptyId: string): RemoteRuntimePtyIdParts | null {
if (!ptyId.startsWith(REMOTE_PTY_ID_PREFIX)) {
if (!isRemoteRuntimePtyId(ptyId)) {
return null
}
const rest = ptyId.slice(REMOTE_PTY_ID_PREFIX.length)
+2 -2
View File
@@ -3,7 +3,7 @@ import {
toSshExecutionHostId,
type ExecutionHostId
} from './execution-host'
import { parseRemoteRuntimePtyId } from './remote-runtime-pty-id'
import { parseRemoteRuntimePtyId, isRemoteRuntimePtyId } from './remote-runtime-pty-id'
import { parseAppSshPtyId } from './ssh-pty-id'
/** 'foreign' = the id proves the PTY runs off this host but cannot name where. */
@@ -30,7 +30,7 @@ export function getPtyExecutionHost(ptyId: string | null | undefined): PtyExecut
? toRuntimeExecutionHostId(environmentId)
: 'foreign'
}
if (ptyId.startsWith('ssh:') || ptyId.startsWith('remote:')) {
if (ptyId.startsWith('ssh:') || isRemoteRuntimePtyId(ptyId)) {
return 'foreign'
}
return null