mirror of
https://github.com/stablyai/orca.git
synced 2026-10-18 16:03:17 +00:00
fix(runtime): fail closed on unresolved terminal owners
This commit is contained in:
@@ -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.'
|
||||
|
||||
@@ -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') {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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": "まだ形になりつつある新機能。試してみてください。",
|
||||
|
||||
@@ -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": "아직 구체화되고 있는 새로운 기능입니다. 한번 시도해 보세요.",
|
||||
|
||||
@@ -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": "仍在完善中的新功能。尝试一下。",
|
||||
|
||||
Reference in New Issue
Block a user