refactor(renderer): name the owner adapters for what they do (resolveOwnerOrLocal) (#31626)

* refactor(renderer): name the owner adapters for what they do: resolveOwnerOrLocal, runtimeEnvironmentIdOrLocalUnlessTwin

The Active Server focus fallback they were named for is gone (#31058, #31119); what is left reads an
id no row places as this app's (a folder's saved-session server, a web client's only server).

* docs(renderer): resolveOwnerOrLocal keeps a named host's miss; baseline note names the setup-terminal read
This commit is contained in:
Neil
2026-10-21 13:58:36 -07:00
committed by GitHub
parent 554862d671
commit c054e94eff
87 changed files with 223 additions and 297 deletions
+1 -1
View File
@@ -257,7 +257,7 @@
4 src/renderer/src/lib/workspace-browser-tab-open.ts
1 src/renderer/src/lib/worktree-activation.ts
1 src/renderer/src/lib/worktree-jump-navigation.ts
1 src/renderer/src/lib/worktree-runtime-owner.ts # typed owner resolver and the legacy-fallback adapter the folds moved to (round 3 F0); F8 deleted the deprecated pair
1 src/renderer/src/lib/worktree-runtime-owner.ts # a setup terminal with no row spawns on the focused runtime
17 src/renderer/src/runtime/runtime-jira-client.ts
2 src/renderer/src/runtime/runtime-jira-summary-client.ts
1 src/renderer/src/runtime/runtime-jira-target.ts
@@ -35,7 +35,7 @@ vi.mock('@/store', async () => {
}
})
vi.mock('@/lib/worktree-runtime-owner', () => ({
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin: () => null,
runtimeEnvironmentIdOrLocalUnlessTwin: () => null,
getExecutionHostIdForWorktree: () => mocks.executionHostId
}))
vi.mock('@/components/contextual-tours/use-contextual-tour', () => ({
@@ -33,7 +33,7 @@ vi.mock('@/store', () => ({
}))
vi.mock('@/lib/worktree-runtime-owner', () => ({
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin: () => null
runtimeEnvironmentIdOrLocalUnlessTwin: () => null
}))
vi.mock('@/components/contextual-tours/use-contextual-tour', () => ({
@@ -1,6 +1,6 @@
import { useCallback, useEffect, useMemo } from 'react'
import { useAppStore } from '@/store'
import { runtimeEnvironmentIdWithLegacyFallbackUnlessTwin } from '@/lib/worktree-runtime-owner'
import { runtimeEnvironmentIdOrLocalUnlessTwin } from '@/lib/worktree-runtime-owner'
import { isClientHostedBrowserPageHandle } from '@/store/slices/browser'
import type { BrowserWorkspace as BrowserWorkspaceState } from '../../../../../shared/browser-workspace-types'
import { destroyPersistentWebview } from '../host-guest/webview-registry'
@@ -41,7 +41,7 @@ export default function BrowserPane({
}): React.JSX.Element {
const resolvedChromeShortcutScope = chromeShortcutScope ?? (isActive ? 'focused' : 'inactive')
const activeRuntimeEnvironmentId = useAppStore((s) =>
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(s, browserTab.worktreeId)
runtimeEnvironmentIdOrLocalUnlessTwin(s, browserTab.worktreeId)
)
const browserPages = useAppStore((s) =>
getBrowserPagesForWorkspace(s.browserPagesByWorkspace, browserTab.id)
@@ -1,6 +1,6 @@
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
import { useAppStore } from '@/store'
import { runtimeEnvironmentIdWithLegacyFallbackUnlessTwin } from '@/lib/worktree-runtime-owner'
import { runtimeEnvironmentIdOrLocalUnlessTwin } from '@/lib/worktree-runtime-owner'
import { RemoteBrowserStreamLifecycle } from './remote-browser-stream-lifecycle'
import type {
RemoteBrowserOperationToken,
@@ -237,10 +237,7 @@ export function useRemoteBrowserPageLifecycle({
return
}
const state = useAppStore.getState()
const currentEnvironmentId = runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(
state,
worktreeId
)
const currentEnvironmentId = runtimeEnvironmentIdOrLocalUnlessTwin(state, worktreeId)
const pageStillExists = browserPageExists(browserTab.id)
if (currentEnvironmentId === activeRuntimeEnvironmentId && pageStillExists) {
return
@@ -28,7 +28,7 @@ vi.mock('@/lib/connection-owner-resolution', () => ({
getConnectionIdForFileFromState: () => mocks.connectionIdForFile
}))
vi.mock('@/lib/worktree-runtime-owner', () => ({
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin: () => mocks.worktreeRuntimeOwnerId
runtimeEnvironmentIdOrLocalUnlessTwin: () => mocks.worktreeRuntimeOwnerId
}))
vi.mock('@/lib/file-owner-runtime-target', () => {
const getRuntimeTargetForFileOwner = (
@@ -2,7 +2,7 @@ import { LOCAL_EXECUTION_HOST_ID } from '../../../../../shared/execution-host'
import { toast } from 'sonner'
import { translate } from '@/i18n/i18n'
import { getConnectionIdForFileFromState } from '@/lib/connection-owner-resolution'
import { runtimeEnvironmentIdWithLegacyFallbackUnlessTwin } from '@/lib/worktree-runtime-owner'
import { runtimeEnvironmentIdOrLocalUnlessTwin } from '@/lib/worktree-runtime-owner'
import { isTwinWorkspace } from '@/lib/resolve-owner'
import { detectLanguage } from '@/lib/language-detect'
import {
@@ -71,7 +71,7 @@ export function openDocPreviewExternally(document: DocPreviewDocument): void {
// renders through was minted against the worktree's owner at render time, and a tab opened or
// restored before that owner was known still carries null — which reads as local.
const runtimeEnvironmentId =
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(state, document.worktreeId) ??
runtimeEnvironmentIdOrLocalUnlessTwin(state, document.worktreeId) ??
document.runtimeEnvironmentId
const fileContext = buildWorkspaceFileContextForFile(
document.worktreeId,
@@ -23,7 +23,7 @@ vi.mock('@/store', () => ({
useAppStore: (selector: (state: unknown) => unknown) => selector(mocks.storeState)
}))
vi.mock('@/lib/worktree-runtime-owner', () => ({
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin: () => null
runtimeEnvironmentIdOrLocalUnlessTwin: () => null
}))
vi.mock('./HtmlDocPreview', () => ({
HtmlDocPreview: ({ holdsGuestFocus }: { holdsGuestFocus?: boolean }) => {
@@ -1,6 +1,6 @@
import { useMemo } from 'react'
import { getRelativePathInsideRoot } from '@/lib/path'
import { runtimeEnvironmentIdWithLegacyFallbackUnlessTwin } from '@/lib/worktree-runtime-owner'
import { runtimeEnvironmentIdOrLocalUnlessTwin } from '@/lib/worktree-runtime-owner'
import { useAppStore } from '@/store'
import { HtmlDocPreview } from './HtmlDocPreview'
import type { BrowserPage } from '../../../../../shared/browser-workspace-types'
@@ -24,7 +24,7 @@ export function WorkspaceDocPagePane({
// Why resolved here rather than stored on the page: ownership moves. A page persisted before a
// pairing or an SSH reconnect would otherwise route its document actions at yesterday's host.
const runtimeEnvironmentId = useAppStore((store) =>
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(store, worktreeId)
runtimeEnvironmentIdOrLocalUnlessTwin(store, worktreeId)
)
const relativePath = useMemo(
() => getRelativePathInsideRoot(filePath, worktreeRoot) ?? filePath,
@@ -22,7 +22,7 @@ vi.mock('@/lib/connection-owner-resolution', async (importOriginal) => ({
}))
vi.mock('@/lib/worktree-runtime-owner', async (importOriginal) => ({
...(await importOriginal<typeof WorktreeRuntimeOwnerModule>()),
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin: () => 'env-1'
runtimeEnvironmentIdOrLocalUnlessTwin: () => 'env-1'
}))
import { NativeChatInlineVisual } from './NativeChatInlineVisual'
@@ -4,7 +4,7 @@ import {
resolveExplicitFileLinkTarget,
resolveExplicitFileLinkTargetPath
} from '@/lib/explicit-file-link-target'
import { runtimeEnvironmentIdWithLegacyFallbackUnlessTwin } from '@/lib/worktree-runtime-owner'
import { runtimeEnvironmentIdOrLocalUnlessTwin } from '@/lib/worktree-runtime-owner'
import type { AppState } from '@/store/types'
import { resolveNativeChatTabDirectory } from './native-chat-tab-directory'
@@ -122,7 +122,7 @@ function resolveNativeChatFileLinkContextForOwner(
return {
worktreeId,
worktreePath,
runtimeEnvironmentId: runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(state, worktreeId),
runtimeEnvironmentId: runtimeEnvironmentIdOrLocalUnlessTwin(state, worktreeId),
sourceTabId: terminalTabId
}
}
@@ -6,15 +6,14 @@ import {
} from './native-chat-http-link-source-owner'
const mocks = vi.hoisted(() => ({
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin: vi.fn(),
runtimeEnvironmentIdOrLocalUnlessTwin: vi.fn(),
getConnectionIdFromState: vi.fn(),
canOpenWorkspaceBrowserTabOnRuntime: vi.fn(),
canOpenWorkspaceBrowserTabOnSsh: vi.fn()
}))
vi.mock('@/lib/worktree-runtime-owner', () => ({
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin:
mocks.runtimeEnvironmentIdWithLegacyFallbackUnlessTwin
runtimeEnvironmentIdOrLocalUnlessTwin: mocks.runtimeEnvironmentIdOrLocalUnlessTwin
}))
vi.mock('@/lib/connection-owner-resolution', () => ({
getConnectionIdFromState: mocks.getConnectionIdFromState
@@ -32,7 +31,7 @@ afterEach(() => {
describe('resolveNativeChatHttpLinkSourceOwner', () => {
it('prefers the workspace runtime owner', () => {
mocks.runtimeEnvironmentIdWithLegacyFallbackUnlessTwin.mockReturnValue('env-1')
mocks.runtimeEnvironmentIdOrLocalUnlessTwin.mockReturnValue('env-1')
expect(resolveNativeChatHttpLinkSourceOwner(state, 'wt-1')).toEqual({
kind: 'runtime',
@@ -42,7 +41,7 @@ describe('resolveNativeChatHttpLinkSourceOwner', () => {
})
it('falls back to the SSH connection that owns the workspace', () => {
mocks.runtimeEnvironmentIdWithLegacyFallbackUnlessTwin.mockReturnValue(null)
mocks.runtimeEnvironmentIdOrLocalUnlessTwin.mockReturnValue(null)
mocks.getConnectionIdFromState.mockReturnValue('ssh-1')
expect(resolveNativeChatHttpLinkSourceOwner(state, 'wt-1')).toEqual({
@@ -52,7 +51,7 @@ describe('resolveNativeChatHttpLinkSourceOwner', () => {
})
it('reads a null connection as local', () => {
mocks.runtimeEnvironmentIdWithLegacyFallbackUnlessTwin.mockReturnValue(null)
mocks.runtimeEnvironmentIdOrLocalUnlessTwin.mockReturnValue(null)
mocks.getConnectionIdFromState.mockReturnValue(null)
expect(resolveNativeChatHttpLinkSourceOwner(state, 'wt-1')).toEqual({ kind: 'local' })
@@ -61,7 +60,7 @@ describe('resolveNativeChatHttpLinkSourceOwner', () => {
// An unresolved owner must not be mistaken for local: a remote link would then
// open against the wrong host.
it('reports an unresolved owner as unknown', () => {
mocks.runtimeEnvironmentIdWithLegacyFallbackUnlessTwin.mockReturnValue(null)
mocks.runtimeEnvironmentIdOrLocalUnlessTwin.mockReturnValue(null)
mocks.getConnectionIdFromState.mockReturnValue(undefined)
expect(resolveNativeChatHttpLinkSourceOwner(state, 'wt-1')).toEqual({ kind: 'unknown' })
@@ -4,7 +4,7 @@ import {
canOpenWorkspaceBrowserTabOnRuntime,
canOpenWorkspaceBrowserTabOnSsh
} from '@/lib/workspace-browser-tab-open'
import { runtimeEnvironmentIdWithLegacyFallbackUnlessTwin } from '@/lib/worktree-runtime-owner'
import { runtimeEnvironmentIdOrLocalUnlessTwin } from '@/lib/worktree-runtime-owner'
import type { AppState } from '@/store/types'
/** The chat transcript has no PTY, so link ownership comes from the session's
@@ -14,7 +14,7 @@ export function resolveNativeChatHttpLinkSourceOwner(
state: AppState,
worktreeId: string
): HttpLinkSourceOwner {
const runtimeEnvironmentId = runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(state, worktreeId)
const runtimeEnvironmentId = runtimeEnvironmentIdOrLocalUnlessTwin(state, worktreeId)
if (runtimeEnvironmentId) {
return { kind: 'runtime', runtimeEnvironmentId }
}
@@ -1,4 +1,4 @@
import { runtimeEnvironmentIdWithLegacyFallbackUnlessTwin } from '@/lib/worktree-runtime-owner'
import { runtimeEnvironmentIdOrLocalUnlessTwin } from '@/lib/worktree-runtime-owner'
import type { WorktreeRuntimeOwnerState } from '@/lib/worktree-runtime-owner-state'
import type { AppState } from '@/store/types'
import {
@@ -26,7 +26,7 @@ export function selectNativeChatRuntimeEnvironmentId(
state: NativeChatRuntimeOwnerState,
terminalTabId: string
): string | null {
return runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(
return runtimeEnvironmentIdOrLocalUnlessTwin(
state,
findTerminalTabWorktreeId(state.tabsByWorktree, terminalTabId)
)
@@ -35,7 +35,7 @@ export function selectNativeChatRuntimeEnvironmentId(
export function createNativeChatRuntimeSelector(terminalTabId: string) {
const selectOwner = createNativeChatTabOwnerSelector(terminalTabId)
return (state: NativeChatRuntimeOwnerState): string | null => {
return runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(
return runtimeEnvironmentIdOrLocalUnlessTwin(
state,
selectOwner({ tabsByWorktree: state.tabsByWorktree })
)
@@ -1,6 +1,6 @@
import React, { useCallback, useMemo } from 'react'
import { useAppStore } from '@/store'
import { runtimeEnvironmentIdWithLegacyFallbackUnlessTwin } from '@/lib/worktree-runtime-owner'
import { runtimeEnvironmentIdOrLocalUnlessTwin } from '@/lib/worktree-runtime-owner'
import { getWorkspaceFileRevealOwner } from '@/lib/reveal-in-file-manager'
import { getRouteForLocalPathOpenOwner } from '@/lib/local-path-open-guard'
import { useActiveWorktree, useRepoById } from '@/store/selectors'
@@ -51,7 +51,7 @@ function FileExplorerFiles(): React.JSX.Element {
const searchPanel = useFileSearchPanel(explorerView)
const activeWorktreeId = useAppStore((s) => s.activeWorktreeId)
const runtimeEnvironmentId = useAppStore((s) =>
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(s, activeWorktreeId)
runtimeEnvironmentIdOrLocalUnlessTwin(s, activeWorktreeId)
)
const wslHostUnavailable = useWslHostUnavailableReason(activeWorktreeId)
const activeWorktree = useActiveWorktree()
@@ -9,7 +9,7 @@ import { useMountedRef } from '@/hooks/useMountedRef'
import { useConfirmationDialog } from '@/components/confirmation-dialog-context'
import { getWorktreeGitIdentityDisplay } from '../../../../../shared/worktree/git-identity-display'
import { resolveRepoWorktreeLaunchPlatform } from '@/lib/execution-host-facts'
import { runtimeEnvironmentIdWithLegacyFallbackUnlessTwin } from '@/lib/worktree-runtime-owner'
import { runtimeEnvironmentIdOrLocalUnlessTwin } from '@/lib/worktree-runtime-owner'
import { normalizeGlobalWindowsRuntimeDefault } from '../../../../../shared/project-execution-runtime'
import {
saveSourceControlActionRecipe,
@@ -223,7 +223,7 @@ export function useChecksPanelControllerState() {
? resolveRepoWorktreeLaunchPlatform(useAppStore.getState(), repo, activeWorktree)
: undefined
const runtimeEnvironmentId = useAppStore((s) =>
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(s, activeWorktreeId)
runtimeEnvironmentIdOrLocalUnlessTwin(s, activeWorktreeId)
)
const ownerSettings = useMemo<AppState['settings']>(
() =>
@@ -34,7 +34,7 @@ import {
revealInFileManager
} from '@/lib/reveal-in-file-manager'
import { isRevealOrEditorOpenBlocked } from '@/lib/local-path-open-guard'
import { runtimeEnvironmentIdWithLegacyFallbackUnlessTwin } from '@/lib/worktree-runtime-owner'
import { runtimeEnvironmentIdOrLocalUnlessTwin } from '@/lib/worktree-runtime-owner'
import type { FileExplorerRowProps } from './FileExplorerRow'
import {
shouldShowCollapseFolderAction,
@@ -123,7 +123,7 @@ export function FileExplorerRowContextMenu({
const revealOwner = useAppStore((s) =>
getWorkspaceFileRevealOwner(s, s.activeWorktreeId, {
connectionId,
runtimeEnvironmentId: runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(s, s.activeWorktreeId)
runtimeEnvironmentId: runtimeEnvironmentIdOrLocalUnlessTwin(s, s.activeWorktreeId)
})
)
const revealBlocked = isRevealOrEditorOpenBlocked(revealOwner)
@@ -2,7 +2,7 @@ import { useEffect, useRef } from 'react'
import { useAppStore } from '@/store'
import { shouldPollActiveGitStatus } from '@/lib/passive-macos-app-data-access'
import { isWindowVisible } from '@/lib/window-visibility-interval'
import { runtimeEnvironmentIdWithLegacyFallbackUnlessTwin } from '@/lib/worktree-runtime-owner'
import { runtimeEnvironmentIdOrLocalUnlessTwin } from '@/lib/worktree-runtime-owner'
import {
normalizeRuntimePathForComparison,
relativePathInsideRoot
@@ -72,7 +72,7 @@ export function useGitStatusFileWatchRefresh({
worktreePath
}: UseGitStatusFileWatchRefreshParams): void {
const activeRuntimeEnvironmentId = useAppStore((state) =>
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(state, activeWorktreeId)
runtimeEnvironmentIdOrLocalUnlessTwin(state, activeWorktreeId)
)
const fetchStatusRef = useRef(fetchStatus)
fetchStatusRef.current = fetchStatus
@@ -48,7 +48,7 @@ vi.mock('@/store', () => ({
}))
vi.mock('@/lib/worktree-runtime-owner', () => ({
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin: () => ownerRuntime.environmentId
runtimeEnvironmentIdOrLocalUnlessTwin: () => ownerRuntime.environmentId
}))
vi.mock(import('@/lib/reveal-in-file-manager'), async (importOriginal) => ({
@@ -25,7 +25,7 @@ import {
getRouteForLocalPathOpenOwner,
isRevealOrEditorOpenBlocked
} from '@/lib/local-path-open-guard'
import { runtimeEnvironmentIdWithLegacyFallbackUnlessTwin } from '@/lib/worktree-runtime-owner'
import { runtimeEnvironmentIdOrLocalUnlessTwin } from '@/lib/worktree-runtime-owner'
import { NO_OPEN_IN_APPLICATIONS } from '@/lib/open-in-application-selection'
import {
getOpenInEntryAvailability,
@@ -63,7 +63,7 @@ export function SourceControlEntryContextMenu({
// Why: a repo can belong to a runtime other than the focused one, and the OS reveal
// cannot tell that host's path from a local one of the same name.
const runtimeEnvironmentId = useAppStore((s) =>
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(s, currentWorktreeId)
runtimeEnvironmentIdOrLocalUnlessTwin(s, currentWorktreeId)
)
const revealOwner = useAppStore((s) =>
getWorkspaceFileRevealOwner(s, currentWorktreeId, { connectionId, runtimeEnvironmentId })
@@ -1,7 +1,7 @@
import type { Dispatch, RefObject, SetStateAction } from 'react'
import { useEffect, useMemo, useRef } from 'react'
import { useAppStore } from '@/store'
import { runtimeEnvironmentIdWithLegacyFallbackUnlessTwin } from '@/lib/worktree-runtime-owner'
import { runtimeEnvironmentIdOrLocalUnlessTwin } from '@/lib/worktree-runtime-owner'
import { useWorkspaceFileBrowserActionPredicate } from '@/lib/file-preview'
import { openWorkspaceFile } from '@/lib/workspace-file-open'
import type { RuntimeFileOperationArgs } from '@/runtime/runtime-file-client-types'
@@ -130,7 +130,7 @@ export function useFileExplorerTreePaneState({
: false
})
const ownerRuntimeEnvironmentId = useAppStore((s) =>
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(s, activeWorktreeId)
runtimeEnvironmentIdOrLocalUnlessTwin(s, activeWorktreeId)
)
const toggleDir = useAppStore((s) => s.toggleDir)
const makePreviewFilePermanent = useAppStore((s) => s.makePreviewFilePermanent)
@@ -14,7 +14,7 @@ import { searchRuntimeFiles } from '@/runtime/runtime-file-search-client'
import { useAppStore } from '@/store'
import {
getExecutionHostIdForWorktree,
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin
runtimeEnvironmentIdOrLocalUnlessTwin
} from '@/lib/worktree-runtime-owner'
import { isTwinWorkspace } from '@/lib/resolve-owner'
import type { SearchResult } from '../../../../shared/code-search-types'
@@ -45,7 +45,7 @@ export function useFileSearchRunner({
cancelPendingSearch: () => void
} {
const runtimeEnvironmentId = useAppStore((state) =>
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(state, activeWorktreeId)
runtimeEnvironmentIdOrLocalUnlessTwin(state, activeWorktreeId)
)
const executionHostId = useAppStore((state) =>
getExecutionHostIdForWorktree(state, activeWorktreeId)
@@ -2,7 +2,7 @@ import { useCallback, useEffect } from 'react'
import { useAppStore } from '@/store'
import {
getExecutionHostIdForWorktree,
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin
runtimeEnvironmentIdOrLocalUnlessTwin
} from '@/lib/worktree-runtime-owner'
import {
isFileSearchResultOwnerCurrent,
@@ -25,7 +25,7 @@ export function useFileSearchScope({
updateActiveSearchState: (updates: { results: null; resultOwner: null; error: null }) => void
}) {
const runtimeEnvironmentId = useAppStore((state) =>
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(state, activeWorktreeId)
runtimeEnvironmentIdOrLocalUnlessTwin(state, activeWorktreeId)
)
const executionHostId = useAppStore((state) =>
getExecutionHostIdForWorktree(state, activeWorktreeId)
@@ -107,7 +107,7 @@ export function tabBarRuntimeModelStubs(): Record<string, () => Record<string, u
detectedAgents: () => ({ useDetectedAgents: () => DETECTED_AGENTS }),
detectionTarget: () => ({ useAgentDetectionTargetForWorktree: () => null }),
connectionContext: () => ({ getConnectionIdFromState: () => null }),
runtimeOwner: () => ({ runtimeEnvironmentIdWithLegacyFallbackUnlessTwin: () => null }),
runtimeOwner: () => ({ runtimeEnvironmentIdOrLocalUnlessTwin: () => null }),
nativeChatReadability: () => ({ isNativeChatTranscriptLocalReadable: () => false }),
creationPolicy: () => ({ getClientCreationActionPolicy: () => CREATION_POLICY }),
agentProjections: () => ({ selectTabBarAgentProjections: () => AGENT_PROJECTIONS }),
@@ -11,7 +11,7 @@ import { useAgentDetectionTargetForWorktree } from '@/hooks/useAgentDetectionTar
import { getConnectionIdFromState } from '@/lib/connection-context'
import { getLocalProjectExecutionRuntimeContext } from '@/lib/local-preflight-context'
import { isNativeChatTranscriptLocalReadable } from '@/lib/native-chat-transcript-readability'
import { runtimeEnvironmentIdWithLegacyFallbackUnlessTwin } from '@/lib/worktree-runtime-owner'
import { runtimeEnvironmentIdOrLocalUnlessTwin } from '@/lib/worktree-runtime-owner'
import { runtimeTargetForOwnerEnvironment } from '@/runtime/runtime-client-target'
import { useOptionalShortcutLabel, useShortcutLabel } from '@/hooks/useShortcutLabel'
import {
@@ -126,7 +126,7 @@ export function useTabBarRuntimeModel({
const settings = useAppStore((s) => s.settings)
// Why: use the worktree's owning host so offered Windows shells match the host that actually runs the terminal.
const activeRuntimeEnvironmentId = useAppStore((s) =>
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(s, worktreeId)
runtimeEnvironmentIdOrLocalUnlessTwin(s, worktreeId)
)
// Why: retained tab strips rerun selectors on every store write; reuse canonical indexes, don't flatten both slices here.
const worktreeConnectionId = useAppStore(
@@ -1,5 +1,5 @@
import type { RuntimeMobileSessionTabMove } from '../../../../shared/runtime-types'
import { runtimeEnvironmentIdWithLegacyFallbackUnlessTwin } from '@/lib/worktree-runtime-owner'
import { runtimeEnvironmentIdOrLocalUnlessTwin } from '@/lib/worktree-runtime-owner'
import { useAppStore } from '../../store'
import { isWebRuntimeSessionActive } from '../../runtime/web-runtime-session-environment'
import { moveWebRuntimeSessionTab } from '../../runtime/web-runtime-session-tab-move'
@@ -9,7 +9,7 @@ export function mirrorWebRuntimeTabMove(
worktreeId: string
}
): void {
const environmentId = runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(
const environmentId = runtimeEnvironmentIdOrLocalUnlessTwin(
useAppStore.getState(),
args.worktreeId
)
@@ -5,7 +5,7 @@ import { focusTerminalTabSurface } from '../../lib/focus-terminal-tab-surface'
import { TOGGLE_TERMINAL_PANE_EXPAND_EVENT } from '@/constants/terminal'
import { activateWebRuntimeSessionTab } from '../../runtime/web-runtime-session-tab-lifecycle'
import { isWebRuntimeSessionActive } from '../../runtime/web-runtime-session-environment'
import { runtimeEnvironmentIdWithLegacyFallbackUnlessTwin } from '@/lib/worktree-runtime-owner'
import { runtimeEnvironmentIdOrLocalUnlessTwin } from '@/lib/worktree-runtime-owner'
import { browserWorkspaceHasRemoteOwner } from '@/runtime/remote-browser-tab-ownership'
import { activateStructuredAgentSessionTab } from '@/lib/structured-agent-session-tab-activation'
import type { TabGroupWorktreeSnapshot } from './useTabGroupItemProjections'
@@ -38,7 +38,7 @@ export function useTabGroupActivationCommands({
}
focusGroup(worktreeId, groupId)
activateTab(item.id)
const runtimeEnvironmentId = runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(
const runtimeEnvironmentId = runtimeEnvironmentIdOrLocalUnlessTwin(
useAppStore.getState(),
worktreeId
)
@@ -117,7 +117,7 @@ export function useTabGroupActivationCommands({
}
focusGroup(worktreeId, groupId)
activateTab(item.id)
const runtimeEnvironmentId = runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(
const runtimeEnvironmentId = runtimeEnvironmentIdOrLocalUnlessTwin(
useAppStore.getState(),
worktreeId
)
@@ -13,7 +13,7 @@ const mocks = vi.hoisted(() => ({
openNewBrowserTabInActiveWorkspace: vi.fn(),
openNewMarkdownInActiveWorkspace: vi.fn(),
openNewTerminalTabInActiveWorkspace: vi.fn(),
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin: vi.fn(),
runtimeEnvironmentIdOrLocalUnlessTwin: vi.fn(),
focusTerminalTabSurface: vi.fn(),
runtimeCall: vi.fn()
}))
@@ -21,8 +21,7 @@ const mocks = vi.hoisted(() => ({
vi.mock('react', async () => await stubHeadlessReact())
vi.mock('@/lib/worktree-runtime-owner', () => ({
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin:
mocks.runtimeEnvironmentIdWithLegacyFallbackUnlessTwin
runtimeEnvironmentIdOrLocalUnlessTwin: mocks.runtimeEnvironmentIdOrLocalUnlessTwin
}))
vi.mock('@/lib/runtime-session-mirror-owners', () => ({
getRuntimeSessionMirrorEnvironmentIds: () => []
@@ -77,7 +76,7 @@ describe('tab group "+" menu shell launch on a locally-owned workspace', () => {
beforeEach(() => {
vi.clearAllMocks()
storeState.activeWorkspaceExecutionHostId = 'local'
mocks.runtimeEnvironmentIdWithLegacyFallbackUnlessTwin.mockReturnValue(null)
mocks.runtimeEnvironmentIdOrLocalUnlessTwin.mockReturnValue(null)
stubRuntimeEnvironmentBridge({ call: mocks.runtimeCall })
})
@@ -9,7 +9,7 @@ import { openTabBarEntry, type TabCreateEntryArgs } from '../tab-bar/tab-create-
import { openMobileEmulatorTab } from '@/lib/open-mobile-emulator-tab'
import { ensureSimulatorTab, getSimulatorTabForWorktree } from '@/lib/ensure-simulator-tab'
import { buildDuplicatedBrowserTabOptions } from '@/lib/duplicate-browser-tab-options'
import { runtimeEnvironmentIdWithLegacyFallbackUnlessTwin } from '@/lib/worktree-runtime-owner'
import { runtimeEnvironmentIdOrLocalUnlessTwin } from '@/lib/worktree-runtime-owner'
import { browserWorkspaceHasRemoteOwner } from '@/runtime/remote-browser-tab-ownership'
import { getClientCreationActionPolicy } from '@/lib/client-creation-action-policy'
import type { TabGroupWorktreeSnapshot } from './useTabGroupItemProjections'
@@ -70,10 +70,7 @@ export function useTabGroupCreationCommands({
if (!source) {
return
}
const runtimeEnvironmentId = runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(
state,
worktreeId
)
const runtimeEnvironmentId = runtimeEnvironmentIdOrLocalUnlessTwin(state, worktreeId)
const browserAvailability = getClientCreationActionPolicy(state, worktreeId)[
'managed-browser'
]
@@ -116,7 +113,7 @@ export function useTabGroupCreationCommands({
},
newTerminalWithShell: (shellOverride: string) => {
void (async () => {
const environmentId = runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(
const environmentId = runtimeEnvironmentIdOrLocalUnlessTwin(
useAppStore.getState(),
worktreeId
)
@@ -4,7 +4,7 @@ import { isWslUncPath } from '../../../../shared/wsl-paths'
import { getConnectionIdFromState } from '@/lib/connection-context'
import {
getExecutionHostIdForWorktree,
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin
runtimeEnvironmentIdOrLocalUnlessTwin
} from '@/lib/worktree-runtime-owner'
import { getRemoteRuntimePtyEnvironmentId } from '@/runtime/runtime-terminal-stream'
import type { AppState } from '@/store/types'
@@ -90,10 +90,7 @@ export function resolveTerminalInputHostPlatform(args: {
const host = parseExecutionHostId(getExecutionHostIdForWorktree(args.state, args.worktreeId))
if (host?.kind === 'ssh') {
const ownerEnvironmentId = runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(
args.state,
args.worktreeId
)
const ownerEnvironmentId = runtimeEnvironmentIdOrLocalUnlessTwin(args.state, args.worktreeId)
return ownerEnvironmentId
? (args.state.sshStateByEnvironment
.get(ownerEnvironmentId)
@@ -4,7 +4,7 @@ import {
isRemoteRuntimePtyId,
parseRemoteRuntimePtyId
} from '../../../../shared/remote-runtime-pty-id'
import { runtimeEnvironmentIdWithLegacyFallbackUnlessTwin } from '@/lib/worktree-runtime-owner'
import { runtimeEnvironmentIdOrLocalUnlessTwin } from '@/lib/worktree-runtime-owner'
import type { WorktreeRuntimeOwnerState } from '@/lib/worktree-runtime-owner-state'
import type { PtyTransport } from './pty-transport-types'
@@ -122,6 +122,6 @@ export function resolveTerminalPasteRuntimeEnvironmentId(
const ptyId = transport?.getPtyId() ?? null
return (
(ptyId ? parseRemoteRuntimePtyId(ptyId)?.environmentId : null) ??
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(state, worktreeId)
runtimeEnvironmentIdOrLocalUnlessTwin(state, worktreeId)
)
}
@@ -1,7 +1,7 @@
import type { Tab } from '../../../shared/tab-types'
import type { KeybindingContext } from '../../../shared/keybindings'
import { useAppStore } from '../store'
import { runtimeEnvironmentIdWithLegacyFallbackUnlessTwin } from '@/lib/worktree-runtime-owner'
import { runtimeEnvironmentIdOrLocalUnlessTwin } from '@/lib/worktree-runtime-owner'
import { isEditorTabContentType } from '@/store/slices/editor/tabs/editor-tab-content-type'
export const CLOSE_DIALOG_DEBOUNCE_MS = 200
@@ -56,7 +56,7 @@ export function isPinnedVisibleTab(
}
export function getActiveWorktreeRuntimeEnvironmentId(worktreeId: string | null): string | null {
return runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(useAppStore.getState(), worktreeId)
return runtimeEnvironmentIdOrLocalUnlessTwin(useAppStore.getState(), worktreeId)
}
export function isPinnedActiveEditorTab(
@@ -12,7 +12,7 @@ const mocks = vi.hoisted(() => ({ state: {} as Record<string, unknown> }))
vi.mock('@/store', () => ({ useAppStore: { getState: () => mocks.state } }))
vi.mock('@/lib/worktree-runtime-owner', () => ({
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin: () => RUNTIME_ID
runtimeEnvironmentIdOrLocalUnlessTwin: () => RUNTIME_ID
}))
vi.mock('@/components/sidebar/delete-worktree-flow', () => ({ runWorktreeDelete: vi.fn() }))
@@ -4,7 +4,7 @@ import { findWorktreeById } from '@/store/slices/worktree-helpers'
import { isVirtualEditorFile } from '@/store/slices/editor/tabs/editor-tab-content-type'
import { findRepoForHost } from '@/store/slices/repo-host-identity'
import { isLocalWindowsDesktopClient } from '@/lib/desktop-window-chrome'
import { runtimeEnvironmentIdWithLegacyFallbackUnlessTwin } from '@/lib/worktree-runtime-owner'
import { runtimeEnvironmentIdOrLocalUnlessTwin } from '@/lib/worktree-runtime-owner'
import {
parseExecutionHostId,
toRuntimeExecutionHostId,
@@ -123,7 +123,7 @@ function getSidebarWatchConsumer(
worktreeId: string
): WatchConsumer {
// Why: sidebar watcher must follow the selected worktree's host owner, not the host currently focused in the UI.
const owner = runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(state, worktreeId)
const owner = runtimeEnvironmentIdOrLocalUnlessTwin(state, worktreeId)
if (owner) {
return { owner, hostId: toRuntimeExecutionHostId(owner) }
}
@@ -4,7 +4,7 @@ vi.mock('../../store', () => ({
useAppStore: { getState: () => ({}) }
}))
vi.mock('@/lib/worktree-runtime-owner', () => ({
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin: () => null
runtimeEnvironmentIdOrLocalUnlessTwin: () => null
}))
vi.mock('@/components/browser-pane/describe-page/live-browser-url-registry', () => ({
rememberLiveBrowserUrl: vi.fn()
@@ -1,5 +1,5 @@
import { rememberLiveBrowserUrl } from '@/components/browser-pane/describe-page/live-browser-url-registry'
import { runtimeEnvironmentIdWithLegacyFallbackUnlessTwin } from '@/lib/worktree-runtime-owner'
import { runtimeEnvironmentIdOrLocalUnlessTwin } from '@/lib/worktree-runtime-owner'
import { resolveBrowserSourceUnifiedTab } from '@/lib/browser-workspace-source-resolution'
import { redactKagiSessionToken } from '../../../../shared/browser-url'
import { useAppStore } from '../../store'
@@ -138,10 +138,7 @@ export function registerBrowserStateIpcBridge(
const sourcePage = Object.values(store.browserPagesByWorkspace)
.flat()
.find((page) => page.id === browserPageId)
if (
!sourcePage ||
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(store, sourcePage.worktreeId)
) {
if (!sourcePage || runtimeEnvironmentIdOrLocalUnlessTwin(store, sourcePage.worktreeId)) {
return
}
// Why: the link inherits the opener's cookie jar. Falling back to the default profile would let
@@ -11,7 +11,7 @@ vi.mock('../../store', () => ({
useAppStore: { getState: () => storeState.value }
}))
vi.mock('@/lib/worktree-runtime-owner', () => ({
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin: () => null
runtimeEnvironmentIdOrLocalUnlessTwin: () => null
}))
vi.mock('@/components/browser-pane/describe-page/live-browser-url-registry', () => ({
rememberLiveBrowserUrl: vi.fn()
@@ -21,7 +21,7 @@ vi.mock('@/runtime/runtime-rpc-client', () => ({
}))
vi.mock('./worktree-runtime-owner', () => ({
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin: mocks.environmentIdFor
runtimeEnvironmentIdOrLocalUnlessTwin: mocks.environmentIdFor
}))
import { revealStructuredSession } from './activate-ai-vault-structured-session'
@@ -4,7 +4,7 @@ import { translate } from '@/i18n/i18n'
import { activateAndRevealWorktree } from './worktree-activation'
import { activateStructuredAgentSessionById } from './structured-agent-session-tab-activation'
import { useAppStore } from '@/store'
import { runtimeEnvironmentIdWithLegacyFallbackUnlessTwin } from './worktree-runtime-owner'
import { runtimeEnvironmentIdOrLocalUnlessTwin } from './worktree-runtime-owner'
import { callRuntimeRpc, runtimeEnvironmentSupportsCapability } from '@/runtime/runtime-rpc-client'
import type { RuntimeClientTarget } from '../../../shared/runtime-client-target'
import { runtimeTargetForOwnerEnvironment } from '@/runtime/runtime-client-target'
@@ -105,10 +105,7 @@ export async function activateAiVaultStructuredSession(
const hostKey = executionHostIdForStructuredTarget(
target ??
runtimeTargetForOwnerEnvironment(
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(
useAppStore.getState(),
structured.workspaceId
)
runtimeEnvironmentIdOrLocalUnlessTwin(useAppStore.getState(), structured.workspaceId)
)
)
const key = `${hostKey}\0${structured.workspaceId}\0${structured.sessionId}`
@@ -199,7 +196,7 @@ export async function revealStructuredSession(target: {
const host =
target.target ??
runtimeTargetForOwnerEnvironment(
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(useAppStore.getState(), target.worktreeId)
runtimeEnvironmentIdOrLocalUnlessTwin(useAppStore.getState(), target.worktreeId)
)
// Negotiated against the host that will answer this call, not the local one: a paired host runs
// its own build, and its method-not-found is indistinguishable from a refusal we should surface.
@@ -259,9 +256,7 @@ async function refreshStructuredSessionTabs(
const state = useAppStore.getState()
const host =
target ??
runtimeTargetForOwnerEnvironment(
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(state, worktreeId)
)
runtimeTargetForOwnerEnvironment(runtimeEnvironmentIdOrLocalUnlessTwin(state, worktreeId))
if (host.kind === 'local') {
await withStructuredSessionRestoreTimeout(refreshLocalStructuredSessionWorktreeTabs(worktreeId))
return
@@ -3,7 +3,7 @@ import { runtimeTargetForOwnerEnvironment } from '@/runtime/runtime-client-targe
import type { RuntimeClientTarget } from '../../../shared/runtime-client-target'
import { useAppStore } from '@/store'
import { callRuntimeRpc } from '@/runtime/runtime-rpc-client'
import { runtimeEnvironmentIdWithLegacyFallbackUnlessTwin } from '@/lib/worktree-runtime-owner'
import { runtimeEnvironmentIdOrLocalUnlessTwin } from '@/lib/worktree-runtime-owner'
import { findActiveRuntimeTerminal, getActiveTerminalNoteTarget } from './active-agent-note-target'
import type { ActiveTerminalNoteTarget } from './active-agent-note-target'
import type { ActiveAgentNotesSendResult } from './active-agent-note-send-result'
@@ -74,7 +74,7 @@ async function sendNotesToActiveAgentSessionInternal({
return reportNoteSendFailure({ status: 'no-active-terminal', code: 'no-note-target' }, null)
}
const runtimeTarget = runtimeTargetForOwnerEnvironment(
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(state, worktreeId)
runtimeEnvironmentIdOrLocalUnlessTwin(state, worktreeId)
)
const terminal = await findActiveRuntimeTerminal(
runtimeTarget,
@@ -8,7 +8,7 @@ import {
} from '../../../shared/agent-status-types'
import type { AppState } from '@/store/types'
import { callRuntimeRpc } from '@/runtime/runtime-rpc-client'
import { runtimeEnvironmentIdWithLegacyFallbackUnlessTwin } from '@/lib/worktree-runtime-owner'
import { runtimeEnvironmentIdOrLocalUnlessTwin } from '@/lib/worktree-runtime-owner'
import type { WorktreeRuntimeOwnerState } from '@/lib/worktree-runtime-owner-state'
import { toRuntimeWorktreeSelector } from '@/runtime/runtime-worktree-selector'
import { isTerminalLeafId, makePaneKey } from '../../../shared/stable-pane-id'
@@ -122,7 +122,7 @@ export function getActiveAgentRuntimeProbeDescriptor(
// Route by the worktree's owner host so the probe targets the host that runs
// this worktree's agent terminal, not the focused runtime.
const runtimeTarget = runtimeTargetForOwnerEnvironment(
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(state, worktreeId)
runtimeEnvironmentIdOrLocalUnlessTwin(state, worktreeId)
)
const runtimeKey =
runtimeTarget.kind === 'environment' ? `env:${runtimeTarget.environmentId}` : 'local'
@@ -30,7 +30,7 @@ vi.mock('@/runtime/web-runtime-session-environment', () => ({
}))
vi.mock('@/lib/worktree-runtime-owner', () => ({
getExecutionHostIdForWorktree: () => 'local',
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin: () => null
runtimeEnvironmentIdOrLocalUnlessTwin: () => null
}))
vi.mock('@/components/tab-bar/reconcile-order', () => ({
reconcileTabOrder: (_stored: unknown, terminalIds: string[]) => terminalIds
@@ -31,7 +31,7 @@ vi.mock('@/runtime/web-runtime-session-environment', () => ({
}))
vi.mock('@/lib/worktree-runtime-owner', () => ({
getExecutionHostIdForWorktree: () => 'local',
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin: () => null
runtimeEnvironmentIdOrLocalUnlessTwin: () => null
}))
vi.mock('@/components/tab-bar/reconcile-order', () => ({
reconcileTabOrder: (_stored: unknown, terminalIds: string[]) => terminalIds
@@ -32,7 +32,7 @@ vi.mock('@/runtime/web-runtime-session-environment', () => ({
}))
vi.mock('@/lib/worktree-runtime-owner', () => ({
getExecutionHostIdForWorktree: () => 'local',
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin: () => null
runtimeEnvironmentIdOrLocalUnlessTwin: () => null
}))
vi.mock('@/components/tab-bar/reconcile-order', () => ({
reconcileTabOrder: (_stored: unknown, terminalIds: string[]) => terminalIds
@@ -37,7 +37,7 @@ vi.mock('@/runtime/web-runtime-session-environment', () => ({
}))
vi.mock('@/lib/worktree-runtime-owner', () => ({
getExecutionHostIdForWorktree: () => mockExecutionHostId(),
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin: () => 'web-runtime'
runtimeEnvironmentIdOrLocalUnlessTwin: () => 'web-runtime'
}))
vi.mock('@/lib/launch-agent-web-host-tab', () => ({
launchAgentInWebHostTab: mockLaunchAgentInWebHostTab
@@ -3,7 +3,7 @@ import { BROWSER_SCREENCAST_RUNTIME_CAPABILITY } from '../../../shared/protocol-
import type { RuntimeStatus } from '../../../shared/runtime-types'
import type { AppState } from '@/store/types'
import { isPairedWebClientWindow } from './desktop-window-chrome'
import { runtimeEnvironmentIdWithLegacyFallbackUnlessTwin } from './worktree-runtime-owner'
import { runtimeEnvironmentIdOrLocalUnlessTwin } from './worktree-runtime-owner'
import { expectsBrowserClientHosting } from '../../../shared/browser-client-hosting-eligibility'
import {
getOrcadSshDeployment,
@@ -77,7 +77,7 @@ export function getClientCreationActionPolicy(
const runtimeEnvironmentId = floatingWorkspace
? null
: worktreeId
? runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(state, worktreeId)
? runtimeEnvironmentIdOrLocalUnlessTwin(state, worktreeId)
: (state.settings?.activeRuntimeEnvironmentId?.trim() ?? null)
const runtimeStatus = runtimeEnvironmentId
? (state.runtimeStatusByEnvironmentId?.get(runtimeEnvironmentId)?.status ?? null)
@@ -12,8 +12,7 @@ vi.mock('@/lib/connection-owner-resolution', () => ({
getConnectionIdForFileFromState: () => mocks.connectionId
}))
vi.mock('@/lib/worktree-runtime-owner', () => ({
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin: () =>
mocks.ambiguous ? null : mocks.environmentId
runtimeEnvironmentIdOrLocalUnlessTwin: () => (mocks.ambiguous ? null : mocks.environmentId)
}))
import {
+2 -2
View File
@@ -1,7 +1,7 @@
import type { DocPreviewGrantRequest } from '../../../preload/api/doc-preview-api'
import { basename, dirname, getRelativePathInsideRoot } from '@/lib/path'
import { getConnectionIdForFileFromState } from '@/lib/connection-owner-resolution'
import { runtimeEnvironmentIdWithLegacyFallbackUnlessTwin } from '@/lib/worktree-runtime-owner'
import { runtimeEnvironmentIdOrLocalUnlessTwin } from '@/lib/worktree-runtime-owner'
import { toRuntimeWorktreeSelector } from '@/runtime/runtime-worktree-selector'
import { chatScratchFileAddress } from '@/runtime/runtime-chat-scratch-file-address'
import type { AppState } from '@/store/types'
@@ -44,7 +44,7 @@ export function buildDocPreviewGrantRequest(
return { owner: { kind: 'ssh', connectionId }, requestBase, root, entryRelativePath }
}
// Why: a twin names no server to mint against, so it gets no grant.
const environmentId = runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(state, worktreeId)
const environmentId = runtimeEnvironmentIdOrLocalUnlessTwin(state, worktreeId)
// Outside the workspace, a chat's scratch folder stands in for it: its host serves it as a root.
const base =
worktreeRoot && worktreeRelativePath
@@ -12,7 +12,7 @@ import {
} from '@/lib/remap-open-editor-tabs-for-path-change'
import { verifyLatchedMoveDestinations } from '@/hooks/useEditorExternalWatch'
import { notifyHostOfMirroredEditorClose } from '@/runtime/close-mirrored-editor-tab'
import { runtimeEnvironmentIdWithLegacyFallbackUnlessTwin } from '@/lib/worktree-runtime-owner'
import { runtimeEnvironmentIdOrLocalUnlessTwin } from '@/lib/worktree-runtime-owner'
import { editorMoveOwnerFromContext, isOpenFileOnMoveOwner } from '@/lib/open-editor-move-owner'
import { documentFolderAccess } from '@/lib/local-file-access'
import { contentRekeyOutcome } from '@/runtime/layout-owner-content-rekeys'
@@ -55,7 +55,7 @@ export async function executeOpenEditorPathMove(args: {
const addScope = (wtId: string, owner: string | null): void => {
scopes.set(`${wtId}::${owner ?? 'local'}`, { worktreeId: wtId, owner })
}
addScope(worktreeId, runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(moveState, worktreeId))
addScope(worktreeId, runtimeEnvironmentIdOrLocalUnlessTwin(moveState, worktreeId))
for (const f of affected) {
addScope(f.worktreeId, f.runtimeEnvironmentId?.trim() || null)
}
+2 -4
View File
@@ -14,7 +14,7 @@ import { getLocalProjectExecutionRuntimeContext } from './local-preflight-contex
import { CLIENT_PLATFORM } from './new-workspace'
import {
getExecutionHostIdForWorktree,
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin
runtimeEnvironmentIdOrLocalUnlessTwin
} from './worktree-runtime-owner'
/**
@@ -148,9 +148,7 @@ export function resolveWorktreeExecutionHostPlatform(
nestedSshTargetId:
host?.kind === 'runtime' ? (getConnectionIdFromState(state, worktreeId) ?? null) : null,
sshOwnerEnvironmentId:
host?.kind === 'ssh'
? runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(state, worktreeId)
: null,
host?.kind === 'ssh' ? runtimeEnvironmentIdOrLocalUnlessTwin(state, worktreeId) : null,
workspacePath,
projectRuntime: () => getLocalProjectExecutionRuntimeContext(state, worktreeId)
})
@@ -18,7 +18,7 @@ vi.mock('@/lib/client-creation-action-policy', () => ({
getClientCreationActionPolicy: () => ({ 'managed-browser': mocks.browserAvailability })
}))
vi.mock('@/lib/worktree-runtime-owner', () => ({
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin: () => mocks.environmentId
runtimeEnvironmentIdOrLocalUnlessTwin: () => mocks.environmentId
}))
const storeState = {
+1 -1
View File
@@ -52,7 +52,7 @@ vi.mock('@/lib/client-creation-action-policy', () => ({
}))
vi.mock('@/lib/worktree-runtime-owner', () => ({
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin: () => mocks.environmentId
runtimeEnvironmentIdOrLocalUnlessTwin: () => mocks.environmentId
}))
vi.mock('@/store', () => ({
+4 -4
View File
@@ -7,7 +7,7 @@ import { useShallow } from 'zustand/react/shallow'
import { basename, getRelativePathInsideRoot } from '@/lib/path'
import { getConnectionIdForFileFromState } from '@/lib/connection-owner-resolution'
import { activateBrowserWorkspaceTab } from '@/lib/browser-workspace-tab-activation'
import { runtimeEnvironmentIdWithLegacyFallbackUnlessTwin } from '@/lib/worktree-runtime-owner'
import { runtimeEnvironmentIdOrLocalUnlessTwin } from '@/lib/worktree-runtime-owner'
import { isTwinWorkspace } from '@/lib/resolve-owner'
import {
REMOTE_FILE_BROWSER_UNSUPPORTED_MESSAGE,
@@ -99,7 +99,7 @@ export function getWorkspaceFilePreviewPlan(
}
// Why: the doc preview needs no browser at all, so a paired runtime without the
// screencast capability still previews documents.
if (runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(state, worktreeId)) {
if (runtimeEnvironmentIdOrLocalUnlessTwin(state, worktreeId)) {
const worktreeRoot = state.getKnownWorktreeById(worktreeId)?.path ?? null
const outsideWorktree = worktreeRoot && !getRelativePathInsideRoot(filePath, worktreeRoot)
// Why: the host's files.read is worktree-scoped (plus chat scratch folders, served by path), so
@@ -140,7 +140,7 @@ export function useWorkspaceFileBrowserActionPredicate(
? getClientCreationActionPolicy(state, worktreeId)['managed-browser'].state
: null,
runtimeEnvironmentId: worktreeId
? runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(state, worktreeId)
? runtimeEnvironmentIdOrLocalUnlessTwin(state, worktreeId)
: null,
folderWorkspaces: selectFolderWorkspaces(state),
projectGroups: state.projectGroups,
@@ -292,7 +292,7 @@ export function openFilePreviewToSide(params: {
// Why the unfocused split on a paired workspace: the preview opens in the background, and a host
// snapshot reads an activated empty group as a terminal pane.
const activate = !runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(state, worktreeId)
const activate = !runtimeEnvironmentIdOrLocalUnlessTwin(state, worktreeId)
const placement = existingSibling
? { targetGroupId: existingSibling }
: { split: { sourceGroupId, splitDirection: 'right' as const, activate } }
@@ -19,7 +19,7 @@ import {
} from '@/components/terminal-pane/pty-dispatcher'
import { subscribeToPtyData } from '@/components/terminal-pane/pty-data-sidecar-subscriptions'
import { callRuntimeRpc } from '@/runtime/runtime-rpc-client'
import { runtimeEnvironmentIdWithLegacyFallbackUnlessTwin } from '@/lib/worktree-runtime-owner'
import { runtimeEnvironmentIdOrLocalUnlessTwin } from '@/lib/worktree-runtime-owner'
import { retireProvider } from '@/lib/retire-unowned-background-terminal'
import { createRuntimeAgentBackgroundTerminal } from '@/lib/runtime-agent-background-create'
import { subscribeToRuntimeTerminalData } from '@/runtime/runtime-terminal-stream'
@@ -57,7 +57,7 @@ export async function launchAgentBackgroundSession(
const { platform: launchPlatform, isRemote, isLocalHost } = launchHost
// Route by the worktree's owner host, not the focused runtime.
const runtimeTarget = runtimeTargetForOwnerEnvironment(
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(store, worktreeId)
runtimeEnvironmentIdOrLocalUnlessTwin(store, worktreeId)
)
if (hasExtraAgentArgs(args.extraAgentArgs) && runtimeTarget.kind === 'environment') {
// Why: that server rebuilds the command from its own settings and would drop the extras.
@@ -44,7 +44,7 @@ vi.mock('@/runtime/web-runtime-session-environment', () => ({
vi.mock('@/lib/worktree-runtime-owner', () => ({
getExecutionHostIdForWorktree: () => 'local',
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin: () => 'web-runtime'
runtimeEnvironmentIdOrLocalUnlessTwin: () => 'web-runtime'
}))
vi.mock('@/lib/launch-agent-web-host-tab', () => ({
@@ -69,7 +69,7 @@ vi.mock('@/runtime/web-runtime-session-environment', () => ({
vi.mock('@/lib/worktree-runtime-owner', () => ({
getExecutionHostIdForWorktree: () => 'local',
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin: () => null
runtimeEnvironmentIdOrLocalUnlessTwin: () => null
}))
vi.mock('@/components/tab-bar/reconcile-order', () => ({
@@ -13,7 +13,7 @@ import {
launchNewTabPromptThroughHost,
newTabPromptLaunchesThroughHost
} from '@/lib/launch-agent-new-tab-host-route'
import { runtimeEnvironmentIdWithLegacyFallbackUnlessTwin } from '@/lib/worktree-runtime-owner'
import { runtimeEnvironmentIdOrLocalUnlessTwin } from '@/lib/worktree-runtime-owner'
import { isWebRuntimeSessionActive } from '@/runtime/web-runtime-session-environment'
import { launchAgentInWebHostTab } from '@/lib/launch-agent-web-host-tab'
import {
@@ -222,7 +222,7 @@ function launchAgentInNewTabInternal(args: LaunchAgentInNewTabArgs): LaunchAgent
}
args.focusIntent?.cancel()
const runtimeEnvironmentId = runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(store, worktreeId)
const runtimeEnvironmentId = runtimeEnvironmentIdOrLocalUnlessTwin(store, worktreeId)
if (isWebRuntimeSessionActive(runtimeEnvironmentId)) {
if (beforeSurfaceOpen?.({ kind: 'host-published' }) === false) {
return null
@@ -180,7 +180,7 @@ vi.mock('@/runtime/local-runtime-capabilities', () => ({
vi.mock('@/lib/worktree-runtime-owner', () => ({
getExecutionHostIdForWorktree: () =>
store.repos[0]?.connectionId ? `ssh:${store.repos[0].connectionId}` : 'local',
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin: () => null
runtimeEnvironmentIdOrLocalUnlessTwin: () => null
}))
vi.mock('@/runtime/structured-agent-session-client', () => ({
callStructuredAgentSession: (target: unknown, method: string, params: unknown) =>
@@ -11,7 +11,7 @@ const mockSetActiveTabType = vi.fn()
const mockSetTabBarOrder = vi.fn()
const runtimeMocks = vi.hoisted(() => ({
createWebRuntimeSessionTerminal: vi.fn(),
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin: vi.fn<() => string | null>(() => null),
runtimeEnvironmentIdOrLocalUnlessTwin: vi.fn<() => string | null>(() => null),
isWebRuntimeSessionActive: vi.fn(() => false)
}))
@@ -47,8 +47,7 @@ vi.mock('@/lib/telemetry', () => ({
}))
vi.mock('@/lib/worktree-runtime-owner', () => ({
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin:
runtimeMocks.runtimeEnvironmentIdWithLegacyFallbackUnlessTwin
runtimeEnvironmentIdOrLocalUnlessTwin: runtimeMocks.runtimeEnvironmentIdOrLocalUnlessTwin
}))
vi.mock('@/runtime/web-runtime-terminal-creation', () => ({
@@ -63,7 +62,7 @@ import { launchAiVaultSessionInNewTab } from './launch-ai-vault-session'
describe('launchAiVaultSessionInNewTab', () => {
beforeEach(() => {
vi.clearAllMocks()
runtimeMocks.runtimeEnvironmentIdWithLegacyFallbackUnlessTwin.mockReturnValue(null)
runtimeMocks.runtimeEnvironmentIdOrLocalUnlessTwin.mockReturnValue(null)
runtimeMocks.isWebRuntimeSessionActive.mockReturnValue(false)
runtimeMocks.createWebRuntimeSessionTerminal.mockResolvedValue({ status: 'created' })
mockState.tabsByWorktree = {}
@@ -159,7 +158,7 @@ describe('launchAiVaultSessionInNewTab', () => {
})
it('creates runtime-hosted resume terminals through the paired host', async () => {
runtimeMocks.runtimeEnvironmentIdWithLegacyFallbackUnlessTwin.mockReturnValue('env-1')
runtimeMocks.runtimeEnvironmentIdOrLocalUnlessTwin.mockReturnValue('env-1')
runtimeMocks.isWebRuntimeSessionActive.mockReturnValue(true)
const result = launchAiVaultSessionInNewTab({
@@ -211,7 +210,7 @@ describe('launchAiVaultSessionInNewTab', () => {
// #22791: a drop on a pane edge in a paired-runtime worktree opened a tab instead of a split.
it('splits the pane before a runtime-hosted resume and targets the new group', async () => {
runtimeMocks.runtimeEnvironmentIdWithLegacyFallbackUnlessTwin.mockReturnValue('env-1')
runtimeMocks.runtimeEnvironmentIdOrLocalUnlessTwin.mockReturnValue('env-1')
runtimeMocks.isWebRuntimeSessionActive.mockReturnValue(true)
const result = launchAiVaultSessionInNewTab({
@@ -234,7 +233,7 @@ describe('launchAiVaultSessionInNewTab', () => {
})
it('removes the still-empty split group when the runtime create does not succeed', async () => {
runtimeMocks.runtimeEnvironmentIdWithLegacyFallbackUnlessTwin.mockReturnValue('env-1')
runtimeMocks.runtimeEnvironmentIdOrLocalUnlessTwin.mockReturnValue('env-1')
runtimeMocks.isWebRuntimeSessionActive.mockReturnValue(true)
runtimeMocks.createWebRuntimeSessionTerminal.mockResolvedValue({
status: 'failed',
@@ -1,7 +1,7 @@
import { useAppStore } from '@/store'
import { persistAgentLaunchTabOrder } from '@/lib/launch-agent-tab-order'
import { tuiAgentToAgentKind } from '@/lib/telemetry'
import { runtimeEnvironmentIdWithLegacyFallbackUnlessTwin } from '@/lib/worktree-runtime-owner'
import { runtimeEnvironmentIdOrLocalUnlessTwin } from '@/lib/worktree-runtime-owner'
import { createWebRuntimeSessionTerminal } from '@/runtime/web-runtime-terminal-creation'
import { isWebRuntimeSessionActive } from '@/runtime/web-runtime-session-environment'
import type { AiVaultAgent } from '../../../shared/ai-vault-types'
@@ -34,10 +34,7 @@ export function launchAiVaultSessionInNewTab(args: {
args.splitDirection && args.targetGroupId
? { sourceGroupId: args.targetGroupId, splitDirection: args.splitDirection }
: undefined
const runtimeEnvironmentId = runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(
store,
args.worktreeId
)
const runtimeEnvironmentId = runtimeEnvironmentIdOrLocalUnlessTwin(store, args.worktreeId)
if (isWebRuntimeSessionActive(runtimeEnvironmentId)) {
// Why an empty group first: the paired host creates the tab after a round trip.
const splitGroupId = split
@@ -1,5 +1,5 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import { runtimeEnvironmentIdWithLegacyFallbackUnlessTwin } from './worktree-runtime-owner'
import { runtimeEnvironmentIdOrLocalUnlessTwin } from './worktree-runtime-owner'
import type { WorktreeRuntimeOwnerState } from './worktree-runtime-owner-state'
import { resolveWorktreeOperationRoute } from './worktree-operation-route'
import { resolveTerminalHostOwnership } from './terminal-worktree-route'
@@ -42,7 +42,7 @@ describe('a paired web client routes every owner through its server (#9047)', ()
webState({ activeWorktreeId: WT, activeWorkspaceExecutionHostId: 'local' }),
webState(UNSTAMPED_LOCAL_ROW)
]) {
expect(runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(state, WT)).toBe(ENV)
expect(runtimeEnvironmentIdOrLocalUnlessTwin(state, WT)).toBe(ENV)
expect(resolveTerminalHostOwnership(state, WT, 'spawn')).toEqual({
kind: 'runtime',
runtimeEnvironmentId: ENV
@@ -64,9 +64,9 @@ describe('a paired web client routes every owner through its server (#9047)', ()
{ id: 'folder-1', projectGroupId: 'group-1', executionHostId: 'local', connectionId: null }
]
})
expect(
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(folderState, folderWorkspaceKey('folder-1'))
).toBe(ENV)
expect(runtimeEnvironmentIdOrLocalUnlessTwin(folderState, folderWorkspaceKey('folder-1'))).toBe(
ENV
)
})
it('keeps an SSH target as the place and the server as the transport', () => {
@@ -88,12 +88,12 @@ describe('a paired web client routes every owner through its server (#9047)', ()
]
}
})
expect(runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(state, WT)).toBeNull()
expect(runtimeEnvironmentIdOrLocalUnlessTwin(state, WT)).toBeNull()
})
it('leaves a desktop window local workspace on this computer', () => {
const state = webState(UNSTAMPED_LOCAL_ROW)
expect(runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(state, WT)).toBeNull()
expect(runtimeEnvironmentIdOrLocalUnlessTwin(state, WT)).toBeNull()
expect(resolveTerminalHostOwnership(state, WT, 'spawn').kind).toBe('local-or-ssh')
})
})
@@ -1,5 +1,5 @@
/** Hand-reviewed owner per matrix case for `resolveOwnerWithLegacyFallback` (round-3 design §5). */
export const LEGACY_FALLBACK_OWNER_ACCEPTANCE: Record<string, string> = {
/** Hand-reviewed owner per matrix case for `resolveOwnerOrLocal` (round-3 design §5). */
export const OWNER_OR_LOCAL_ACCEPTANCE: Record<string, string> = {
'focused | global-floating-terminal': 'self@local',
'focused | local-repo::wt': 'self@local',
'focused | legacy-repo::wt': 'self@local',
@@ -1,9 +1,9 @@
import { describe, expect, it } from 'vitest'
import type { ExecutionHostOwnerMatch } from '../../../shared/worktree-execution-host-resolution'
import type { HostAuthority } from '../../../shared/host-authority'
import { resolveOwnerWithLegacyFallback } from './worktree-runtime-owner'
import { resolveOwnerOrLocal } from './worktree-runtime-owner'
import { OWNER_RESOLUTION_CASES } from './runtime-owner-alias-matrix.test-fixture'
import { LEGACY_FALLBACK_OWNER_ACCEPTANCE } from './resolve-owner-acceptance.test-fixture'
import { OWNER_OR_LOCAL_ACCEPTANCE } from './resolve-owner-acceptance.test-fixture'
import { LEGACY_RUNTIME_OWNER_GOLDEN } from './legacy-runtime-owner-answers.test-fixture'
import type { WorktreeRuntimeOwnerState } from './worktree-runtime-owner-state'
@@ -33,20 +33,20 @@ const DELIBERATE_CHANGES: Record<string, string> = {
'folder:missing-folder': 'same'
}
describe('resolveOwnerWithLegacyFallback acceptance (round-3 design §5)', () => {
describe('resolveOwnerOrLocal acceptance (round-3 design §5)', () => {
it('pins the owner of every matrix case', () => {
const actual = Object.fromEntries(
casesWithIds.map((c) => [
c.name,
describeMatch(resolveOwnerWithLegacyFallback(c.state, { workspaceId: c.worktreeId }))
describeMatch(resolveOwnerOrLocal(c.state, { workspaceId: c.worktreeId }))
])
)
expect(actual).toEqual(LEGACY_FALLBACK_OWNER_ACCEPTANCE)
expect(actual).toEqual(OWNER_OR_LOCAL_ACCEPTANCE)
})
it('differs from the deprecated resolver only in the deliberate cases', () => {
const changed = casesWithIds.filter((c) => {
const owner = resolveOwnerWithLegacyFallback(c.state, { workspaceId: c.worktreeId })
const owner = resolveOwnerOrLocal(c.state, { workspaceId: c.worktreeId })
const environmentId =
owner.kind === 'resolved' && owner.owner.endpoint.kind === 'environment'
? owner.owner.endpoint.environmentId
@@ -64,13 +64,13 @@ describe('resolveOwnerWithLegacyFallback acceptance (round-3 design §5)', () =>
})
})
describe('resolveOwnerWithLegacyFallback probes', () => {
describe('resolveOwnerOrLocal probes', () => {
const base = OWNER_RESOLUTION_CASES.find((c) => c.name === 'focused | stale-hub-repo::wt')!.state
const single = OWNER_RESOLUTION_CASES.find(
(c) => c.name === 'single focus | legacy-repo::wt'
)!.state
const ownerOf = (state: WorktreeRuntimeOwnerState, workspaceId: string, hostId?: 'local') =>
describeMatch(resolveOwnerWithLegacyFallback(state, { workspaceId, hostId }))
describeMatch(resolveOwnerOrLocal(state, { workspaceId, hostId }))
it('reads a genuine twin as ambiguous', () => {
const withDirect = {
@@ -19,7 +19,7 @@ vi.mock('@/components/native-chat/use-native-chat-composer-reveal-focus', () =>
}))
vi.mock('./worktree-runtime-owner', () => ({
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin: () => 'env-1'
runtimeEnvironmentIdOrLocalUnlessTwin: () => 'env-1'
}))
vi.mock('@/runtime/runtime-rpc-client', () => ({ callRuntimeRpc: mocks.callRuntimeRpc }))
@@ -1,4 +1,4 @@
import { runtimeEnvironmentIdWithLegacyFallbackUnlessTwin } from './worktree-runtime-owner'
import { runtimeEnvironmentIdOrLocalUnlessTwin } from './worktree-runtime-owner'
import { requestNativeChatComposerFocus } from '@/components/native-chat/use-native-chat-composer-reveal-focus'
import { useAppStore } from '@/store'
import { callRuntimeRpc } from '@/runtime/runtime-rpc-client'
@@ -49,7 +49,7 @@ export function activateStructuredAgentSessionTab(args: {
state.activateTab(tab.id, { worktreeId: args.worktreeId })
state.setActiveTabType('agent-session', args.worktreeId)
requestNativeChatComposerFocus(tab.id)
const environmentId = runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(state, args.worktreeId)
const environmentId = runtimeEnvironmentIdOrLocalUnlessTwin(state, args.worktreeId)
void callRuntimeRpc(
args.target ?? runtimeTargetForOwnerEnvironment(environmentId),
'session.tabs.activate',
+2 -2
View File
@@ -1,7 +1,7 @@
import { focusTerminalTabSurface } from '@/lib/focus-terminal-tab-surface'
import { useAppStore } from '@/store'
import type { AppState } from '@/store/types'
import { runtimeEnvironmentIdWithLegacyFallbackUnlessTwin } from '@/lib/worktree-runtime-owner'
import { runtimeEnvironmentIdOrLocalUnlessTwin } from '@/lib/worktree-runtime-owner'
import { dedupeTabOrder } from '../../../shared/workspace-layout/tab-order'
import type { Tab } from '../../../shared/tab-types'
import { activateWebRuntimeSessionTab } from '@/runtime/web-runtime-session-tab-lifecycle'
@@ -60,7 +60,7 @@ export function activateTabNumberShortcut(index: number): boolean {
}
const worktreeId = target.worktreeId
const runtimeEnvironmentId = runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(store, worktreeId)
const runtimeEnvironmentId = runtimeEnvironmentIdOrLocalUnlessTwin(store, worktreeId)
store.focusGroup(worktreeId, target.groupId)
store.activateTab(target.id)
@@ -2,14 +2,13 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
const mocks = vi.hoisted(() => ({
activateWebRuntimeSessionTab: vi.fn(),
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin: vi.fn(),
runtimeEnvironmentIdOrLocalUnlessTwin: vi.fn(),
resolveWebSessionVisibleTabId: vi.fn()
}))
vi.mock('@/store', () => ({ useAppStore: { getState: () => ({}) } }))
vi.mock('@/lib/worktree-runtime-owner', () => ({
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin:
mocks.runtimeEnvironmentIdWithLegacyFallbackUnlessTwin
runtimeEnvironmentIdOrLocalUnlessTwin: mocks.runtimeEnvironmentIdOrLocalUnlessTwin
}))
vi.mock('@/runtime/web-runtime-session-tab-lifecycle', () => ({
activateWebRuntimeSessionTab: mocks.activateWebRuntimeSessionTab
@@ -31,12 +30,12 @@ describe('activateTerminalTabOnOwner', () => {
})
it('tells the paired server that owns the workspace, not the focused one', () => {
mocks.runtimeEnvironmentIdWithLegacyFallbackUnlessTwin.mockReturnValue('env-owner')
mocks.runtimeEnvironmentIdOrLocalUnlessTwin.mockReturnValue('env-owner')
mocks.resolveWebSessionVisibleTabId.mockReturnValue('unified-visible')
activateTerminalTabOnOwner('wt-1', 'web-terminal-host-tab', 'leaf-b')
expect(mocks.runtimeEnvironmentIdWithLegacyFallbackUnlessTwin).toHaveBeenCalledWith({}, 'wt-1')
expect(mocks.runtimeEnvironmentIdOrLocalUnlessTwin).toHaveBeenCalledWith({}, 'wt-1')
expect(mocks.activateWebRuntimeSessionTab).toHaveBeenCalledWith({
worktreeId: 'wt-1',
tabId: 'web-terminal-host-tab',
@@ -48,7 +47,7 @@ describe('activateTerminalTabOnOwner', () => {
})
it('makes no owner round trip for a workspace this client runs', () => {
mocks.runtimeEnvironmentIdWithLegacyFallbackUnlessTwin.mockReturnValue(null)
mocks.runtimeEnvironmentIdOrLocalUnlessTwin.mockReturnValue(null)
activateTerminalTabOnOwner('wt-local', 'tab-1', null)
@@ -1,5 +1,5 @@
import { useAppStore } from '@/store'
import { runtimeEnvironmentIdWithLegacyFallbackUnlessTwin } from '@/lib/worktree-runtime-owner'
import { runtimeEnvironmentIdOrLocalUnlessTwin } from '@/lib/worktree-runtime-owner'
import { activateWebRuntimeSessionTab } from '@/runtime/web-runtime-session-tab-lifecycle'
import { isWebRuntimeSessionActive } from '@/runtime/web-runtime-session-environment'
import { resolveWebSessionVisibleTabId } from '@/runtime/web-session-focus-intent'
@@ -17,7 +17,7 @@ export function activateTerminalTabOnOwner(
leafId: string | null
): void {
const state = useAppStore.getState()
const environmentId = runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(state, worktreeId)
const environmentId = runtimeEnvironmentIdOrLocalUnlessTwin(state, worktreeId)
if (!environmentId || !isWebRuntimeSessionActive(environmentId)) {
return
}
@@ -3,7 +3,7 @@ import { isEphemeralSetupTerminalWorktreeId } from '../../../shared/ephemeral-se
import { parseExecutionHostId } from '../../../shared/execution-host'
import { parseWorkspaceKey } from '../../../shared/workspace-scope'
import type { AppState } from '@/store/types'
import { resolveOwnerWithLegacyFallback } from './worktree-runtime-owner'
import { resolveOwnerOrLocal } from './worktree-runtime-owner'
import { namedHostForOwnerRef, runtimeEnvironmentIdUnlessTwin } from './resolve-owner'
import type { WorktreeRuntimeOwnerState } from './worktree-runtime-owner-state'
import {
@@ -69,7 +69,7 @@ export function resolveTerminalHostOwnership(
worktreeId === FLOATING_TERMINAL_WORKTREE_ID ||
parseWorkspaceKey(worktreeId)?.type === 'folder'
) {
const owner = resolveOwnerWithLegacyFallback(state, { workspaceId: worktreeId })
const owner = resolveOwnerOrLocal(state, { workspaceId: worktreeId })
if (owner.kind !== 'resolved') {
// Why: spawn must not pick a twin; a twin's runtime PTYs carry `remote:` ids, so a raw id is
// local or SSH on either copy.
@@ -8,7 +8,7 @@ import {
beginWebRuntimeWakeTerminalRespawn,
endWebRuntimeWakeTerminalRespawn
} from '@/runtime/web-runtime-wake-terminal-respawn'
import { runtimeEnvironmentIdWithLegacyFallbackUnlessTwin } from '@/lib/worktree-runtime-owner'
import { runtimeEnvironmentIdOrLocalUnlessTwin } from '@/lib/worktree-runtime-owner'
import type { WorktreeStartupPayload } from '@/lib/worktree-startup-payload'
import type { TuiAgent } from '../../../shared/tui-agent'
import { toast } from 'sonner'
@@ -35,7 +35,7 @@ export function ensureWebRuntimeWorktreeTerminalAfterWake(
const runtimeEnvironmentId =
opts && 'runtimeEnvironmentId' in opts
? (opts.runtimeEnvironmentId ?? null)
: runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(state, worktreeId)
: runtimeEnvironmentIdOrLocalUnlessTwin(state, worktreeId)
if (!runtimeEnvironmentId || !isWebRuntimeSessionActive(runtimeEnvironmentId)) {
return
}
@@ -13,7 +13,7 @@ const mocks = vi.hoisted(() => ({
}))
vi.mock('@/lib/worktree-runtime-owner', () => ({
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin: () => mocks.environmentId
runtimeEnvironmentIdOrLocalUnlessTwin: () => mocks.environmentId
}))
vi.mock('@/store', () => ({
@@ -2,7 +2,7 @@ import { absolutePathToFileUri } from '@/components/editor/markdown-internal-lin
import { getConnectionIdForFile } from '@/lib/connection-context'
import { basename } from '@/lib/path'
import { isTwinWorkspace } from '@/lib/resolve-owner'
import { runtimeEnvironmentIdWithLegacyFallbackUnlessTwin } from '@/lib/worktree-runtime-owner'
import { runtimeEnvironmentIdOrLocalUnlessTwin } from '@/lib/worktree-runtime-owner'
import { useAppStore } from '@/store'
/** Still the answer for flows that need a real `file://` URL (e.g. dropping a file on a browser pane). */
@@ -39,7 +39,7 @@ export function getWorkspaceFileBrowserOpenTarget(params: {
}
if (
getConnectionIdForFile(params.worktreeId, params.filePath) !== null ||
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(state, params.worktreeId)
runtimeEnvironmentIdOrLocalUnlessTwin(state, params.worktreeId)
) {
// Why: Chromium resolves file:// URLs on the local machine. Remote files
// need an Orca-served URL before the browser can render them correctly.
@@ -63,7 +63,7 @@ const mocks = vi.hoisted(() => {
activateAndRevealWorkspace: vi.fn(),
activateWebRuntimeSessionTab: vi.fn(),
focusTerminalTabSurface: vi.fn(),
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin: vi.fn(),
runtimeEnvironmentIdOrLocalUnlessTwin: vi.fn(),
isWebRuntimeSessionActive: vi.fn()
}
})
@@ -79,8 +79,7 @@ vi.mock('@/lib/focus-terminal-tab-surface', () => ({
}))
vi.mock('@/lib/worktree-runtime-owner', () => ({
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin:
mocks.runtimeEnvironmentIdWithLegacyFallbackUnlessTwin
runtimeEnvironmentIdOrLocalUnlessTwin: mocks.runtimeEnvironmentIdOrLocalUnlessTwin
}))
vi.mock('@/runtime/web-runtime-session-tab-lifecycle', () => ({
@@ -173,7 +172,7 @@ describe('activateWorkspaceTabPaletteResult', () => {
.find((worktree) => worktree.id === worktreeId)
)
mocks.activateAndRevealWorkspace.mockReturnValue(true)
mocks.runtimeEnvironmentIdWithLegacyFallbackUnlessTwin.mockReturnValue('runtime-1')
mocks.runtimeEnvironmentIdOrLocalUnlessTwin.mockReturnValue('runtime-1')
mocks.isWebRuntimeSessionActive.mockReturnValue(false)
})
@@ -1,5 +1,5 @@
import { focusTerminalTabSurface } from '@/lib/focus-terminal-tab-surface'
import { runtimeEnvironmentIdWithLegacyFallbackUnlessTwin } from '@/lib/worktree-runtime-owner'
import { runtimeEnvironmentIdOrLocalUnlessTwin } from '@/lib/worktree-runtime-owner'
import { activateWebRuntimeSessionTab } from '@/runtime/web-runtime-session-tab-lifecycle'
import { isWebRuntimeSessionActive } from '@/runtime/web-runtime-session-environment'
import { useAppStore } from '@/store'
@@ -119,10 +119,7 @@ export function activateWorkspaceTabPaletteResult(
return { status: 'failed', reason: finalFailure }
}
const runtimeEnvironmentId = runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(
state,
result.worktreeId
)
const runtimeEnvironmentId = runtimeEnvironmentIdOrLocalUnlessTwin(state, result.worktreeId)
state.focusGroup(result.worktreeId, result.groupId)
state.activateTab(result.tabId, { worktreeId: result.worktreeId })
@@ -2,7 +2,7 @@ import { FLOATING_TERMINAL_WORKTREE_ID } from '../../../shared/constants'
import { parseExecutionHostId } from '../../../shared/execution-host'
import type { PtyListedSession, PtySessionListScope } from '../../../shared/pty-listed-session'
import { getRepoIdFromWorktreeId } from '../../../shared/worktree/id'
import { runtimeEnvironmentIdWithLegacyFallbackUnlessTwin } from './worktree-runtime-owner'
import { runtimeEnvironmentIdOrLocalUnlessTwin } from './worktree-runtime-owner'
import {
resolveIndexedRepoOwner,
resolveIndexedWorktreeOwner
@@ -21,10 +21,7 @@ export function resolveActivationPtyListScope(
return { connectionId: null }
}
const resolution = resolveWorktreeOperationRouteResult(state, worktreeId)
if (
resolution.kind === 'missing' &&
!runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(state, worktreeId)
) {
if (resolution.kind === 'missing' && !runtimeEnvironmentIdOrLocalUnlessTwin(state, worktreeId)) {
const repo = resolveIndexedRepoOwner(state.repos, getRepoIdFromWorktreeId(worktreeId))
const worktree = resolveIndexedWorktreeOwner(state.worktreesByRepo, worktreeId)
// A known native repo remains usable while the unrelated runtime catalog hydrates.
+2 -2
View File
@@ -7,7 +7,7 @@ import { registerWorktreeActivation } from '@/lib/worktree-activation-nav-regist
import { workspaceHasSleepingAgentSessions } from '@/lib/worktree-agent-activation-gate'
import { resumeSleepingAgentSessionsForWorktree } from '@/lib/resume-sleeping-agent-session'
import { shouldAutoCreateInitialTerminal } from '@/components/terminal/initial-terminal'
import { runtimeEnvironmentIdWithLegacyFallbackUnlessTwin } from '@/lib/worktree-runtime-owner'
import { runtimeEnvironmentIdOrLocalUnlessTwin } from '@/lib/worktree-runtime-owner'
import { folderWorkspaceKey, parseWorkspaceKey } from '../../../shared/workspace-scope'
import {
folderWorkspaceActivationBlocked,
@@ -211,7 +211,7 @@ export function activateAndRevealWorktree(
// 3. Core activation: setActiveWorktree also restores per-worktree state, clears unread, bumps dead PTY generations, refreshes GitHub
state.setActiveWorktree(worktreeId, opts?.executionHostId)
const postActivationState = useAppStore.getState()
const ownerRuntimeEnvironmentId = runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(
const ownerRuntimeEnvironmentId = runtimeEnvironmentIdOrLocalUnlessTwin(
postActivationState,
wt.id
)
@@ -2,7 +2,7 @@ import { useAppStore } from '@/store'
import { seedNativeChatAppliedSessionOptions } from '@/components/native-chat/native-chat-session-option-cache'
import { seedNativeChatLaunchDraftForAgentTab } from '@/lib/agent-launch-prompt-delivery'
import { queueHookCommandsForFirstWorktreeTab } from '@/lib/hook-command-delayed-delivery'
import { runtimeEnvironmentIdWithLegacyFallbackUnlessTwin } from '@/lib/worktree-runtime-owner'
import { runtimeEnvironmentIdOrLocalUnlessTwin } from '@/lib/worktree-runtime-owner'
import { toWebTerminalSurfaceTabId } from '../../../shared/terminal-surface-id'
import type { WorktreeCreationRequest } from '@/lib/pending-worktree-creation'
import type { TuiAgent } from '../../../shared/tui-agent'
@@ -31,7 +31,7 @@ function resolveLaunchAgentTabId(
const worktreeTabs = state.tabsByWorktree[args.worktreeId] ?? []
// Main spawned the agent itself, so its startup tab is authoritative.
if (args.backendSpawned && args.startupTerminalTabId) {
return runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(state, args.worktreeId)
return runtimeEnvironmentIdOrLocalUnlessTwin(state, args.worktreeId)
? toWebTerminalSurfaceTabId(args.startupTerminalTabId)
: args.startupTerminalTabId
}
@@ -7,7 +7,7 @@ import { waitForWorktreeAgentActivationGateForTests } from './worktree-agent-act
import { makeCreatedAgentWorktree as makeWorktree } from '@/lib/worktree-activation-created-agent.test-fixture'
import { makePaneKey } from '../../../shared/stable-pane-id'
import type { ExecutionHostId } from '../../../shared/execution-host'
import { runtimeEnvironmentIdWithLegacyFallbackUnlessTwin } from './worktree-runtime-owner'
import { runtimeEnvironmentIdOrLocalUnlessTwin } from './worktree-runtime-owner'
import {
markHostSessionMirrorHydrated,
resetHostSessionMirrorHydrationForTests
@@ -331,9 +331,9 @@ describe('preserved-pane replacement contract on workspace activation', () => {
}
}))
expect(
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(useAppStore.getState(), worktree.id)
).toBe(RUNTIME_ENV_ID)
expect(runtimeEnvironmentIdOrLocalUnlessTwin(useAppStore.getState(), worktree.id)).toBe(
RUNTIME_ENV_ID
)
activateAndRevealWorktree(worktree.id, { notifyHostRuntime: false })
await waitForWorktreeAgentActivationGateForTests(worktree.id)
@@ -1,6 +1,6 @@
import { parseExecutionHostId } from '../../../shared/execution-host'
import type { Worktree } from '../../../shared/worktree/types'
import { resolveOwnerWithLegacyFallback } from './worktree-runtime-owner'
import { resolveOwnerOrLocal } from './worktree-runtime-owner'
import type { WorktreeRuntimeOwnerState } from './worktree-runtime-owner-state'
import {
resolveWorktreeOperationRouteResultForHost,
@@ -46,7 +46,7 @@ export function resolveWorktreeRowOpenInRuntimeOwner(
}
if (!worktree.hostId) {
// Why: a twin names no owner, and Open in must not launch it as this desktop's checkout.
const owner = resolveOwnerWithLegacyFallback(state, { workspaceId: worktree.id })
const owner = resolveOwnerOrLocal(state, { workspaceId: worktree.id })
if (owner.kind === 'ambiguous') {
return UNRESOLVED
}
@@ -3,7 +3,7 @@ import { FLOATING_TERMINAL_WORKTREE_ID } from '../../../shared/constants'
import {
getExecutionHostIdForWorktree,
getKnownExecutionHostIdForWorktree,
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin
runtimeEnvironmentIdOrLocalUnlessTwin
} from './worktree-runtime-owner'
import { resolveTerminalPtyOwner } from './terminal-worktree-route'
import { getRuntimeSessionMirrorEnvironmentIds } from './runtime-session-mirror-owners'
@@ -44,15 +44,13 @@ const state: WorktreeRuntimeOwnerState = {
]
}
describe('runtimeEnvironmentIdWithLegacyFallbackUnlessTwin', () => {
describe('runtimeEnvironmentIdOrLocalUnlessTwin', () => {
it('routes to the runtime owner of the worktree', () => {
expect(runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(state, 'runtime-repo::wt-b')).toBe(
'owner-env'
)
expect(runtimeEnvironmentIdOrLocalUnlessTwin(state, 'runtime-repo::wt-b')).toBe('owner-env')
})
it('keeps explicit-local worktrees local even while a runtime is focused', () => {
expect(runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(state, 'local-repo::wt-a')).toBeNull()
expect(runtimeEnvironmentIdOrLocalUnlessTwin(state, 'local-repo::wt-a')).toBeNull()
})
it('keeps the synthetic floating workspace local while a runtime is focused', () => {
@@ -62,7 +60,7 @@ describe('runtimeEnvironmentIdWithLegacyFallbackUnlessTwin', () => {
activeWorkspaceExecutionHostId: 'runtime:focused-env'
}
expect(
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(floatingState, FLOATING_TERMINAL_WORKTREE_ID)
runtimeEnvironmentIdOrLocalUnlessTwin(floatingState, FLOATING_TERMINAL_WORKTREE_ID)
).toBeNull()
expect(getKnownExecutionHostIdForWorktree(floatingState, FLOATING_TERMINAL_WORKTREE_ID)).toBe(
'local'
@@ -73,9 +71,7 @@ describe('runtimeEnvironmentIdWithLegacyFallbackUnlessTwin', () => {
})
it('routes folder workspaces to their project group runtime owner', () => {
expect(runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(state, 'folder:runtime-folder')).toBe(
'folder-env'
)
expect(runtimeEnvironmentIdOrLocalUnlessTwin(state, 'folder:runtime-folder')).toBe('folder-env')
expect(getExecutionHostIdForWorktree(state, 'folder:runtime-folder')).toBe('runtime:folder-env')
})
@@ -111,10 +107,7 @@ describe('runtimeEnvironmentIdWithLegacyFallbackUnlessTwin', () => {
}
expect(
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(
restoredFolderState,
'folder:restored-folder'
)
runtimeEnvironmentIdOrLocalUnlessTwin(restoredFolderState, 'folder:restored-folder')
).toBe('restored-env')
expect(ownerEnvironmentId(restoredFolderState, 'folder:restored-folder')).toBe('restored-env')
expect(getExecutionHostIdForWorktree(restoredFolderState, 'folder:restored-folder')).toBe(
@@ -123,9 +116,7 @@ describe('runtimeEnvironmentIdWithLegacyFallbackUnlessTwin', () => {
})
it('keeps explicit-local folder workspaces local even while a runtime is focused', () => {
expect(
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(state, 'folder:local-folder')
).toBeNull()
expect(runtimeEnvironmentIdOrLocalUnlessTwin(state, 'folder:local-folder')).toBeNull()
expect(getExecutionHostIdForWorktree(state, 'folder:local-folder')).toBe('local')
const restoredOwnerState: WorktreeRuntimeOwnerState = {
@@ -135,7 +126,7 @@ describe('runtimeEnvironmentIdWithLegacyFallbackUnlessTwin', () => {
}
}
expect(
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(restoredOwnerState, 'folder:local-folder')
runtimeEnvironmentIdOrLocalUnlessTwin(restoredOwnerState, 'folder:local-folder')
).toBeNull()
expect(getExecutionHostIdForWorktree(restoredOwnerState, 'folder:local-folder')).toBe('local')
})
@@ -150,7 +141,7 @@ describe('runtimeEnvironmentIdWithLegacyFallbackUnlessTwin', () => {
}
expect(
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(folderConnectionState, 'folder:folder-ssh')
runtimeEnvironmentIdOrLocalUnlessTwin(folderConnectionState, 'folder:folder-ssh')
).toBeNull()
expect(getExecutionHostIdForWorktree(folderConnectionState, 'folder:folder-ssh')).toBe(
'ssh:folder-remote'
@@ -166,10 +157,7 @@ describe('runtimeEnvironmentIdWithLegacyFallbackUnlessTwin', () => {
}
expect(
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(
staleFolderConnectionState,
'folder:runtime-folder'
)
runtimeEnvironmentIdOrLocalUnlessTwin(staleFolderConnectionState, 'folder:runtime-folder')
).toBe('folder-env')
expect(getExecutionHostIdForWorktree(staleFolderConnectionState, 'folder:runtime-folder')).toBe(
'runtime:folder-env'
@@ -200,7 +188,7 @@ describe('resolveTerminalPtyOwner', () => {
kind: 'ambiguous'
})
// Why: read-only callers keep the old collapse to "no server".
expect(runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(twinState, 'twin-repo::wt')).toBeNull()
expect(runtimeEnvironmentIdOrLocalUnlessTwin(twinState, 'twin-repo::wt')).toBeNull()
})
})
@@ -228,9 +216,9 @@ describe('the explicit owner environment (resolveOwner)', () => {
}
expect(ownerEnvironmentId(nestedState, 'nested-repo::remote-worktree')).toBe('owner-hub')
expect(
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(nestedState, 'nested-repo::remote-worktree')
).toBe('owner-hub')
expect(runtimeEnvironmentIdOrLocalUnlessTwin(nestedState, 'nested-repo::remote-worktree')).toBe(
'owner-hub'
)
expect(getExecutionHostIdForWorktree(nestedState, 'nested-repo::remote-worktree')).toBe(
'ssh:hub-private-ssh'
)
@@ -349,12 +337,12 @@ describe('the explicit owner environment (resolveOwner)', () => {
}
}
expect(
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(multiHubState, 'repo-a::ssh-worktree')
).toBe('hub-a')
expect(
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(multiHubState, 'repo-b::ssh-worktree')
).toBe('hub-b')
expect(runtimeEnvironmentIdOrLocalUnlessTwin(multiHubState, 'repo-a::ssh-worktree')).toBe(
'hub-a'
)
expect(runtimeEnvironmentIdOrLocalUnlessTwin(multiHubState, 'repo-b::ssh-worktree')).toBe(
'hub-b'
)
expect(getExecutionHostIdForWorktree(multiHubState, 'repo-a::ssh-worktree')).toBe(
'ssh:private-a'
)
@@ -364,9 +352,7 @@ describe('the explicit owner environment (resolveOwner)', () => {
})
it('does not treat the focused runtime as ownership for legacy-local worktrees', () => {
expect(
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(state, 'legacy-repo::wt-legacy')
).toBeNull()
expect(runtimeEnvironmentIdOrLocalUnlessTwin(state, 'legacy-repo::wt-legacy')).toBeNull()
expect(ownerEnvironmentId(state, 'legacy-repo::wt-legacy')).toBeNull()
expect(getExecutionHostIdForWorktree(state, 'legacy-repo::wt-legacy')).toBe('local')
expect(getExecutionHostIdForWorktree(state, 'unknown-repo::wt')).toBe('local')
@@ -401,10 +387,7 @@ describe('the explicit owner environment (resolveOwner)', () => {
expect(ownerEnvironmentId(hostOverrideState, 'runtime-repo::wt-local-override')).toBeNull()
expect(
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(
hostOverrideState,
'runtime-repo::wt-local-override'
)
runtimeEnvironmentIdOrLocalUnlessTwin(hostOverrideState, 'runtime-repo::wt-local-override')
).toBeNull()
expect(
getExecutionHostIdForWorktree(hostOverrideState, 'runtime-repo::wt-local-override')
@@ -413,10 +396,7 @@ describe('the explicit owner environment (resolveOwner)', () => {
'worktree-env'
)
expect(
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(
hostOverrideState,
'runtime-repo::wt-runtime-override'
)
runtimeEnvironmentIdOrLocalUnlessTwin(hostOverrideState, 'runtime-repo::wt-runtime-override')
).toBe('worktree-env')
expect(
getExecutionHostIdForWorktree(hostOverrideState, 'runtime-repo::wt-runtime-override')
@@ -503,13 +483,10 @@ describe('runtime owner identity indexes', () => {
}
for (let lookup = 0; lookup < 200; lookup += 1) {
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(indexedState, targetWorktreeId)
runtimeEnvironmentIdOrLocalUnlessTwin(indexedState, targetWorktreeId)
ownerEnvironmentId(indexedState, targetWorktreeId)
getExecutionHostIdForWorktree(indexedState, targetWorktreeId)
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(
indexedState,
`folder:${targetFolderWorkspaceId}`
)
runtimeEnvironmentIdOrLocalUnlessTwin(indexedState, `folder:${targetFolderWorkspaceId}`)
ownerEnvironmentId(indexedState, `folder:${targetFolderWorkspaceId}`)
getExecutionHostIdForWorktree(indexedState, `folder:${targetFolderWorkspaceId}`)
}
@@ -519,9 +496,7 @@ describe('runtime owner identity indexes', () => {
expect(repoIdReads).toBe(200)
expect(folderWorkspaceIdReads).toBe(1_000)
expect(projectGroupIdReads).toBe(1_000)
expect(runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(indexedState, targetWorktreeId)).toBe(
'repo-owner'
)
expect(runtimeEnvironmentIdOrLocalUnlessTwin(indexedState, targetWorktreeId)).toBe('repo-owner')
expect(getExecutionHostIdForWorktree(indexedState, `folder:${targetFolderWorkspaceId}`)).toBe(
'runtime:folder-owner'
)
@@ -534,10 +509,8 @@ describe('runtime owner identity indexes', () => {
folderWorkspaces: [{ id: 'folder', projectGroupId: 'group' }],
projectGroups: [{ id: 'group', connectionId: null, executionHostId: 'runtime:folder-one' }]
}
expect(runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(first, 'worktree')).toBe('repo-one')
expect(runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(first, 'folder:folder')).toBe(
'folder-one'
)
expect(runtimeEnvironmentIdOrLocalUnlessTwin(first, 'worktree')).toBe('repo-one')
expect(runtimeEnvironmentIdOrLocalUnlessTwin(first, 'folder:folder')).toBe('folder-one')
const second: WorktreeRuntimeOwnerState = {
repos: [{ id: 'repo', connectionId: null, executionHostId: 'runtime:repo-two' }],
@@ -547,12 +520,8 @@ describe('runtime owner identity indexes', () => {
folderWorkspaces: [{ id: 'folder', projectGroupId: 'group' }],
projectGroups: [{ id: 'group', connectionId: null, executionHostId: 'runtime:folder-two' }]
}
expect(runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(second, 'worktree')).toBe(
'worktree-two'
)
expect(runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(second, 'folder:folder')).toBe(
'folder-two'
)
expect(runtimeEnvironmentIdOrLocalUnlessTwin(second, 'worktree')).toBe('worktree-two')
expect(runtimeEnvironmentIdOrLocalUnlessTwin(second, 'folder:folder')).toBe('folder-two')
})
})
@@ -654,9 +623,9 @@ describe('active workspace host selection', () => {
}
it('keeps the HUB transport for the active paired SSH worktree', () => {
expect(
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(pairedHubState, PAIRED_HUB_WORKTREE_ID)
).toBe('hub-a')
expect(runtimeEnvironmentIdOrLocalUnlessTwin(pairedHubState, PAIRED_HUB_WORKTREE_ID)).toBe(
'hub-a'
)
expect(ownerEnvironmentId(pairedHubState, PAIRED_HUB_WORKTREE_ID)).toBe('hub-a')
})
@@ -123,10 +123,10 @@ export function getExecutionHostIdForWorktree(
}
/**
* {@link resolveOwner}, reading an id no row places as this app's — or, for a folder, the server
* its saved session names, or a web client's only server.
* {@link resolveOwner}, except an id no row places and no caller names a host resolves to this app
* instead of `missing` — or, for a folder, to its saved session's server or a web client's only one.
*/
export function resolveOwnerWithLegacyFallback(
export function resolveOwnerOrLocal(
state: WorktreeRuntimeOwnerState,
ref: WorkspaceOwnerRef
): ExecutionHostOwnerMatch<HostAuthority> {
@@ -148,17 +148,18 @@ export function resolveOwnerWithLegacyFallback(
}
/**
* The paired server behind {@link resolveOwnerWithLegacyFallback}, or `null` for this app, a
* directly dialed SSH target or a twin: for readers whose twin answer is "no server", as before.
* The paired server {@link resolveOwnerOrLocal} names, or `null` for this app, a directly dialed
* SSH target or a twin. Unlike `runtimeEnvironmentIdUnlessTwin`, an unplaced folder on a web
* client names that client's server.
*/
export function runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(
export function runtimeEnvironmentIdOrLocalUnlessTwin(
state: WorktreeRuntimeOwnerState,
workspaceId: string | null | undefined
): string | null {
if (!workspaceId) {
return null
}
const match = resolveOwnerWithLegacyFallback(state, { workspaceId })
const match = resolveOwnerOrLocal(state, { workspaceId })
return match.kind === 'resolved' && match.owner.endpoint.kind === 'environment'
? match.owner.endpoint.environmentId
: null
@@ -1,6 +1,6 @@
import { LOCAL_EXECUTION_HOST_ID, toRuntimeExecutionHostId } from '../../../shared/execution-host'
import { isUnprovenServerSshRoute } from './resolve-owner'
import { resolveOwnerWithLegacyFallback } from './worktree-runtime-owner'
import { resolveOwnerOrLocal } from './worktree-runtime-owner'
import type { WorktreeRuntimeOwnerState } from './worktree-runtime-owner-state'
export type WorktreeSortOrderHostGroup = {
@@ -25,7 +25,7 @@ export function splitWorktreeSortOrderByHost(
): WorktreeSortOrderHostGroup[] {
const groups = new Map<string, { orderedIds: string[]; rowHostIds: string[] }>()
for (const id of orderedIds) {
const owner = resolveOwnerWithLegacyFallback(state, { workspaceId: id })
const owner = resolveOwnerOrLocal(state, { workspaceId: id })
// Why: a twin names no host to write to, and an unproven server route may be another machine.
if (owner.kind !== 'resolved' || isUnprovenServerSshRoute(state, id, owner.owner)) {
continue
@@ -31,7 +31,7 @@ const mocks = vi.hoisted(() => ({
trackTerminalPaneSplit: vi.fn(),
deliverLaunchPromptToAgentTab: vi.fn(),
seedNativeChatLaunchDraftForAgentTab: vi.fn(),
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin: vi.fn(),
runtimeEnvironmentIdOrLocalUnlessTwin: vi.fn(),
hasMaterializedWebRuntimeBrowserPage: vi.fn(),
recoverWebSessionTerminalOrphansBeforeApply: vi.fn(
async (_state: unknown, snapshot: unknown) => snapshot
@@ -76,8 +76,7 @@ vi.mock('@/lib/feature-education-telemetry', () => ({
}))
vi.mock('@/lib/worktree-runtime-owner', () => ({
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin:
mocks.runtimeEnvironmentIdWithLegacyFallbackUnlessTwin
runtimeEnvironmentIdOrLocalUnlessTwin: mocks.runtimeEnvironmentIdOrLocalUnlessTwin
}))
vi.mock('@/lib/agent-launch-prompt-delivery', () => ({
@@ -8,7 +8,7 @@ import type { BrowserPage } from '../../../../../shared/browser-workspace-types'
import { planBrowserPageConversion } from '../browser-page-conversion'
import { findPage, findWorkspace, mirrorWorkspaceFromActivePage } from '../browser-page-records'
import { assertManagedBrowserMaterializationAllowed } from '@/lib/client-creation-action-policy'
import { runtimeEnvironmentIdWithLegacyFallbackUnlessTwin } from '@/lib/worktree-runtime-owner'
import { runtimeEnvironmentIdOrLocalUnlessTwin } from '@/lib/worktree-runtime-owner'
import { closeRemoteBrowserPageInOwningEnvironment } from './browser-remote-close'
import { releaseDocPreviewGrant } from '@/lib/doc-preview-grants'
import { isLocalBrowserPageOwner } from './browser-host-state'
@@ -37,10 +37,7 @@ export function createBrowserPageConversionActions(
declaredOwnership !== undefined
? declaredOwnership
: oldPageForOwnership
? runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(
get(),
oldPageForOwnership.worktreeId
)
? runtimeEnvironmentIdOrLocalUnlessTwin(get(), oldPageForOwnership.worktreeId)
: null
)
let converted: BrowserPage | null = null
@@ -16,7 +16,7 @@ import {
findWorkspace
} from '../browser-page-records'
import { getBrowserSessionProfileHostId } from './browser-host-state'
import { runtimeEnvironmentIdWithLegacyFallbackUnlessTwin } from '@/lib/worktree-runtime-owner'
import { runtimeEnvironmentIdOrLocalUnlessTwin } from '@/lib/worktree-runtime-owner'
import { admitBrowserPageMount } from '@/components/browser-pane/host-guest/browser-page-mount-admission'
import { ownsGlobalSelection } from '../../global-selection-owner'
import {
@@ -197,10 +197,7 @@ export function createBrowserTabActions(
throw new Error(browserAvailability.reason)
}
const defaultUrl = state.browserDefaultUrl ?? 'about:blank'
const runtimeEnvironmentId = runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(
state,
worktreeId
)
const runtimeEnvironmentId = runtimeEnvironmentIdOrLocalUnlessTwin(state, worktreeId)
if (browserAvailability.provider === 'paired-runtime') {
if (!runtimeEnvironmentId) {
throw new Error('The paired runtime browser provider is unavailable.')
@@ -253,10 +250,7 @@ export function createBrowserTabActions(
if (browserAvailability.state !== 'enabled') {
return false
}
const runtimeEnvironmentId = runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(
state,
worktreeId
)
const runtimeEnvironmentId = runtimeEnvironmentIdOrLocalUnlessTwin(state, worktreeId)
if (browserAvailability.provider === 'paired-runtime') {
if (!runtimeEnvironmentId) {
return false
@@ -1,7 +1,7 @@
import type { AppState } from '../../types'
import type { TerminalTab } from '../../../../../shared/terminal-tab-types'
import { findTabAndWorktree } from '../tab-group-state'
import { runtimeEnvironmentIdWithLegacyFallbackUnlessTwin } from '@/lib/worktree-runtime-owner'
import { runtimeEnvironmentIdOrLocalUnlessTwin } from '@/lib/worktree-runtime-owner'
import { locateTerminalTab } from '../../terminals/terminal-tab-location'
/**
@@ -33,7 +33,7 @@ export function mirrorTabPinnedToHost(state: AppState, tabId: string, isPinned:
if (
!found ||
found.tab.contentType !== 'terminal' ||
!runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(state, found.worktreeId)
!runtimeEnvironmentIdOrLocalUnlessTwin(state, found.worktreeId)
) {
return
}
@@ -55,7 +55,7 @@ export function mirrorTabViewModeToHost(
if (
!found ||
found.tab.contentType !== 'terminal' ||
!runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(state, found.worktreeId)
!runtimeEnvironmentIdOrLocalUnlessTwin(state, found.worktreeId)
) {
return
}
@@ -1,7 +1,7 @@
import { FLOATING_TERMINAL_WORKTREE_ID } from '../../../../shared/constants'
import { parseWorkspaceKey } from '../../../../shared/workspace-scope'
import { focusTerminalTabSurface } from '@/lib/focus-terminal-tab-surface'
import { runtimeEnvironmentIdWithLegacyFallbackUnlessTwin } from '@/lib/worktree-runtime-owner'
import { runtimeEnvironmentIdOrLocalUnlessTwin } from '@/lib/worktree-runtime-owner'
import { resolveWorktreeOperationRouteResult } from '@/lib/worktree-operation-route'
import { isWebClientLocation } from '@/lib/web-client-location'
import type { TerminalSlice, TerminalStoreGet, TerminalStoreSet } from './terminal-state'
@@ -27,7 +27,7 @@ export function createActiveWorkspaceTerminalActions(
}
const runtimeEnvironmentId = worktreeRoute
? worktreeRoute.route.runtimeEnvironmentId
: runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(state, worktreeId)
: runtimeEnvironmentIdOrLocalUnlessTwin(state, worktreeId)
if (runtimeEnvironmentId) {
const { createWebRuntimeSessionTerminal } =
await import('@/runtime/web-runtime-terminal-creation')