fix(terminal): park SSH worktrees like local ones (C1 retention, slice A)

SSH ptys were blanket-excluded from hidden-view parking, so a hidden SSH
worktree retained every pane forever (C1: renderer heap climbs to the V8
ceiling). SSH bytes transit local main — fact-mode watchers already cover
them, and main keeps a headless model served over pty:getMainBufferSnapshot
that the SSH reattach path never consulted.

- isParkRestorableTerminalPty: snapshot-backed OR (SSH + policy); threaded
  through both park verdicts, both selectors, watcher coverage, and the
  watcher start guard. Remote-runtime/fail-open/foreign/null unchanged.
- Parked-SSH reveal paints from main's headless model (dimension-matched,
  ~5k rows) and degrades to the relay 100KiB replay unless the snapshot is a
  non-empty source==='headless' payload — never a blank/stale paint.
- Kill switch: settings.terminalSshViewParking (default on).

DESIGN.md records the approved plan and the H1 magnitude non-claim.

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Jinjing
2026-07-28 13:21:53 -07:00
co-authored by Orca
parent b41e813cb5
commit 6362a5c442
11 changed files with 628 additions and 12 deletions
+3
View File
@@ -272,6 +272,7 @@ function Terminal(): React.JSX.Element | null {
const tabsByWorktree = useAppStore((s) => s.tabsByWorktree)
const pendingStartupByTabId = useAppStore((s) => s.pendingStartupByTabId)
const terminalParkingEnabled = useAppStore((s) => s.settings?.terminalHiddenViewParking !== false)
const terminalSshParkingEnabled = useAppStore((s) => s.settings?.terminalSshViewParking !== false)
const terminalTitleSnapshotAuthorityEnabled = useAppStore((s) =>
isMainTerminalSideEffectAuthorityForPty({
settings: s.settings,
@@ -883,6 +884,7 @@ function Terminal(): React.JSX.Element | null {
pendingStartupByTabId,
parkingEnabled: terminalParkingEnabled,
nowMs,
restorePolicy: { sshParkingEnabled: terminalSshParkingEnabled },
...overrides
})
// Why: a worktree with any watcher-uncoverable tab must never park, or it goes silent for bells/titles/completions (sank the first parking attempt).
@@ -931,6 +933,7 @@ function Terminal(): React.JSX.Element | null {
tabsByWorktree,
terminalParkingEnabled,
terminalParkingRevision,
terminalSshParkingEnabled,
workspaceSurfaces
])
// Why: gate on workspaceSessionReady so TerminalPane doesn't mount and spawn a duplicate PTY before reconnectPersistedTerminals() finishes.
@@ -243,6 +243,10 @@ import {
scheduleHiddenOutputRestore
} from './hidden-output-restore-scheduler'
import { resolveHiddenRestoreScrollbackRows } from './terminal-hidden-restore-scrollback'
import {
decideSshReattachPaintSource,
shouldFetchSshReattachModelSnapshot
} from './ssh-reattach-model-restore'
import {
getExecutionHostIdForWorktree,
getSettingsForWorktreeRuntimeOwner
@@ -7614,6 +7618,37 @@ export function connectPanePty(
registerPaneSerializerFor(ptyId)
let reattachPayloadApplied = !hasStructuralReplay
// Why (C1 SSH parking): main's headless model holds ~5k rows for SSH ptys
// while the relay replay is a 100KiB raw-byte tail; prefer the model on
// reveal. Only a non-empty 'headless'-sourced snapshot qualifies — the
// renderer-serializer fallback has no mounted xterm after a park.
const tryApplySshMainModelReattachSnapshot = async (): Promise<boolean> => {
const sshParkingEnabled = useAppStore.getState().settings?.terminalSshViewParking !== false
if (!shouldFetchSshReattachModelSnapshot({ ptyId, sshParkingEnabled })) {
return false
}
const snapshot = await window.api.pty
.getMainBufferSnapshot(ptyId, {
scrollbackRows: resolveHiddenRestoreScrollbackRows(pane.terminal.options.scrollback)
})
.catch(() => null)
if (!isCurrentReattachPayload()) {
// Why: a superseded attempt must not paint, but reporting true would skip the replay fallback of the live attempt.
return true
}
if (
!snapshot ||
decideSshReattachPaintSource({ ptyId, sshParkingEnabled, snapshot }) !==
'main-model-snapshot'
) {
return false
}
rememberReattachPayloadAgentSignal(snapshot.data, { fullScreenReplay: true })
kittyKeyboardModes.scanReplay(snapshot.data)
await applyMainBufferSnapshot(snapshot)
sendFocusedReattachFocusInAfterReplay(ptyId, attemptGeneration)
return true
}
const applyReattachPayload = async (): Promise<void> => {
if (!isCurrentReattachPayload()) {
return
@@ -7659,6 +7694,15 @@ export function connectPanePty(
}
}
} else if (connectResult?.replay) {
if (await tryApplySshMainModelReattachSnapshot()) {
if (connectResult.coldRestore && !isRemoteRuntimePtyId(ptyId)) {
window.api.pty.ackColdRestore(ptyId)
}
return
}
if (!isCurrentReattachPayload()) {
return
}
rememberReattachPayloadAgentSignal(connectResult.replay, { fullScreenReplay: true })
// Relay replay may overlap xterm's pre-disconnect content; clear first to avoid duplication.
writeReplayData('\x1b[2J\x1b[3J\x1b[H')
@@ -0,0 +1,66 @@
import { describe, expect, it } from 'vitest'
import {
decideSshReattachPaintSource,
shouldFetchSshReattachModelSnapshot
} from './ssh-reattach-model-restore'
const SSH_PTY_ID = 'ssh:conn-1@@relay-pty-1'
const LOCAL_PTY_ID = 'repo::/worktree@@session-1'
describe('shouldFetchSshReattachModelSnapshot', () => {
it('fetches only for SSH ptys with SSH parking enabled', () => {
expect(
shouldFetchSshReattachModelSnapshot({ ptyId: SSH_PTY_ID, sshParkingEnabled: true })
).toBe(true)
expect(
shouldFetchSshReattachModelSnapshot({ ptyId: SSH_PTY_ID, sshParkingEnabled: false })
).toBe(false)
expect(
shouldFetchSshReattachModelSnapshot({ ptyId: LOCAL_PTY_ID, sshParkingEnabled: true })
).toBe(false)
})
})
describe('decideSshReattachPaintSource', () => {
const headless = { data: 'screen', source: 'headless' as const }
it('paints from the main model only for a non-empty headless snapshot', () => {
expect(
decideSshReattachPaintSource({
ptyId: SSH_PTY_ID,
sshParkingEnabled: true,
snapshot: headless
})
).toBe('main-model-snapshot')
})
it('degrades to relay replay on null, renderer-sourced, sourceless, or empty snapshots', () => {
for (const snapshot of [
null,
{ data: 'screen', source: 'renderer' as const },
{ data: 'screen' },
{ data: '', source: 'headless' as const }
]) {
expect(
decideSshReattachPaintSource({ ptyId: SSH_PTY_ID, sshParkingEnabled: true, snapshot })
).toBe('relay-replay')
}
})
it('never upgrades when the kill switch is off or the pty is not SSH', () => {
expect(
decideSshReattachPaintSource({
ptyId: SSH_PTY_ID,
sshParkingEnabled: false,
snapshot: headless
})
).toBe('relay-replay')
expect(
decideSshReattachPaintSource({
ptyId: LOCAL_PTY_ID,
sshParkingEnabled: true,
snapshot: headless
})
).toBe('relay-replay')
})
})
@@ -0,0 +1,36 @@
import { parseAppSshPtyId } from '../../../../shared/ssh-pty-id'
export type SshReattachPaintSource = 'main-model-snapshot' | 'relay-replay'
export type SshReattachModelSnapshot = {
data: string
source?: 'headless' | 'renderer'
}
/**
* Which payload paints an SSH reattach (C1 SSH-parking design gate). Only
* main's headless model is trusted: a 'renderer'-sourced snapshot serializes a
* mounted xterm, which no longer exists once the pane parked — anything but a
* non-empty headless snapshot degrades to the relay replay, never a blank paint.
*/
export function decideSshReattachPaintSource(args: {
ptyId: string
sshParkingEnabled: boolean
snapshot: SshReattachModelSnapshot | null
}): SshReattachPaintSource {
if (!args.sshParkingEnabled || parseAppSshPtyId(args.ptyId) === null) {
return 'relay-replay'
}
if (!args.snapshot || args.snapshot.source !== 'headless' || args.snapshot.data.length === 0) {
return 'relay-replay'
}
return 'main-model-snapshot'
}
/** Skip the snapshot fetch entirely when the paint could never use it. */
export function shouldFetchSshReattachModelSnapshot(args: {
ptyId: string
sshParkingEnabled: boolean
}): boolean {
return args.sshParkingEnabled && parseAppSshPtyId(args.ptyId) !== null
}
@@ -11,6 +11,7 @@ import {
canParkTerminalWorktreeRenderers,
getTerminalTabColdParkRecheckDelayMs,
getTerminalWorktreeColdParkRecheckDelayMs,
isParkRestorableTerminalPty,
isSnapshotBackedTerminalPty,
selectColdParkedTerminalTabs,
selectColdParkedTerminalWorktrees
@@ -46,6 +47,32 @@ describe('isSnapshotBackedTerminalPty', () => {
})
})
describe('isParkRestorableTerminalPty', () => {
const worktreeId = 'repo::/worktree'
const sshPolicy = { sshParkingEnabled: true }
it('accepts every snapshot-backed pty regardless of policy', () => {
expect(isParkRestorableTerminalPty(`${worktreeId}@@session-1`, worktreeId)).toBe(true)
expect(isParkRestorableTerminalPty(`${worktreeId}@@session-1`, worktreeId, sshPolicy)).toBe(
true
)
})
it('accepts SSH ptys only when the SSH-parking policy is enabled', () => {
expect(isParkRestorableTerminalPty('ssh:ssh-1@@pty-1', worktreeId, sshPolicy)).toBe(true)
expect(isParkRestorableTerminalPty('ssh:ssh-1@@pty-1', worktreeId)).toBe(false)
expect(
isParkRestorableTerminalPty('ssh:ssh-1@@pty-1', worktreeId, { sshParkingEnabled: false })
).toBe(false)
})
it('rejects remote-runtime, fail-open, foreign, and null ptys under every policy', () => {
for (const ptyId of ['remote:env-1@@terminal-1', 'pty-local-detached', 'other@@s-1', null]) {
expect(isParkRestorableTerminalPty(ptyId, worktreeId, sshPolicy)).toBe(false)
}
})
})
describe('canParkTerminalWorktreeRenderers', () => {
const hiddenSinceMs = 1_000
const nowMs = hiddenSinceMs + TERMINAL_WORKTREE_PARK_DELAY_MS
@@ -65,6 +92,24 @@ describe('canParkTerminalWorktreeRenderers', () => {
expect(canParkTerminalWorktreeRenderers(base)).toBe(true)
})
it('parks a hidden SSH worktree only under the SSH restore policy', () => {
const sshArgs = {
...base,
terminalTabs: [{ id: 'tab-1', ptyId: 'ssh:conn-1@@pty-1' }]
}
expect(canParkTerminalWorktreeRenderers(sshArgs)).toBe(false)
expect(
canParkTerminalWorktreeRenderers({ ...sshArgs, restorePolicy: { sshParkingEnabled: true } })
).toBe(true)
expect(
canParkTerminalWorktreeRenderers({
...sshArgs,
terminalTabs: [...sshArgs.terminalTabs, { id: 'tab-2', ptyId: 'remote:env-1@@t-1' }],
restorePolicy: { sshParkingEnabled: true }
})
).toBe(false)
})
it('keeps a previously mounted v19 terminal eligible for ordinary parking', () => {
const legacyPtyId = 'repo::/worktree@@session-1'
clearTerminalProviderSnapshotCapabilities()
@@ -67,6 +67,26 @@ export function isSnapshotBackedTerminalPty(ptyId: string | null, worktreeId: st
return separatorIdx !== -1 && ptyId.slice(0, separatorIdx) === worktreeId
}
export type TerminalParkRestorePolicy = {
/** settings.terminalSshViewParking !== false — the C1 SSH-parking kill switch. */
sshParkingEnabled?: boolean
}
// Why: SSH bytes transit local main, so main's headless model (served over
// pty:getMainBufferSnapshot) can re-hydrate a parked SSH reveal, with the
// relay's replay buffer as fallback — fact-mode watchers cover side effects
// either way. Remote-runtime ptys never transit main; they stay un-parkable.
export function isParkRestorableTerminalPty(
ptyId: string | null,
worktreeId: string,
policy?: TerminalParkRestorePolicy
): boolean {
if (isSnapshotBackedTerminalPty(ptyId, worktreeId)) {
return true
}
return policy?.sshParkingEnabled === true && ptyId !== null && parseAppSshPtyId(ptyId) !== null
}
export function canParkTerminalWorktreeRenderers(args: {
worktreeId: string
terminalTabs: readonly ColdParkableTerminalTab[]
@@ -80,6 +100,7 @@ export function canParkTerminalWorktreeRenderers(args: {
hiddenSinceMs: number | null
nowMs: number
coldParkDelayMs?: number
restorePolicy?: TerminalParkRestorePolicy
}): boolean {
if (
!args.parkingEnabled ||
@@ -103,7 +124,7 @@ export function canParkTerminalWorktreeRenderers(args: {
if (getPendingActivationSpawnCount(tab.pendingActivationSpawn) > 0) {
return false
}
return isSnapshotBackedTerminalPty(tab.ptyId, args.worktreeId)
return isParkRestorableTerminalPty(tab.ptyId, args.worktreeId, args.restorePolicy)
})
}
@@ -114,6 +135,7 @@ export function canParkTerminalTabRenderer(args: {
parkingEnabled: boolean
nowMs: number
coldParkDelayMs?: number
restorePolicy?: TerminalParkRestorePolicy
}): boolean {
const tab = args.terminalTab
if (
@@ -133,7 +155,7 @@ export function canParkTerminalTabRenderer(args: {
if (getPendingActivationSpawnCount(tab.pendingActivationSpawn) > 0) {
return false
}
return isSnapshotBackedTerminalPty(tab.ptyId, args.worktreeId)
return isParkRestorableTerminalPty(tab.ptyId, args.worktreeId, args.restorePolicy)
}
type ColdParkRetainCandidate = { id: string; hiddenSinceMs: number }
@@ -197,6 +219,7 @@ export function selectColdParkedTerminalWorktrees(
pendingStartupByTabId: Readonly<Record<string, unknown>>
parkingEnabled: boolean
nowMs: number
restorePolicy?: TerminalParkRestorePolicy
} & TerminalColdParkPolicyOverrides
): Set<string> {
if (!args.parkingEnabled) {
@@ -212,7 +235,8 @@ export function selectColdParkedTerminalWorktrees(
pendingStartupByTabId: args.pendingStartupByTabId,
parkingEnabled: args.parkingEnabled,
nowMs: args.nowMs,
coldParkDelayMs
coldParkDelayMs,
...(args.restorePolicy ? { restorePolicy: args.restorePolicy } : {})
})
) {
continue
@@ -233,6 +257,7 @@ export function selectColdParkedTerminalTabs(
pendingStartupByTabId: Readonly<Record<string, unknown>>
parkingEnabled: boolean
nowMs: number
restorePolicy?: TerminalParkRestorePolicy
} & TerminalColdParkPolicyOverrides
): Set<string> {
if (!args.parkingEnabled) {
@@ -249,7 +274,8 @@ export function selectColdParkedTerminalTabs(
pendingStartupByTabId: args.pendingStartupByTabId,
parkingEnabled: args.parkingEnabled,
nowMs: args.nowMs,
coldParkDelayMs
coldParkDelayMs,
...(args.restorePolicy ? { restorePolicy: args.restorePolicy } : {})
})
) {
continue
@@ -79,6 +79,7 @@ type MockStoreState = {
}
>
runtimePaneTitlesByTabId: Record<string, Record<number, string>>
settings: { terminalSshViewParking?: boolean } | null
clearTabLaunchAgent: ReturnType<typeof vi.fn>
clearRuntimePaneTitle: ReturnType<typeof vi.fn>
setTabLayout: ReturnType<typeof vi.fn>
@@ -135,6 +136,7 @@ describe('terminal-parked-tab-watchers', () => {
tabsByWorktree: {},
terminalLayoutsByTabId: {},
runtimePaneTitlesByTabId: {},
settings: null,
clearTabLaunchAgent: vi.fn(),
clearRuntimePaneTitle: vi.fn(),
setTabLayout: vi.fn(),
@@ -204,10 +206,9 @@ describe('terminal-parked-tab-watchers', () => {
expect(startedWatchers[0].options).toMatchObject({ ptyId: SECOND_PTY_ID })
})
it('never starts watchers for remote-runtime or SSH PTYs', () => {
it('never starts watchers for remote-runtime PTYs', () => {
capturePanes([
{ ptyId: 'remote:env-1@@terminal-1', paneId: 1, leafId: LEAF_ID, drivesTabTitle: true },
{ ptyId: 'ssh:conn-1@@pty-1', paneId: 2, leafId: SECOND_LEAF_ID, drivesTabTitle: false }
{ ptyId: 'remote:env-1@@terminal-1', paneId: 1, leafId: LEAF_ID, drivesTabTitle: true }
])
syncParked({ tabs: [{ id: TAB_ID, ptyId: null }] })
@@ -217,6 +218,22 @@ describe('terminal-parked-tab-watchers', () => {
expect(getParkedTerminalWatcherTabIds()).toEqual([TAB_ID])
})
it('starts watchers for SSH PTYs (C1 SSH parking, default on)', () => {
capturePanes([{ ptyId: 'ssh:conn-1@@pty-1', paneId: 1, leafId: LEAF_ID, drivesTabTitle: true }])
syncParked({ tabs: [{ id: TAB_ID, ptyId: 'ssh:conn-1@@pty-1' }] })
expect(startParkedTerminalByteWatcher).toHaveBeenCalledTimes(1)
expect(startedWatchers[0].options).toMatchObject({ ptyId: 'ssh:conn-1@@pty-1' })
})
it('never starts watchers for SSH PTYs when terminalSshViewParking is off', () => {
mockStoreState.settings = { terminalSshViewParking: false }
capturePanes([{ ptyId: 'ssh:conn-1@@pty-1', paneId: 1, leafId: LEAF_ID, drivesTabTitle: true }])
syncParked({ tabs: [{ id: TAB_ID, ptyId: null }] })
expect(startParkedTerminalByteWatcher).not.toHaveBeenCalled()
})
it('keeps existing watchers across repeated syncs of the same parked state', () => {
capturePanes([{ ptyId: PTY_ID, paneId: 1, leafId: LEAF_ID, drivesTabTitle: true }])
syncParked()
@@ -702,6 +719,28 @@ describe('terminal-parked-tab-watchers', () => {
})
it('rejects a capture containing a PTY without snapshot backing', () => {
capturePanes([
{ ptyId: PTY_ID, paneId: 1, leafId: LEAF_ID, drivesTabTitle: true },
// Why foreign-worktree id: minted under another worktree, never restorable.
{ ptyId: 'other::wt@@session-9', paneId: 2, leafId: SECOND_LEAF_ID, drivesTabTitle: false }
])
expect(canWatcherCoverParkedTerminalTab(WORKTREE_ID, { id: TAB_ID, ptyId: PTY_ID })).toBe(
false
)
})
it('accepts an SSH PTY under the default C1 SSH-parking policy', () => {
capturePanes([
{ ptyId: PTY_ID, paneId: 1, leafId: LEAF_ID, drivesTabTitle: true },
{ ptyId: 'ssh:conn-1@@pty-1', paneId: 2, leafId: SECOND_LEAF_ID, drivesTabTitle: false }
])
expect(canWatcherCoverParkedTerminalTab(WORKTREE_ID, { id: TAB_ID, ptyId: PTY_ID })).toBe(
true
)
})
it('rejects an SSH PTY when terminalSshViewParking is off', () => {
mockStoreState.settings = { terminalSshViewParking: false }
capturePanes([
{ ptyId: PTY_ID, paneId: 1, leafId: LEAF_ID, drivesTabTitle: true },
{ ptyId: 'ssh:conn-1@@pty-1', paneId: 2, leafId: SECOND_LEAF_ID, drivesTabTitle: false }
@@ -17,7 +17,10 @@ import { detachTerminalLayoutLeaf } from './terminal-layout-leaf-detach'
import { subscribeToPtyExit } from './pty-dispatcher'
import { discardPreHandlerPtyState } from './pty-pre-handler-buffer'
import { startParkedTerminalByteWatcher } from './parked-terminal-byte-watcher'
import { isSnapshotBackedTerminalPty } from './terminal-hidden-view-parking'
import {
isParkRestorableTerminalPty,
type TerminalParkRestorePolicy
} from './terminal-hidden-view-parking'
import {
resolveTabTitleAfterPaneClose,
shouldClearLaunchAgentForClosedPane
@@ -94,9 +97,18 @@ function resolveParkedTerminalPaneCandidates(
return capturedIsCurrent ? captured.panes : fallbackParkedPaneCandidates(tab, state)
}
// Why: fact-mode watchers work for any pty whose bytes transit local main —
// SSH included — so watcher coverage follows the park-restore policy, not the
// stricter daemon-snapshot predicate.
function parkRestorePolicyFromState(state: {
settings: { terminalSshViewParking?: boolean } | null
}): TerminalParkRestorePolicy {
return { sshParkingEnabled: state.settings?.terminalSshViewParking !== false }
}
/**
* Whether parked byte watchers can fully cover this tab's PTYs (every candidate
* has a snapshot-backed PTY on a valid leaf). Hosts must refuse to park a tab
* has a park-restorable PTY on a valid leaf). Hosts must refuse to park a tab
* that fails this check, or bell/title/completion side effects silently drop.
*/
export function canWatcherCoverParkedTerminalTab(
@@ -105,14 +117,16 @@ export function canWatcherCoverParkedTerminalTab(
// Why: cold activation needs stronger snapshot support (view never mounted); ordinary parking can reattach a mounted view.
isPtyEligible: ParkedTerminalPtyEligibility = allowSnapshotBackedPty
): boolean {
const panes = resolveParkedTerminalPaneCandidates(tab, useAppStore.getState())
const state = useAppStore.getState()
const panes = resolveParkedTerminalPaneCandidates(tab, state)
const restorePolicy = parkRestorePolicyFromState(state)
return (
panes.length > 0 &&
panes.every(
(pane) =>
pane.ptyId !== null &&
isTerminalLeafId(pane.leafId) &&
isSnapshotBackedTerminalPty(pane.ptyId, worktreeId) &&
isParkRestorableTerminalPty(pane.ptyId, worktreeId, restorePolicy) &&
isPtyEligible(pane.ptyId)
)
)
@@ -125,6 +139,7 @@ function startParkedTabWatchers(
): void {
const state = useAppStore.getState()
const panes = resolveParkedTerminalPaneCandidates(tab, state)
const restorePolicy = parkRestorePolicyFromState(state)
const disposersByPtyId = new Map<string, () => void>()
const paneIdByPtyId = new Map<string, number>()
for (const pane of panes) {
@@ -134,7 +149,7 @@ function startParkedTabWatchers(
!ptyId ||
disposersByPtyId.has(ptyId) ||
!isTerminalLeafId(pane.leafId) ||
!isSnapshotBackedTerminalPty(ptyId, worktreeId)
!isParkRestorableTerminalPty(ptyId, worktreeId, restorePolicy)
) {
continue
}
@@ -78,6 +78,9 @@ export function useTerminalTabColdParking(args: {
const terminalParkingEnabled = useAppStore(
(state) => state.settings?.terminalHiddenViewParking !== false
)
const terminalSshParkingEnabled = useAppStore(
(state) => state.settings?.terminalSshViewParking !== false
)
const terminalTabHiddenSinceRef = useRef(new Map<string, number>())
const terminalTabParkingTimersRef = useRef(new Map<string, number>())
const parkVerdictRecordsRef = useRef(new Map<string, ParkVerdictFlipRecord>())
@@ -147,6 +150,7 @@ export function useTerminalTabColdParking(args: {
pendingStartupByTabId,
parkingEnabled: terminalParkingEnabled,
nowMs,
restorePolicy: { sshParkingEnabled: terminalSshParkingEnabled },
...overrides
})
// Why: a tab the byte watchers cannot cover (no capture, no layout
@@ -196,6 +200,7 @@ export function useTerminalTabColdParking(args: {
pendingStartupByTabId,
shouldMeasureHiddenWorktree,
terminalParkingEnabled,
terminalSshParkingEnabled,
terminalTabParkingRevision,
terminalTabs,
worktreeId
+2
View File
@@ -2848,6 +2848,8 @@ export type GlobalSettings = {
terminalScopeHistoryByWorktree: boolean
/** Kill switch for hidden terminal view parking: unmount long-hidden panes while a pane-less watcher keeps PTY side effects alive. */
terminalHiddenViewParking?: boolean
/** Kill switch for SSH terminal parking (C1): SSH panes park like local ones; reveal restores from main's headless model, falling back to relay replay. */
terminalSshViewParking?: boolean
/** Kill switch for main-process PTY side-effect authority; on (default) = title/bell/agent facts via pty:sideEffect channel, not renderer byte parsing. */
terminalMainSideEffectAuthority?: boolean
/** Kill switch for main's hidden-delivery gate (Phase 4): drops PTY bytes to hidden views after model ingestion; requires terminalMainSideEffectAuthority. */