diff --git a/src/renderer/src/components/GitHubItemDialog.tsx b/src/renderer/src/components/GitHubItemDialog.tsx index a884f7fe541..0e2985de627 100644 --- a/src/renderer/src/components/GitHubItemDialog.tsx +++ b/src/renderer/src/components/GitHubItemDialog.tsx @@ -3502,7 +3502,9 @@ function ChecksTab({ toast.error('Could not build the agent launch command.') return } - focusTerminalTabSurface(result.tabId) + if (result.tabId) { + focusTerminalTabSurface(result.tabId) + } toast.success('Started an AI agent for the broken checks.') } finally { setFixingChecks(false) diff --git a/src/renderer/src/components/PullRequestPage.tsx b/src/renderer/src/components/PullRequestPage.tsx index 1467e40147c..a35f247d056 100644 --- a/src/renderer/src/components/PullRequestPage.tsx +++ b/src/renderer/src/components/PullRequestPage.tsx @@ -3756,7 +3756,9 @@ function ChecksTab({ toast.error('Could not build the agent launch command.') return } - focusTerminalTabSurface(result.tabId) + if (result.tabId) { + focusTerminalTabSurface(result.tabId) + } toast.success('Started an AI agent for the broken checks.') } finally { setFixingChecks(false) diff --git a/src/renderer/src/components/right-sidebar/ChecksPanel.tsx b/src/renderer/src/components/right-sidebar/ChecksPanel.tsx index 2ecc473bf06..d626d454f90 100644 --- a/src/renderer/src/components/right-sidebar/ChecksPanel.tsx +++ b/src/renderer/src/components/right-sidebar/ChecksPanel.tsx @@ -1837,7 +1837,9 @@ export default function ChecksPanel(): React.JSX.Element { toast.error('Could not build the agent launch command.') return } - focusTerminalTabSurface(result.tabId) + if (result.tabId) { + focusTerminalTabSurface(result.tabId) + } toast.success('Started an AI agent for the conflicts.') } finally { setIsResolvingConflictsWithAI(false) @@ -1939,7 +1941,9 @@ export default function ChecksPanel(): React.JSX.Element { toast.error('Could not build the agent launch command.') return } - focusTerminalTabSurface(result.tabId) + if (result.tabId) { + focusTerminalTabSurface(result.tabId) + } } finally { setIsFixingChecksWithAI(false) } diff --git a/src/renderer/src/components/right-sidebar/SourceControl.tsx b/src/renderer/src/components/right-sidebar/SourceControl.tsx index 83b0572923b..cebcd464c33 100644 --- a/src/renderer/src/components/right-sidebar/SourceControl.tsx +++ b/src/renderer/src/components/right-sidebar/SourceControl.tsx @@ -1770,7 +1770,9 @@ function SourceControlInner(): React.JSX.Element { return } - focusTerminalTabSurface(result.tabId) + if (result.tabId) { + focusTerminalTabSurface(result.tabId) + } toast.success('Started an AI agent for the conflicts.') } finally { setIsLaunchingConflictAgent(false) @@ -1843,7 +1845,9 @@ function SourceControlInner(): React.JSX.Element { return false } - focusTerminalTabSurface(result.tabId) + if (result.tabId) { + focusTerminalTabSurface(result.tabId) + } toast.success('Started an AI agent for the commit failure.') return true } finally { diff --git a/src/renderer/src/components/tab-bar/QuickLaunchButton.tsx b/src/renderer/src/components/tab-bar/QuickLaunchButton.tsx index e8004507211..8489eb55b74 100644 --- a/src/renderer/src/components/tab-bar/QuickLaunchButton.tsx +++ b/src/renderer/src/components/tab-bar/QuickLaunchButton.tsx @@ -138,16 +138,22 @@ function QuickLaunchAgentMenuItemsInner({ toast.error(`Could not build launch command for ${label}.`) return } + if (!result.tabId) { + // Why: paired web clients create the tab on the host; focus follows the + // next session-tabs snapshot instead of a local tab id. + return + } onFocusTerminal(result.tabId) // Why: launch success means the terminal session exists. Agent readiness // can lag behind on slow machines, and prompt paste flows already own // their own readiness timeout once a PTY exists. - void waitForTerminalPty(result.tabId, 5000).then((hasPty) => { + const launchedTabId = result.tabId + void waitForTerminalPty(launchedTabId, 5000).then((hasPty) => { if (hasPty) { return } - const launchState = getTerminalLaunchState(result.tabId) + const launchState = getTerminalLaunchState(launchedTabId) if (!launchState.stillOpen) { return } diff --git a/src/renderer/src/components/terminal-pane/TerminalPane.tsx b/src/renderer/src/components/terminal-pane/TerminalPane.tsx index 27e7503f644..8ae1adfe552 100644 --- a/src/renderer/src/components/terminal-pane/TerminalPane.tsx +++ b/src/renderer/src/components/terminal-pane/TerminalPane.tsx @@ -47,7 +47,11 @@ import { useNotificationDispatch } from './use-notification-dispatch' import { connectPanePty } from './pty-connection' import { shouldPreserveTerminalScrollbackBuffers } from '../../../../shared/workspace-session-terminal-buffers' import { getFitOverrideForPty, onOverrideChange } from '@/lib/pane-manager/mobile-fit-overrides' -import { getDriverForPty, onDriverChange } from '@/lib/pane-manager/mobile-driver-state' +import { + getDriverForPty, + isPtyLocked, + onDriverChange +} from '@/lib/pane-manager/mobile-driver-state' import { resolvePaneKeyForManager } from '@/lib/pane-manager/pane-key-resolution' import { safeFit } from '@/lib/pane-manager/pane-tree-ops' import { captureTerminalShutdownLayout } from './terminal-shutdown-layout-capture' @@ -1132,9 +1136,19 @@ export default function TerminalPane({ for (const pane of manager.getPanes()) { safeFit(pane) const transport = paneTransportsRef.current.get(pane.id) - if (transport?.isConnected()) { - transport.resize(pane.terminal.cols, pane.terminal.rows) + if (!transport?.isConnected()) { + continue } + const ptyId = transport.getPtyId() + if (!ptyId) { + continue + } + // Why: match pty-connection resize guards so web refit retries do not + // forward SIGWINCH while mobile-lock or phone-fit overrides are active. + if (getFitOverrideForPty(ptyId) || isPtyLocked(ptyId)) { + continue + } + transport.resize(pane.terminal.cols, pane.terminal.rows) } } const scheduleFrame = (): void => { diff --git a/src/renderer/src/components/terminal/terminal-tab-actions.test.ts b/src/renderer/src/components/terminal/terminal-tab-actions.test.ts index 790a1e36469..34b0caea979 100644 --- a/src/renderer/src/components/terminal/terminal-tab-actions.test.ts +++ b/src/renderer/src/components/terminal/terminal-tab-actions.test.ts @@ -15,7 +15,7 @@ const { getStateMock: vi.fn(), isWebRuntimeSessionActiveMock: vi.fn(), isWebTerminalSurfaceTabIdMock: vi.fn(() => false), - resolveHostSessionTabIdForWebSessionTabMock: vi.fn(() => null) + resolveHostSessionTabIdForWebSessionTabMock: vi.fn<() => string | null>(() => null) })) vi.mock('@/store', () => ({ diff --git a/src/renderer/src/components/terminal/terminal-tab-actions.ts b/src/renderer/src/components/terminal/terminal-tab-actions.ts index 3918acea991..5eba2fd7e39 100644 --- a/src/renderer/src/components/terminal/terminal-tab-actions.ts +++ b/src/renderer/src/components/terminal/terminal-tab-actions.ts @@ -84,7 +84,7 @@ export function closeTerminalTab(tabId: string): void { } const runtimeEnvironmentId = state.settings?.activeRuntimeEnvironmentId?.trim() - if (isWebRuntimeSessionActive(runtimeEnvironmentId)) { + if (runtimeEnvironmentId && isWebRuntimeSessionActive(runtimeEnvironmentId)) { const hostBackedTabId = resolveHostSessionTabIdForWebSessionTab(state, { environmentId: runtimeEnvironmentId, diff --git a/src/renderer/src/hooks/useAutomationDispatchEvents.ts b/src/renderer/src/hooks/useAutomationDispatchEvents.ts index 6836b695141..d0a948bc658 100644 --- a/src/renderer/src/hooks/useAutomationDispatchEvents.ts +++ b/src/renderer/src/hooks/useAutomationDispatchEvents.ts @@ -378,14 +378,18 @@ export function useAutomationDispatchEvents(): void { if (!result) { throw new Error('Unable to build an agent launch plan.') } - observeAgentStatus(result.tabId, dispatchStartedAt) + if (!result.tabId) { + throw new Error('Host-backed agent tabs are not yet trackable for automation dispatch.') + } + const launchedTabId = result.tabId + observeAgentStatus(launchedTabId, dispatchStartedAt) try { await markDispatchResult({ runId: run.id, status: 'dispatched', workspaceId: worktree.id, workspaceDisplayName: worktree.displayName, - terminalSessionId: result.tabId, + terminalSessionId: launchedTabId, precheckResult, error: null }) diff --git a/src/renderer/src/lib/launch-agent-in-new-tab.test.ts b/src/renderer/src/lib/launch-agent-in-new-tab.test.ts index 1a868322cf6..bebe6c714e8 100644 --- a/src/renderer/src/lib/launch-agent-in-new-tab.test.ts +++ b/src/renderer/src/lib/launch-agent-in-new-tab.test.ts @@ -11,7 +11,7 @@ const mockTrack = vi.fn() const LEAF_ID = '11111111-1111-4111-8111-111111111111' const store = { - settings: { agentCmdOverrides: {} }, + settings: { agentCmdOverrides: {}, activeRuntimeEnvironmentId: null as string | null }, tabsByWorktree: { 'wt-1': [{ id: 'tab-1' }] }, diff --git a/src/renderer/src/lib/sidebar-worktree-activation.ts b/src/renderer/src/lib/sidebar-worktree-activation.ts index fdbbe49e711..ba0d160b2b6 100644 --- a/src/renderer/src/lib/sidebar-worktree-activation.ts +++ b/src/renderer/src/lib/sidebar-worktree-activation.ts @@ -17,6 +17,7 @@ export function cancelPendingSidebarWorktreeActivation(): void { } function shouldDeferSidebarWorktreeActivation(worktreeId: string): boolean { + // Why: web clients should activate immediately to avoid host/session churn and wake lag. if ((globalThis as { __ORCA_WEB_CLIENT__?: boolean }).__ORCA_WEB_CLIENT__ === true) { return false }