fix(runtime): fail closed on unresolved terminal owners

This commit is contained in:
OrcaWin
2026-07-22 22:16:51 -07:00
parent b8e3cee29e
commit e5f37af6f1
9 changed files with 110 additions and 27 deletions
@@ -14,6 +14,7 @@ import {
getHostModelCapabilitySummary,
getRuntimeCapabilitiesSummary,
getRuntimeServerConnectionState,
isRuntimeEnvironmentRemovalBlocked,
type RuntimeHostDetails
} from './RuntimeEnvironmentsPane'
@@ -208,4 +209,10 @@ describe('RuntimeEnvironmentsPane host details', () => {
expect(getActiveServerModeDescription(false)).toContain('default Host')
expect(getActiveServerModeDescription(false)).toContain('paired Orca runtime')
})
it('blocks removing the active server independently of local-runtime availability', () => {
expect(isRuntimeEnvironmentRemovalBlocked('windows-2', 'windows-2')).toBe(true)
expect(isRuntimeEnvironmentRemovalBlocked(undefined, 'windows-2')).toBe(false)
expect(isRuntimeEnvironmentRemovalBlocked('local', 'windows-2')).toBe(false)
})
})
@@ -200,6 +200,13 @@ export function getActiveServerModeDescription(allowLocalRuntime: boolean): stri
)
}
export function isRuntimeEnvironmentRemovalBlocked(
activeRuntimeEnvironmentId: string | null | undefined,
environmentId: string
): boolean {
return activeRuntimeEnvironmentId === environmentId
}
type RuntimeServerConnectionState = 'connected' | 'checking' | 'disconnected'
export function getRuntimeServerConnectionState(
@@ -294,7 +301,9 @@ export function RuntimeEnvironmentsPane({
switchingValue !== null ||
removingId !== null ||
disconnectingId !== null
const removingActiveServer = pendingRemove?.id === settings.activeRuntimeEnvironmentId
const removingActiveServer = pendingRemove
? isRuntimeEnvironmentRemovalBlocked(settings.activeRuntimeEnvironmentId, pendingRemove.id)
: false
const searchEntry = canGeneratePairingUrl
? getRuntimeEnvironmentsSearchEntry()
: getWebRuntimeEnvironmentsSearchEntry()
@@ -510,7 +519,7 @@ export function RuntimeEnvironmentsPane({
setRemovingId(environment.id)
setRemoveError(null)
try {
if (allowLocalRuntime && settings.activeRuntimeEnvironmentId === environment.id) {
if (isRuntimeEnvironmentRemovalBlocked(settings.activeRuntimeEnvironmentId, environment.id)) {
if (mountedRef.current) {
setRemoveError(
translate(
@@ -1385,15 +1394,10 @@ export function RuntimeEnvironmentsPane({
</DialogTitle>
<DialogDescription>
{removingActiveServer
? allowLocalRuntime
? translate(
'auto.components.settings.RuntimeEnvironmentsPane.removeActiveServerDescription',
'Choose another Active Server in Advanced before removing this server. Existing host sessions are left alone.'
)
: translate(
'auto.components.settings.RuntimeEnvironmentsPane.b2fda48c39',
'Removing the active server disconnects this browser from that host. Existing host sessions are left alone.'
)
? translate(
'auto.components.settings.RuntimeEnvironmentsPane.removeActiveServerDescription',
'Choose another Active Server in Advanced before removing this server. Existing host sessions are left alone.'
)
: translate(
'auto.components.settings.RuntimeEnvironmentsPane.ed3e3f069d',
'This removes the saved server from Orca. It does not change the active server.'
+65 -7
View File
@@ -1825,7 +1825,12 @@ describe('useIpcEvents updater integration', () => {
folderWorkspaces: [],
projectGroups: [],
repos: [{ id: 'repo-1', connectionId: null, executionHostId: 'local' }],
worktreesByRepo: { 'repo-1': [{ id: 'wt-2', repoId: 'repo-1' }] },
worktreesByRepo: {
'repo-1': ['wt-1', 'wt-2', 'wt-3', 'wt-4', 'wt-history'].map((id) => ({
id,
repoId: 'repo-1'
}))
} as Record<string, { id: string; repoId: string }[]>,
openFiles: [],
browserTabsByWorktree: {},
tabBarOrderByWorktree: {},
@@ -2444,12 +2449,18 @@ describe('useIpcEvents updater integration', () => {
createTab.mockClear()
replyTerminalCreate.mockClear()
storeState.repos.push({
id: 'repo-remote',
connectionId: null,
executionHostId: 'runtime:focused-runtime'
})
storeState.worktreesByRepo['repo-remote'] = [{ id: 'wt-remote', repoId: 'repo-remote' }]
storeState.repos = [
...storeState.repos,
{
id: 'repo-remote',
connectionId: null,
executionHostId: 'runtime:focused-runtime'
}
]
storeState.worktreesByRepo = {
...storeState.worktreesByRepo,
'repo-remote': [{ id: 'wt-remote', repoId: 'repo-remote' }]
}
requestTerminalCreateListenerRef.current({
requestId: 'req-remote-owner-blocked',
worktreeId: 'wt-remote',
@@ -2460,6 +2471,53 @@ describe('useIpcEvents updater integration', () => {
requestId: 'req-remote-owner-blocked',
error: 'Local terminal creation is unavailable while a remote runtime is active'
})
delete storeState.worktreesByRepo['repo-remote']
storeState.repos = storeState.repos.filter((repo) => repo.id !== 'repo-remote')
createTab.mockClear()
replyTerminalCreate.mockClear()
storeState.repos = [
...storeState.repos,
{
id: 'repo-conflicting-owner',
connectionId: null,
executionHostId: 'runtime:focused-runtime'
}
]
storeState.worktreesByRepo = {
...storeState.worktreesByRepo,
'repo-1': [...storeState.worktreesByRepo['repo-1'], { id: 'wt-ambiguous', repoId: 'repo-1' }],
'repo-conflicting-owner': [{ id: 'wt-ambiguous', repoId: 'repo-conflicting-owner' }]
}
requestTerminalCreateListenerRef.current({
requestId: 'req-ambiguous-owner',
worktreeId: 'wt-ambiguous',
title: 'Ambiguous Terminal',
source: 'runtime-session'
})
expect(createTab).not.toHaveBeenCalled()
expect(replyTerminalCreate).toHaveBeenCalledWith({
requestId: 'req-ambiguous-owner',
error: 'Terminal creation is unavailable because the worktree owner could not be resolved'
})
storeState.worktreesByRepo['repo-1'] = storeState.worktreesByRepo['repo-1'].filter(
(worktree) => worktree.id !== 'wt-ambiguous'
)
delete storeState.worktreesByRepo['repo-conflicting-owner']
storeState.repos = storeState.repos.filter((repo) => repo.id !== 'repo-conflicting-owner')
createTab.mockClear()
replyTerminalCreate.mockClear()
requestTerminalCreateListenerRef.current({
requestId: 'req-missing-owner',
worktreeId: 'wt-missing',
title: 'Missing Terminal'
})
expect(createTab).not.toHaveBeenCalled()
expect(replyTerminalCreate).toHaveBeenCalledWith({
requestId: 'req-missing-owner',
error: 'Terminal creation is unavailable because the worktree owner could not be resolved'
})
storeState.settings.activeRuntimeEnvironmentId = undefined
if (typeof focusTerminalListenerRef.current !== 'function') {
+13 -4
View File
@@ -133,6 +133,7 @@ import { showTerminalShortcutCaptureNotification } from '@/lib/terminal-shortcut
import { resolveAgentStatusTerminalTitle } from '@/lib/agent-status-terminal-title'
import { titleHasAgentName } from '../../../shared/agent-detection'
import { getRuntimeEnvironmentIdForWorktree } from '@/lib/worktree-runtime-owner'
import { resolveTerminalWorktreeRoute } from '@/lib/terminal-worktree-route'
import { resolveAgentPaneAuthorityKey } from '@/store/slices/agent-pane-authority'
import { translate } from '@/i18n/i18n'
import { closeTerminalTab } from '@/components/terminal/terminal-tab-actions'
@@ -1707,11 +1708,19 @@ export function useIpcEvents(): void {
})
return
}
const worktreeRoute = resolveTerminalWorktreeRoute(store, worktreeId)
if (!worktreeRoute) {
window.api.ui.replyTerminalCreate({
requestId: data.requestId,
error: translate(
'auto.hooks.useIpcEvents.unresolvedTerminalWorktreeOwner',
'Terminal creation is unavailable because the worktree owner could not be resolved'
)
})
return
}
// Why: runtime-session requests are host-owned tabs materialized by this renderer, not ordinary local creates.
if (
getRuntimeEnvironmentIdForWorktree(store, worktreeId) &&
data.source !== 'runtime-session'
) {
if (worktreeRoute.runtimeEnvironmentId && data.source !== 'runtime-session') {
window.api.ui.replyTerminalCreate({
requestId: data.requestId,
error: translate(
+2 -1
View File
@@ -684,7 +684,8 @@
"4f78ba5885": "Workspace synced",
"ef223fbb6b": "A device tried to connect but is not paired",
"11992d0337": "If this was your phone or another Orca client, re-pair it from Settings → Mobile.",
"6573cfe955": "Open Mobile Settings"
"6573cfe955": "Open Mobile Settings",
"unresolvedTerminalWorktreeOwner": "Terminal creation is unavailable because the worktree owner could not be resolved"
},
"useSettingsNavigationMetadata": {
"4a728cd56b": "New features that are still taking shape. Give them a try.",
+2 -1
View File
@@ -661,7 +661,8 @@
"4f78ba5885": "Espacio de trabajo sincronizado",
"ef223fbb6b": "A device tried to connect but is not paired",
"11992d0337": "If this was your phone or another Orca client, re-pair it from Settings → Mobile.",
"6573cfe955": "Open Mobile Settings"
"6573cfe955": "Open Mobile Settings",
"unresolvedTerminalWorktreeOwner": "Terminal creation is unavailable because the worktree owner could not be resolved"
},
"useSettingsNavigationMetadata": {
"4a728cd56b": "Nuevas funciones que aún están tomando forma. Pruébalas.",
+2 -1
View File
@@ -661,7 +661,8 @@
"4f78ba5885": "ワークスペースが同期されました",
"ef223fbb6b": "A device tried to connect but is not paired",
"11992d0337": "If this was your phone or another Orca client, re-pair it from Settings → Mobile.",
"6573cfe955": "Open Mobile Settings"
"6573cfe955": "Open Mobile Settings",
"unresolvedTerminalWorktreeOwner": "Terminal creation is unavailable because the worktree owner could not be resolved"
},
"useSettingsNavigationMetadata": {
"4a728cd56b": "まだ形になりつつある新機能。試してみてください。",
+2 -1
View File
@@ -661,7 +661,8 @@
"4f78ba5885": "워크스페이스가 동기화되었습니다.",
"ef223fbb6b": "A device tried to connect but is not paired",
"11992d0337": "If this was your phone or another Orca client, re-pair it from Settings → Mobile.",
"6573cfe955": "Open Mobile Settings"
"6573cfe955": "Open Mobile Settings",
"unresolvedTerminalWorktreeOwner": "Terminal creation is unavailable because the worktree owner could not be resolved"
},
"useSettingsNavigationMetadata": {
"4a728cd56b": "아직 구체화되고 있는 새로운 기능입니다. 한번 시도해 보세요.",
+2 -1
View File
@@ -661,7 +661,8 @@
"4f78ba5885": "工作区已同步",
"ef223fbb6b": "A device tried to connect but is not paired",
"11992d0337": "If this was your phone or another Orca client, re-pair it from Settings → Mobile.",
"6573cfe955": "Open Mobile Settings"
"6573cfe955": "Open Mobile Settings",
"unresolvedTerminalWorktreeOwner": "Terminal creation is unavailable because the worktree owner could not be resolved"
},
"useSettingsNavigationMetadata": {
"4a728cd56b": "仍在完善中的新功能。尝试一下。",