diff --git a/src/renderer/src/components/activity/activity-answered-ask-timeline.test.tsx b/src/renderer/src/components/activity/activity-answered-ask-timeline.test.tsx index 49e53f64f7e..0f6a44629fc 100644 --- a/src/renderer/src/components/activity/activity-answered-ask-timeline.test.tsx +++ b/src/renderer/src/components/activity/activity-answered-ask-timeline.test.tsx @@ -40,8 +40,6 @@ vi.mock('@/store', async () => { }) vi.mock('@/lib/worktree-runtime-owner', () => ({ - getRuntimeEnvironmentIdForWorktree: (state: { testRuntimeOwner?: string | null }) => - state.testRuntimeOwner ?? null, getExecutionHostIdForWorktree: (state: { testRuntimeOwner?: string | null }) => state.testRuntimeOwner ? `runtime:${state.testRuntimeOwner}` : 'local' })) diff --git a/src/renderer/src/components/browser-pane/assemble-chrome/browser-deferred-lifecycle.test.tsx b/src/renderer/src/components/browser-pane/assemble-chrome/browser-deferred-lifecycle.test.tsx index 0d1e0b08db1..ffaddf3cff8 100644 --- a/src/renderer/src/components/browser-pane/assemble-chrome/browser-deferred-lifecycle.test.tsx +++ b/src/renderer/src/components/browser-pane/assemble-chrome/browser-deferred-lifecycle.test.tsx @@ -35,7 +35,6 @@ vi.mock('@/store', async () => { } }) vi.mock('@/lib/worktree-runtime-owner', () => ({ - getRuntimeEnvironmentIdForWorktree: () => null, runtimeEnvironmentIdWithLegacyFallbackUnlessTwin: () => null, getExecutionHostIdForWorktree: () => mocks.executionHostId })) diff --git a/src/renderer/src/components/browser-pane/workspace-doc/doc-preview-document-actions.test.ts b/src/renderer/src/components/browser-pane/workspace-doc/doc-preview-document-actions.test.ts index b5f862d3a09..498d8fbb28e 100644 --- a/src/renderer/src/components/browser-pane/workspace-doc/doc-preview-document-actions.test.ts +++ b/src/renderer/src/components/browser-pane/workspace-doc/doc-preview-document-actions.test.ts @@ -63,26 +63,14 @@ vi.mock('@/store', () => ({ }, getKnownWorktreeById: () => mocks.worktreePath === null ? undefined : { id: 'wt-1', path: mocks.worktreePath }, - ...(mocks.twin - ? { - repos: [ - { id: 'repo', executionHostId: 'local' }, - { id: 'repo', connectionId: 'box', executionHostId: 'ssh:box' } - ], - worktreesByRepo: { - repo: [ - { id: 'repo::/srv/repo', repoId: 'repo', hostId: 'local' }, - { id: 'repo::/srv/repo', repoId: 'repo', hostId: 'ssh:box' } - ] - } - } - : {}), + ...(mocks.twin ? localAndSshTwinRows('repo', 'repo::/srv/repo') : {}), openFile: mocks.openFile }) } })) import { openDocPreviewExternally, openDocPreviewSource } from './doc-preview-document-actions' +import { localAndSshTwinRows } from '@/lib/workspace-twin-rows.test-fixture' const REMOTE_DOCUMENT = { filePath: '/root/demo/report/index.html', diff --git a/src/renderer/src/components/browser-pane/workspace-doc/doc-preview-document-actions.ts b/src/renderer/src/components/browser-pane/workspace-doc/doc-preview-document-actions.ts index 2828ae45d30..7fd8309f01e 100644 --- a/src/renderer/src/components/browser-pane/workspace-doc/doc-preview-document-actions.ts +++ b/src/renderer/src/components/browser-pane/workspace-doc/doc-preview-document-actions.ts @@ -3,7 +3,7 @@ import { toast } from 'sonner' import { translate } from '@/i18n/i18n' import { getConnectionIdForFileFromState } from '@/lib/connection-owner-resolution' import { runtimeEnvironmentIdWithLegacyFallbackUnlessTwin } from '@/lib/worktree-runtime-owner' -import { resolveOwner } from '@/lib/resolve-owner' +import { isTwinWorkspace } from '@/lib/resolve-owner' import { detectLanguage } from '@/lib/language-detect' import { buildWorkspaceFileContextForFile, @@ -81,7 +81,7 @@ export function openDocPreviewExternally(document: DocPreviewDocument): void { runtimeEnvironmentId === null && worktreeRoot !== null && // Why: a twin reads as "no server", which is not proof this machine holds the file. - resolveOwner(state, { workspaceId: document.worktreeId }).kind !== 'ambiguous' + !isTwinWorkspace(state, document.worktreeId) if (ownedByThisMachine && canClientOsOpenWorkspaceFile(fileContext)) { void window.api.shell.openFilePath(document.filePath, LOCAL_EXECUTION_HOST_ID) return diff --git a/src/renderer/src/components/native-chat/StructuredAgentSessionStatusBridge.test.tsx b/src/renderer/src/components/native-chat/StructuredAgentSessionStatusBridge.test.tsx index 97d257ad0b5..06113cae6b1 100644 --- a/src/renderer/src/components/native-chat/StructuredAgentSessionStatusBridge.test.tsx +++ b/src/renderer/src/components/native-chat/StructuredAgentSessionStatusBridge.test.tsx @@ -46,8 +46,6 @@ vi.mock('@/store', async () => { }) vi.mock('@/lib/worktree-runtime-owner', () => ({ - getRuntimeEnvironmentIdForWorktree: (state: { testRuntimeOwner?: string | null }) => - state.testRuntimeOwner ?? null, getExecutionHostIdForWorktree: (state: { testRuntimeOwner?: string | null }) => state.testRuntimeOwner ? `runtime:${state.testRuntimeOwner}` : 'local' })) diff --git a/src/renderer/src/components/native-chat/native-chat-runtime-owner.ts b/src/renderer/src/components/native-chat/native-chat-runtime-owner.ts index 3dffabd0234..dd5b6558119 100644 --- a/src/renderer/src/components/native-chat/native-chat-runtime-owner.ts +++ b/src/renderer/src/components/native-chat/native-chat-runtime-owner.ts @@ -26,14 +26,18 @@ export function selectNativeChatRuntimeEnvironmentId( state: NativeChatRuntimeOwnerState, terminalTabId: string ): string | null { - const worktreeId = findTerminalTabWorktreeId(state.tabsByWorktree, terminalTabId) - return worktreeId ? runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(state, worktreeId) : null + return runtimeEnvironmentIdWithLegacyFallbackUnlessTwin( + state, + findTerminalTabWorktreeId(state.tabsByWorktree, terminalTabId) + ) } export function createNativeChatRuntimeSelector(terminalTabId: string) { const selectOwner = createNativeChatTabOwnerSelector(terminalTabId) return (state: NativeChatRuntimeOwnerState): string | null => { - const worktreeId = selectOwner({ tabsByWorktree: state.tabsByWorktree }) - return worktreeId ? runtimeEnvironmentIdWithLegacyFallbackUnlessTwin(state, worktreeId) : null + return runtimeEnvironmentIdWithLegacyFallbackUnlessTwin( + state, + selectOwner({ tabsByWorktree: state.tabsByWorktree }) + ) } } diff --git a/src/renderer/src/components/native-chat/structured-agent-session-status-bridge-clock.test.tsx b/src/renderer/src/components/native-chat/structured-agent-session-status-bridge-clock.test.tsx index a52fd0897c7..e47ebea74ee 100644 --- a/src/renderer/src/components/native-chat/structured-agent-session-status-bridge-clock.test.tsx +++ b/src/renderer/src/components/native-chat/structured-agent-session-status-bridge-clock.test.tsx @@ -38,8 +38,6 @@ vi.mock('@/store', async () => { }) vi.mock('@/lib/worktree-runtime-owner', () => ({ - getRuntimeEnvironmentIdForWorktree: (state: { testRuntimeOwner?: string | null }) => - state.testRuntimeOwner ?? null, getExecutionHostIdForWorktree: (state: { testRuntimeOwner?: string | null }) => state.testRuntimeOwner ? `runtime:${state.testRuntimeOwner}` : 'local' })) diff --git a/src/renderer/src/components/native-chat/structured-agent-session-status-bridge-failed-start.test.tsx b/src/renderer/src/components/native-chat/structured-agent-session-status-bridge-failed-start.test.tsx index 1f25263f53a..14fda1ba9d2 100644 --- a/src/renderer/src/components/native-chat/structured-agent-session-status-bridge-failed-start.test.tsx +++ b/src/renderer/src/components/native-chat/structured-agent-session-status-bridge-failed-start.test.tsx @@ -52,7 +52,6 @@ vi.mock('@/store', async () => { vi.mock('sonner', () => ({ toast: { error: vi.fn(), message: vi.fn() } })) vi.mock('@/lib/worktree-runtime-owner', () => ({ - getRuntimeEnvironmentIdForWorktree: () => null, getExecutionHostIdForWorktree: () => 'local' })) diff --git a/src/renderer/src/components/native-chat/structured-agent-session-status-bridge-stopping.test.tsx b/src/renderer/src/components/native-chat/structured-agent-session-status-bridge-stopping.test.tsx index fc1fc8430c3..36ddd24817c 100644 --- a/src/renderer/src/components/native-chat/structured-agent-session-status-bridge-stopping.test.tsx +++ b/src/renderer/src/components/native-chat/structured-agent-session-status-bridge-stopping.test.tsx @@ -35,8 +35,6 @@ vi.mock('@/store', async () => { }) vi.mock('@/lib/worktree-runtime-owner', () => ({ - getRuntimeEnvironmentIdForWorktree: (state: { testRuntimeOwner?: string | null }) => - state.testRuntimeOwner ?? null, getExecutionHostIdForWorktree: (state: { testRuntimeOwner?: string | null }) => state.testRuntimeOwner ? `runtime:${state.testRuntimeOwner}` : 'local' })) diff --git a/src/renderer/src/components/native-chat/structured-agent-session-status-bridge-unconfirmed-start.test.tsx b/src/renderer/src/components/native-chat/structured-agent-session-status-bridge-unconfirmed-start.test.tsx index ef0bc1215cf..784039a0e45 100644 --- a/src/renderer/src/components/native-chat/structured-agent-session-status-bridge-unconfirmed-start.test.tsx +++ b/src/renderer/src/components/native-chat/structured-agent-session-status-bridge-unconfirmed-start.test.tsx @@ -53,7 +53,6 @@ vi.mock('@/store', async () => { vi.mock('sonner', () => ({ toast: { error: vi.fn(), message: vi.fn() } })) vi.mock('@/lib/worktree-runtime-owner', () => ({ - getRuntimeEnvironmentIdForWorktree: () => null, getExecutionHostIdForWorktree: () => 'local' })) diff --git a/src/renderer/src/components/native-chat/structured-child-records-switch.test.tsx b/src/renderer/src/components/native-chat/structured-child-records-switch.test.tsx index ad5fb208099..7f05ebd304a 100644 --- a/src/renderer/src/components/native-chat/structured-child-records-switch.test.tsx +++ b/src/renderer/src/components/native-chat/structured-child-records-switch.test.tsx @@ -23,7 +23,6 @@ import type { TerminalTab } from '../../../../shared/terminal-tab-types' import { isExplicitAgentStatusFresh } from '@/lib/pane-agent-evidence' import type { AppState } from '@/store/types' import type * as RuntimeRpcClientModule from '@/runtime/runtime-rpc-client' -import type * as WorktreeRuntimeOwnerModule from '@/lib/worktree-runtime-owner' type TestStore = { getState: () => AppState @@ -52,12 +51,6 @@ vi.mock('@/store', async () => { return { useAppStore } }) -// Partial: the status projection also resolves each chat's owner from the worktree. -vi.mock('@/lib/worktree-runtime-owner', async (importOriginal) => ({ - ...(await importOriginal()), - getRuntimeEnvironmentIdForWorktree: () => null -})) - vi.mock('@/runtime/runtime-rpc-client', async (importOriginal) => ({ ...(await importOriginal()), runtimeEnvironmentSupportsCapability: vi.fn(async () => true) diff --git a/src/renderer/src/components/right-sidebar/file-explorer-operation-owner.ts b/src/renderer/src/components/right-sidebar/file-explorer-operation-owner.ts index 8a3b284e3d0..570cb831ad2 100644 --- a/src/renderer/src/components/right-sidebar/file-explorer-operation-owner.ts +++ b/src/renderer/src/components/right-sidebar/file-explorer-operation-owner.ts @@ -80,16 +80,7 @@ export function getFileExplorerOperationOwnerFromState( if (explicitOwner?.kind === 'ambiguous') { return { kind: 'unresolved' } } - const connectionId = getConnectionIdFromState(state, worktreeId ?? null) - const explicitRuntimeEnvironmentId = explicitOwner?.environmentId ?? null - // Why: global runtime focus is not ownership evidence while SSH/local - // metadata is unresolved; destructive actions must wait for explicit provenance. - if (connectionId === undefined && explicitRuntimeEnvironmentId === null) { - return { kind: 'unresolved' } - } - // Why: inferred SSH ownership outranks a projected runtime owner, but an explicit - // workspace runtime still owns its files. - const runtimeEnvironmentId = explicitRuntimeEnvironmentId?.trim() + const runtimeEnvironmentId = explicitOwner?.environmentId?.trim() if (runtimeEnvironmentId) { return { kind: 'runtime', @@ -97,6 +88,9 @@ export function getFileExplorerOperationOwnerFromState( executionHostId: toRuntimeExecutionHostId(runtimeEnvironmentId) } } + const connectionId = getConnectionIdFromState(state, worktreeId ?? null) + // Why: global runtime focus is not ownership evidence while SSH/local + // metadata is unresolved; destructive actions must wait for explicit provenance. if (connectionId === undefined) { return { kind: 'unresolved' } } diff --git a/src/renderer/src/components/right-sidebar/useFileSearchRunner.test.tsx b/src/renderer/src/components/right-sidebar/useFileSearchRunner.test.tsx index 2f46e710715..227b209c870 100644 --- a/src/renderer/src/components/right-sidebar/useFileSearchRunner.test.tsx +++ b/src/renderer/src/components/right-sidebar/useFileSearchRunner.test.tsx @@ -4,6 +4,7 @@ import { act, renderHook } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import type { SearchResult } from '../../../../shared/code-search-types' import { useFileSearchRunner } from './useFileSearchRunner' +import { localAndSshTwinRows } from '@/lib/workspace-twin-rows.test-fixture' const mocks = vi.hoisted(() => ({ getConnectionId: vi.fn(), @@ -105,16 +106,7 @@ describe('useFileSearchRunner result ownership', () => { const worktreeId = 'twin-repo::/repo' const state = { settings: { activeRuntimeEnvironmentId: 'ambient-runtime-b' }, - repos: [ - { id: 'twin-repo', executionHostId: 'local' }, - { id: 'twin-repo', connectionId: 'box', executionHostId: 'ssh:box' } - ], - worktreesByRepo: { - 'twin-repo': [ - { id: worktreeId, repoId: 'twin-repo', hostId: 'local' }, - { id: worktreeId, repoId: 'twin-repo', hostId: 'ssh:box' } - ] - }, + ...localAndSshTwinRows('twin-repo', worktreeId), fileSearchStateByWorktree: { [worktreeId]: {} } } const { hook, updates } = renderSearchRunner(state, worktreeId) diff --git a/src/renderer/src/components/right-sidebar/useFileSearchRunner.ts b/src/renderer/src/components/right-sidebar/useFileSearchRunner.ts index 964ab14dd97..1002ac2e93b 100644 --- a/src/renderer/src/components/right-sidebar/useFileSearchRunner.ts +++ b/src/renderer/src/components/right-sidebar/useFileSearchRunner.ts @@ -16,7 +16,7 @@ import { getExecutionHostIdForWorktree, runtimeEnvironmentIdWithLegacyFallbackUnlessTwin } from '@/lib/worktree-runtime-owner' -import { resolveOwner } from '@/lib/resolve-owner' +import { isTwinWorkspace } from '@/lib/resolve-owner' import type { SearchResult } from '../../../../shared/code-search-types' import { runtimeTargetForOwnerEnvironment } from '@/runtime/runtime-client-target' @@ -51,11 +51,7 @@ export function useFileSearchRunner({ getExecutionHostIdForWorktree(state, activeWorktreeId) ) // Why: a workspace two hosts publish names no owner, so either host's results would mislead. - const ownerIsTwin = useAppStore( - (state) => - !!activeWorktreeId && - resolveOwner(state, { workspaceId: activeWorktreeId }).kind === 'ambiguous' - ) + const ownerIsTwin = useAppStore((state) => isTwinWorkspace(state, activeWorktreeId)) const searchTimerRef = useRef | null>(null) // Why: runtime searches can finish out of order; ids keep stale results // from overwriting the newest query state. diff --git a/src/renderer/src/components/tab-group/useTabGroupTabCloseCommands.structured-session.test.ts b/src/renderer/src/components/tab-group/useTabGroupTabCloseCommands.structured-session.test.ts index fd2af0f8f87..961fa7c2e45 100644 --- a/src/renderer/src/components/tab-group/useTabGroupTabCloseCommands.structured-session.test.ts +++ b/src/renderer/src/components/tab-group/useTabGroupTabCloseCommands.structured-session.test.ts @@ -63,8 +63,6 @@ vi.mock('../../runtime/web-runtime-session-environment', () => ({ isWebRuntimeSessionActive: vi.fn(() => false) })) -vi.mock('@/lib/worktree-runtime-owner', () => ({ getRuntimeEnvironmentIdForWorktree: () => null })) - vi.mock('@/runtime/remote-browser-tab-ownership', () => ({ browserWorkspaceHasRemoteOwner: () => false })) diff --git a/src/renderer/src/components/terminal-workspace-keydown.test.ts b/src/renderer/src/components/terminal-workspace-keydown.test.ts index fdebeabb2b7..1b5471b1dd9 100644 --- a/src/renderer/src/components/terminal-workspace-keydown.test.ts +++ b/src/renderer/src/components/terminal-workspace-keydown.test.ts @@ -62,7 +62,6 @@ vi.mock('@/runtime/runtime-rpc-client', () => ({ callRuntimeRpc: mocks.callRunti vi.mock('@/lib/structured-agent-session-launch', () => ({ cancelStructuredAgentLaunch: mocks.cancelStructuredAgentLaunch })) -vi.mock('@/lib/worktree-runtime-owner', () => ({ getRuntimeEnvironmentIdForWorktree: () => null })) vi.mock('@/runtime/runtime-worktree-selector', () => ({ toRuntimeWorktreeSelector: (id: string) => `id:${id}` })) diff --git a/src/renderer/src/lib/file-preview.test.ts b/src/renderer/src/lib/file-preview.test.ts index a40a1db8afa..4d85c691553 100644 --- a/src/renderer/src/lib/file-preview.test.ts +++ b/src/renderer/src/lib/file-preview.test.ts @@ -6,11 +6,8 @@ import { openFileInBrowserTab, openFilePreviewToSide } from './file-preview' -import { - getWorkspaceFileBrowserOpenTarget, - REMOTE_FILE_BROWSER_UNSUPPORTED_MESSAGE, - TWIN_FILE_BROWSER_UNSUPPORTED_MESSAGE -} from './workspace-file-browser-open-target' +import { TWIN_FILE_BROWSER_UNSUPPORTED_MESSAGE } from './workspace-file-browser-open-target' +import { localAndSshTwinRows } from './workspace-twin-rows.test-fixture' import { folderWorkspaceKey } from '../../../shared/workspace-scope' import { useAppStore } from '@/store' @@ -76,16 +73,12 @@ vi.mock('@/store', () => ({ getKnownWorktreeById: () => ({ id: 'wt-1', path: '/srv/repo' }), groupsByWorktree: {}, layoutByWorktree: mocks.layoutByWorktree, - repos: [ - { id: 'repo-1', connectionId: mocks.connectionId }, - ...(mocks.twin ? [{ id: 'repo-1', connectionId: 'box', executionHostId: 'ssh:box' }] : []) - ], - worktreesByRepo: { - 'repo-1': [ - { id: 'wt-1', repoId: 'repo-1', ...(mocks.twin ? { hostId: 'local' } : {}) }, - ...(mocks.twin ? [{ id: 'wt-1', repoId: 'repo-1', hostId: 'ssh:box' }] : []) - ] - } + ...(mocks.twin + ? localAndSshTwinRows('repo-1', 'wt-1') + : { + repos: [{ id: 'repo-1', connectionId: mocks.connectionId }], + worktreesByRepo: { 'repo-1': [{ id: 'wt-1', repoId: 'repo-1' }] } + }) }) } })) @@ -100,6 +93,7 @@ beforeEach(() => { mocks.browserPagesByWorkspace = {} mocks.unifiedTabsByWorktree = {} mocks.activeWorktreeId = 'wt-1' + mocks.twin = false }) /** @@ -484,47 +478,7 @@ describe('canShowWorkspaceFileBrowserAction', () => { }) }) -describe('getWorkspaceFileBrowserOpenTarget', () => { - it('returns a reusable browser navigation target for local files', () => { - expect( - getWorkspaceFileBrowserOpenTarget({ - filePath: 'C:\\repo\\demo page.html', - worktreeId: 'wt-1' - }) - ).toEqual({ - status: 'ready', - url: 'file:///C:/repo/demo%20page.html', - title: 'demo page.html' - }) - }) - - it('still refuses remote files, which have no local file URL', () => { - mocks.connectionId = 'ssh-1' - - expect( - getWorkspaceFileBrowserOpenTarget({ - filePath: '/home/alice/report.html', - worktreeId: 'wt-1' - }) - ).toEqual({ - status: 'unsupported', - reason: 'remote-worktree', - message: REMOTE_FILE_BROWSER_UNSUPPORTED_MESSAGE - }) - }) - - it('refuses a file:// URL for a project two hosts publish, naming why', () => { - mocks.twin = true - expect( - getWorkspaceFileBrowserOpenTarget({ filePath: '/srv/repo/report.html', worktreeId: 'wt-1' }) - ).toEqual({ - status: 'unsupported', - reason: 'remote-worktree', - message: TWIN_FILE_BROWSER_UNSUPPORTED_MESSAGE - }) - mocks.twin = false - }) - +describe('getWorkspaceFilePreviewPlan', () => { it('plans no local browser tab for a project two hosts publish', () => { mocks.twin = true expect( @@ -534,21 +488,6 @@ describe('getWorkspaceFileBrowserOpenTarget', () => { message: TWIN_FILE_BROWSER_UNSUPPORTED_MESSAGE, reason: 'no-channel' }) - mocks.twin = false - }) - - it('refuses paired files even when their connection ID is null', () => { - mocks.environmentId = 'managed-host' - expect( - getWorkspaceFileBrowserOpenTarget({ - filePath: '/srv/repo/report.html', - worktreeId: 'wt-1' - }) - ).toEqual({ - status: 'unsupported', - reason: 'remote-worktree', - message: REMOTE_FILE_BROWSER_UNSUPPORTED_MESSAGE - }) }) }) diff --git a/src/renderer/src/lib/file-preview.ts b/src/renderer/src/lib/file-preview.ts index 2bbf3be9970..14965e2cc50 100644 --- a/src/renderer/src/lib/file-preview.ts +++ b/src/renderer/src/lib/file-preview.ts @@ -8,7 +8,7 @@ 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 { resolveOwner } from '@/lib/resolve-owner' +import { isTwinWorkspace } from '@/lib/resolve-owner' import { REMOTE_FILE_BROWSER_UNSUPPORTED_MESSAGE, TWIN_FILE_BROWSER_UNSUPPORTED_MESSAGE @@ -56,7 +56,7 @@ export function getWorkspaceFilePreviewPlan( filePath: string ): WorkspaceFilePreviewPlan { // Why: a twin reads as "no server" below, which would open this machine's copy of the path. - if (resolveOwner(state, { workspaceId: worktreeId }).kind === 'ambiguous') { + if (isTwinWorkspace(state, worktreeId)) { return { status: 'unsupported', message: TWIN_FILE_BROWSER_UNSUPPORTED_MESSAGE, diff --git a/src/renderer/src/lib/launch-agent-structured-chat-guard.test.ts b/src/renderer/src/lib/launch-agent-structured-chat-guard.test.ts index 6b46b1e5cad..ee3898257dd 100644 --- a/src/renderer/src/lib/launch-agent-structured-chat-guard.test.ts +++ b/src/renderer/src/lib/launch-agent-structured-chat-guard.test.ts @@ -178,7 +178,6 @@ vi.mock('@/runtime/local-runtime-capabilities', () => ({ vi.mock('@/lib/worktree-runtime-owner', () => ({ getExecutionHostIdForWorktree: () => store.repos[0]?.connectionId ? `ssh:${store.repos[0].connectionId}` : 'local', - getRuntimeEnvironmentIdForWorktree: () => null, runtimeEnvironmentIdWithLegacyFallbackUnlessTwin: () => null })) vi.mock('@/runtime/structured-agent-session-client', () => ({ diff --git a/src/renderer/src/lib/resolve-owner.ts b/src/renderer/src/lib/resolve-owner.ts index 6e6894d9694..4fd7b3db0f5 100644 --- a/src/renderer/src/lib/resolve-owner.ts +++ b/src/renderer/src/lib/resolve-owner.ts @@ -178,6 +178,14 @@ function focusFreeRouteState(state: WorktreeOperationRouteState): { } } +/** Two hosts publish this workspace (or contest it), so no host-routed action may pick one. */ +export function isTwinWorkspace( + state: WorktreeOperationRouteState, + workspaceId: string | null | undefined +): boolean { + return !!workspaceId && resolveOwner(state, { workspaceId }).kind === 'ambiguous' +} + /** Transport to the workspace's owner, or `null` when the rows name none or disagree. */ export function runtimeTargetForWorkspaceOwner( state: WorktreeOperationRouteState, @@ -270,13 +278,27 @@ export function isUnprovenServerSshRoute( ) { return false } - const repoId = getRepoIdFromWorktreeId(workspaceId) - const namesServer = (row: { id: string; hostId?: string; runtimeOwnerEnvironmentId?: string }) => - row.id === workspaceId && - parseExecutionHostId(row.hostId)?.id === at && - row.runtimeOwnerEnvironmentId?.trim() === endpoint.environmentId - return !( - state.worktreesByRepo?.[repoId]?.some(namesServer) || - state.detectedWorktreesByRepo?.[repoId]?.worktrees.some(namesServer) + return !someWorktreeCopy( + state, + workspaceId, + (row) => + parseExecutionHostId(row.hostId)?.id === at && + row.runtimeOwnerEnvironmentId?.trim() === endpoint.environmentId + ) +} + +type WorktreeCopy = { hostId?: string; runtimeOwnerEnvironmentId?: string } + +/** Whether a listed or detected row with this worktree id satisfies `matches`. */ +export function someWorktreeCopy( + state: Pick, + worktreeId: string, + matches: (row: WorktreeCopy) => boolean +): boolean { + const repoId = getRepoIdFromWorktreeId(worktreeId) + const copyMatches = (row: WorktreeCopy & { id: string }) => row.id === worktreeId && matches(row) + return Boolean( + state.worktreesByRepo?.[repoId]?.some(copyMatches) || + state.detectedWorktreesByRepo?.[repoId]?.worktrees.some(copyMatches) ) } diff --git a/src/renderer/src/lib/startup-draft-input-kind.test.ts b/src/renderer/src/lib/startup-draft-input-kind.test.ts index 0f4dc8ffcba..0f9127459ad 100644 --- a/src/renderer/src/lib/startup-draft-input-kind.test.ts +++ b/src/renderer/src/lib/startup-draft-input-kind.test.ts @@ -22,8 +22,6 @@ vi.mock('@/store', () => ({ } })) -vi.mock('./worktree-runtime-owner', () => ({ getRuntimeEnvironmentIdForWorktree: () => null })) - vi.mock('./agent-draft-readiness', () => ({ waitForAgentDraftInputReady: async () => true })) diff --git a/src/renderer/src/lib/workspace-file-browser-open-target.test.ts b/src/renderer/src/lib/workspace-file-browser-open-target.test.ts new file mode 100644 index 00000000000..254e4a1a391 --- /dev/null +++ b/src/renderer/src/lib/workspace-file-browser-open-target.test.ts @@ -0,0 +1,90 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { + getWorkspaceFileBrowserOpenTarget, + REMOTE_FILE_BROWSER_UNSUPPORTED_MESSAGE, + TWIN_FILE_BROWSER_UNSUPPORTED_MESSAGE +} from './workspace-file-browser-open-target' +import { localAndSshTwinRows } from './workspace-twin-rows.test-fixture' + +const mocks = vi.hoisted(() => ({ + environmentId: null as string | null, + connectionId: null as string | null, + twin: false +})) + +vi.mock('@/lib/worktree-runtime-owner', () => ({ + runtimeEnvironmentIdWithLegacyFallbackUnlessTwin: () => mocks.environmentId +})) + +vi.mock('@/store', () => ({ + useAppStore: { + getState: () => + mocks.twin + ? localAndSshTwinRows('repo-1', 'wt-1') + : { + repos: [{ id: 'repo-1', connectionId: mocks.connectionId }], + worktreesByRepo: { 'repo-1': [{ id: 'wt-1', repoId: 'repo-1' }] } + } + } +})) + +beforeEach(() => { + mocks.environmentId = null + mocks.connectionId = null + mocks.twin = false +}) + +describe('getWorkspaceFileBrowserOpenTarget', () => { + it('returns a reusable browser navigation target for local files', () => { + expect( + getWorkspaceFileBrowserOpenTarget({ + filePath: 'C:\\repo\\demo page.html', + worktreeId: 'wt-1' + }) + ).toEqual({ + status: 'ready', + url: 'file:///C:/repo/demo%20page.html', + title: 'demo page.html' + }) + }) + + it('still refuses remote files, which have no local file URL', () => { + mocks.connectionId = 'ssh-1' + + expect( + getWorkspaceFileBrowserOpenTarget({ + filePath: '/home/alice/report.html', + worktreeId: 'wt-1' + }) + ).toEqual({ + status: 'unsupported', + reason: 'remote-worktree', + message: REMOTE_FILE_BROWSER_UNSUPPORTED_MESSAGE + }) + }) + + it('refuses a file:// URL for a project two hosts publish, naming why', () => { + mocks.twin = true + expect( + getWorkspaceFileBrowserOpenTarget({ filePath: '/srv/repo/report.html', worktreeId: 'wt-1' }) + ).toEqual({ + status: 'unsupported', + reason: 'remote-worktree', + message: TWIN_FILE_BROWSER_UNSUPPORTED_MESSAGE + }) + }) + + it('refuses paired files even when their connection ID is null', () => { + mocks.environmentId = 'managed-host' + expect( + getWorkspaceFileBrowserOpenTarget({ + filePath: '/srv/repo/report.html', + worktreeId: 'wt-1' + }) + ).toEqual({ + status: 'unsupported', + reason: 'remote-worktree', + message: REMOTE_FILE_BROWSER_UNSUPPORTED_MESSAGE + }) + }) +}) diff --git a/src/renderer/src/lib/workspace-file-browser-open-target.ts b/src/renderer/src/lib/workspace-file-browser-open-target.ts index e5271801f09..5cf848dd31d 100644 --- a/src/renderer/src/lib/workspace-file-browser-open-target.ts +++ b/src/renderer/src/lib/workspace-file-browser-open-target.ts @@ -1,7 +1,7 @@ import { absolutePathToFileUri } from '@/components/editor/markdown-internal-links' import { getConnectionIdForFile } from '@/lib/connection-context' import { basename } from '@/lib/path' -import { resolveOwner } from '@/lib/resolve-owner' +import { isTwinWorkspace } from '@/lib/resolve-owner' import { runtimeEnvironmentIdWithLegacyFallbackUnlessTwin } from '@/lib/worktree-runtime-owner' import { useAppStore } from '@/store' @@ -30,7 +30,7 @@ export function getWorkspaceFileBrowserOpenTarget(params: { }): WorkspaceFileBrowserOpenTarget { const state = useAppStore.getState() // Why: a twin reads as "no server", which must not let a file:// URL open this machine's copy. - if (resolveOwner(state, { workspaceId: params.worktreeId }).kind === 'ambiguous') { + if (isTwinWorkspace(state, params.worktreeId)) { return { status: 'unsupported', reason: 'remote-worktree', diff --git a/src/renderer/src/lib/workspace-twin-rows.test-fixture.ts b/src/renderer/src/lib/workspace-twin-rows.test-fixture.ts new file mode 100644 index 00000000000..dd2162a4b87 --- /dev/null +++ b/src/renderer/src/lib/workspace-twin-rows.test-fixture.ts @@ -0,0 +1,23 @@ +import type { ExecutionHostId } from '../../../shared/execution-host' + +type TwinRepoRow = { id: string; connectionId?: string; executionHostId: ExecutionHostId } +type TwinWorktreeRow = { id: string; repoId: string; hostId: ExecutionHostId } + +/** Repo and worktree rows for one project that this app and the SSH target `box` both publish. */ +export function localAndSshTwinRows( + repoId: string, + worktreeId: string +): { repos: TwinRepoRow[]; worktreesByRepo: Record } { + return { + repos: [ + { id: repoId, executionHostId: 'local' }, + { id: repoId, connectionId: 'box', executionHostId: 'ssh:box' } + ], + worktreesByRepo: { + [repoId]: [ + { id: worktreeId, repoId, hostId: 'local' }, + { id: worktreeId, repoId, hostId: 'ssh:box' } + ] + } + } +} diff --git a/src/renderer/src/lib/worktree-runtime-owner.ts b/src/renderer/src/lib/worktree-runtime-owner.ts index 989ae47c4e3..f7b6bba946e 100644 --- a/src/renderer/src/lib/worktree-runtime-owner.ts +++ b/src/renderer/src/lib/worktree-runtime-owner.ts @@ -22,7 +22,12 @@ import { getExecutionHostIdForFolderWorkspace, getRuntimeEnvironmentIdForFolderWorkspace } from './folder-workspace-runtime-owner' -import { namedHostForOwnerRef, resolveOwner, type WorkspaceOwnerRef } from './resolve-owner' +import { + namedHostForOwnerRef, + resolveOwner, + someWorktreeCopy, + type WorkspaceOwnerRef +} from './resolve-owner' import { SELF_LOCAL_AUTHORITY, type HostAuthority } from '../../../shared/host-authority' import type { ExecutionHostOwnerMatch } from '../../../shared/worktree-execution-host-resolution' import { @@ -249,16 +254,10 @@ function hasHostEvidence(state: WorktreeRuntimeOwnerState, workspaceId: string): ) } const repoId = getRepoIdFromWorktreeId(workspaceId) - const isStampedCopy = (row: { - id: string - hostId?: string - runtimeOwnerEnvironmentId?: string - }) => - row.id === workspaceId && Boolean(row.hostId?.trim() || row.runtimeOwnerEnvironmentId?.trim()) - return Boolean( - state.worktreesByRepo?.[repoId]?.some(isStampedCopy) || - state.detectedWorktreesByRepo?.[repoId]?.worktrees.some(isStampedCopy) || - state.repos?.some((repo) => repo.id === repoId && hasRowHost(repo)) + return ( + someWorktreeCopy(state, workspaceId, (row) => + Boolean(row.hostId?.trim() || row.runtimeOwnerEnvironmentId?.trim()) + ) || Boolean(state.repos?.some((repo) => repo.id === repoId && hasRowHost(repo))) ) } diff --git a/src/renderer/src/runtime/web-runtime-browser-capability-cleanup.test.ts b/src/renderer/src/runtime/web-runtime-browser-capability-cleanup.test.ts index 96e06d16ccd..04975ee4e9d 100644 --- a/src/renderer/src/runtime/web-runtime-browser-capability-cleanup.test.ts +++ b/src/renderer/src/runtime/web-runtime-browser-capability-cleanup.test.ts @@ -40,8 +40,6 @@ vi.mock('@/lib/feature-education-telemetry', () => ({ trackTerminalPaneSplit: vi.fn() })) -vi.mock('@/lib/worktree-runtime-owner', () => ({ getRuntimeEnvironmentIdForWorktree: vi.fn() })) - vi.mock('@/lib/agent-launch-prompt-delivery', () => ({ deliverLaunchPromptToAgentTab: vi.fn(), seedNativeChatLaunchDraftForAgentTab: vi.fn() diff --git a/src/renderer/src/runtime/web-runtime-session-browser-client-placement.test.ts b/src/renderer/src/runtime/web-runtime-session-browser-client-placement.test.ts index e93f664fa39..ac34590f857 100644 --- a/src/renderer/src/runtime/web-runtime-session-browser-client-placement.test.ts +++ b/src/renderer/src/runtime/web-runtime-session-browser-client-placement.test.ts @@ -36,7 +36,6 @@ const mocks = vi.hoisted(() => ({ trackTerminalPaneSplit: vi.fn(), deliverLaunchPromptToAgentTab: vi.fn(), seedNativeChatLaunchDraftForAgentTab: vi.fn(), - getRuntimeEnvironmentIdForWorktree: vi.fn(), hasMaterializedWebRuntimeBrowserPage: vi.fn() })) @@ -70,9 +69,6 @@ vi.mock('./web-session-tabs-sync/tracking-mappings', () => ({ vi.mock('@/lib/feature-education-telemetry', () => ({ trackTerminalPaneSplit: mocks.trackTerminalPaneSplit })) -vi.mock('@/lib/worktree-runtime-owner', () => ({ - getRuntimeEnvironmentIdForWorktree: mocks.getRuntimeEnvironmentIdForWorktree -})) vi.mock('@/lib/agent-launch-prompt-delivery', () => ({ deliverLaunchPromptToAgentTab: mocks.deliverLaunchPromptToAgentTab, seedNativeChatLaunchDraftForAgentTab: mocks.seedNativeChatLaunchDraftForAgentTab diff --git a/src/renderer/src/runtime/web-runtime-session-browser-create-failure.test.ts b/src/renderer/src/runtime/web-runtime-session-browser-create-failure.test.ts index 251aaf60f6d..4676587c579 100644 --- a/src/renderer/src/runtime/web-runtime-session-browser-create-failure.test.ts +++ b/src/renderer/src/runtime/web-runtime-session-browser-create-failure.test.ts @@ -29,7 +29,6 @@ const mocks = vi.hoisted(() => ({ trackTerminalPaneSplit: vi.fn(), deliverLaunchPromptToAgentTab: vi.fn(), seedNativeChatLaunchDraftForAgentTab: vi.fn(), - getRuntimeEnvironmentIdForWorktree: vi.fn(), hasMaterializedWebRuntimeBrowserPage: vi.fn() })) @@ -66,10 +65,6 @@ vi.mock('@/lib/feature-education-telemetry', () => ({ trackTerminalPaneSplit: mocks.trackTerminalPaneSplit })) -vi.mock('@/lib/worktree-runtime-owner', () => ({ - getRuntimeEnvironmentIdForWorktree: mocks.getRuntimeEnvironmentIdForWorktree -})) - vi.mock('@/lib/agent-launch-prompt-delivery', () => ({ deliverLaunchPromptToAgentTab: mocks.deliverLaunchPromptToAgentTab, seedNativeChatLaunchDraftForAgentTab: mocks.seedNativeChatLaunchDraftForAgentTab diff --git a/src/renderer/src/runtime/web-runtime-session-browser-create-focus.test.ts b/src/renderer/src/runtime/web-runtime-session-browser-create-focus.test.ts index 9f8b2eb05b1..07dc0c1c007 100644 --- a/src/renderer/src/runtime/web-runtime-session-browser-create-focus.test.ts +++ b/src/renderer/src/runtime/web-runtime-session-browser-create-focus.test.ts @@ -31,7 +31,6 @@ const mocks = vi.hoisted(() => ({ trackTerminalPaneSplit: vi.fn(), deliverLaunchPromptToAgentTab: vi.fn(), seedNativeChatLaunchDraftForAgentTab: vi.fn(), - getRuntimeEnvironmentIdForWorktree: vi.fn(), hasMaterializedWebRuntimeBrowserPage: vi.fn() })) @@ -68,10 +67,6 @@ vi.mock('@/lib/feature-education-telemetry', () => ({ trackTerminalPaneSplit: mocks.trackTerminalPaneSplit })) -vi.mock('@/lib/worktree-runtime-owner', () => ({ - getRuntimeEnvironmentIdForWorktree: mocks.getRuntimeEnvironmentIdForWorktree -})) - vi.mock('@/lib/agent-launch-prompt-delivery', () => ({ deliverLaunchPromptToAgentTab: mocks.deliverLaunchPromptToAgentTab, seedNativeChatLaunchDraftForAgentTab: mocks.seedNativeChatLaunchDraftForAgentTab diff --git a/src/renderer/src/runtime/web-runtime-session-browser-create-split-placement.test.ts b/src/renderer/src/runtime/web-runtime-session-browser-create-split-placement.test.ts index 56a741bc07f..61299f8ec31 100644 --- a/src/renderer/src/runtime/web-runtime-session-browser-create-split-placement.test.ts +++ b/src/renderer/src/runtime/web-runtime-session-browser-create-split-placement.test.ts @@ -32,7 +32,6 @@ const mocks = vi.hoisted(() => ({ trackTerminalPaneSplit: vi.fn(), deliverLaunchPromptToAgentTab: vi.fn(), seedNativeChatLaunchDraftForAgentTab: vi.fn(), - getRuntimeEnvironmentIdForWorktree: vi.fn(), hasMaterializedWebRuntimeBrowserPage: vi.fn() })) @@ -69,10 +68,6 @@ vi.mock('@/lib/feature-education-telemetry', () => ({ trackTerminalPaneSplit: mocks.trackTerminalPaneSplit })) -vi.mock('@/lib/worktree-runtime-owner', () => ({ - getRuntimeEnvironmentIdForWorktree: mocks.getRuntimeEnvironmentIdForWorktree -})) - vi.mock('@/lib/agent-launch-prompt-delivery', () => ({ deliverLaunchPromptToAgentTab: mocks.deliverLaunchPromptToAgentTab, seedNativeChatLaunchDraftForAgentTab: mocks.seedNativeChatLaunchDraftForAgentTab diff --git a/src/renderer/src/runtime/web-runtime-session-browser-create-staged-focus.test.ts b/src/renderer/src/runtime/web-runtime-session-browser-create-staged-focus.test.ts index 625a3a46826..ae0abdd658b 100644 --- a/src/renderer/src/runtime/web-runtime-session-browser-create-staged-focus.test.ts +++ b/src/renderer/src/runtime/web-runtime-session-browser-create-staged-focus.test.ts @@ -37,7 +37,6 @@ const mocks = vi.hoisted(() => ({ trackTerminalPaneSplit: vi.fn(), deliverLaunchPromptToAgentTab: vi.fn(), seedNativeChatLaunchDraftForAgentTab: vi.fn(), - getRuntimeEnvironmentIdForWorktree: vi.fn(), hasMaterializedWebRuntimeBrowserPage: vi.fn() })) @@ -71,9 +70,6 @@ vi.mock('./web-session-tabs-sync/tracking-mappings', () => ({ vi.mock('@/lib/feature-education-telemetry', () => ({ trackTerminalPaneSplit: mocks.trackTerminalPaneSplit })) -vi.mock('@/lib/worktree-runtime-owner', () => ({ - getRuntimeEnvironmentIdForWorktree: mocks.getRuntimeEnvironmentIdForWorktree -})) vi.mock('@/lib/agent-launch-prompt-delivery', () => ({ deliverLaunchPromptToAgentTab: mocks.deliverLaunchPromptToAgentTab, seedNativeChatLaunchDraftForAgentTab: mocks.seedNativeChatLaunchDraftForAgentTab diff --git a/src/renderer/src/runtime/web-runtime-session-browser-create-staged-hosting-intent.test.ts b/src/renderer/src/runtime/web-runtime-session-browser-create-staged-hosting-intent.test.ts index f5a55c6adde..ce20a93584e 100644 --- a/src/renderer/src/runtime/web-runtime-session-browser-create-staged-hosting-intent.test.ts +++ b/src/renderer/src/runtime/web-runtime-session-browser-create-staged-hosting-intent.test.ts @@ -37,7 +37,6 @@ const mocks = vi.hoisted(() => ({ trackTerminalPaneSplit: vi.fn(), deliverLaunchPromptToAgentTab: vi.fn(), seedNativeChatLaunchDraftForAgentTab: vi.fn(), - getRuntimeEnvironmentIdForWorktree: vi.fn(), hasMaterializedWebRuntimeBrowserPage: vi.fn() })) @@ -71,9 +70,6 @@ vi.mock('./web-session-tabs-sync/tracking-mappings', () => ({ vi.mock('@/lib/feature-education-telemetry', () => ({ trackTerminalPaneSplit: mocks.trackTerminalPaneSplit })) -vi.mock('@/lib/worktree-runtime-owner', () => ({ - getRuntimeEnvironmentIdForWorktree: mocks.getRuntimeEnvironmentIdForWorktree -})) vi.mock('@/lib/agent-launch-prompt-delivery', () => ({ deliverLaunchPromptToAgentTab: mocks.deliverLaunchPromptToAgentTab, seedNativeChatLaunchDraftForAgentTab: mocks.seedNativeChatLaunchDraftForAgentTab diff --git a/src/renderer/src/runtime/web-runtime-session-browser-create-staging.test.ts b/src/renderer/src/runtime/web-runtime-session-browser-create-staging.test.ts index d519d51ab78..88a8b6ab678 100644 --- a/src/renderer/src/runtime/web-runtime-session-browser-create-staging.test.ts +++ b/src/renderer/src/runtime/web-runtime-session-browser-create-staging.test.ts @@ -32,7 +32,6 @@ const mocks = vi.hoisted(() => ({ trackTerminalPaneSplit: vi.fn(), deliverLaunchPromptToAgentTab: vi.fn(), seedNativeChatLaunchDraftForAgentTab: vi.fn(), - getRuntimeEnvironmentIdForWorktree: vi.fn(), hasMaterializedWebRuntimeBrowserPage: vi.fn() })) @@ -69,10 +68,6 @@ vi.mock('@/lib/feature-education-telemetry', () => ({ trackTerminalPaneSplit: mocks.trackTerminalPaneSplit })) -vi.mock('@/lib/worktree-runtime-owner', () => ({ - getRuntimeEnvironmentIdForWorktree: mocks.getRuntimeEnvironmentIdForWorktree -})) - vi.mock('@/lib/agent-launch-prompt-delivery', () => ({ deliverLaunchPromptToAgentTab: mocks.deliverLaunchPromptToAgentTab, seedNativeChatLaunchDraftForAgentTab: mocks.seedNativeChatLaunchDraftForAgentTab diff --git a/src/renderer/src/runtime/web-runtime-session-browser-placement-staleness.test.ts b/src/renderer/src/runtime/web-runtime-session-browser-placement-staleness.test.ts index add36d9a4dc..f5e7113ab0a 100644 --- a/src/renderer/src/runtime/web-runtime-session-browser-placement-staleness.test.ts +++ b/src/renderer/src/runtime/web-runtime-session-browser-placement-staleness.test.ts @@ -36,7 +36,6 @@ const mocks = vi.hoisted(() => ({ trackTerminalPaneSplit: vi.fn(), deliverLaunchPromptToAgentTab: vi.fn(), seedNativeChatLaunchDraftForAgentTab: vi.fn(), - getRuntimeEnvironmentIdForWorktree: vi.fn(), hasMaterializedWebRuntimeBrowserPage: vi.fn() })) @@ -69,9 +68,6 @@ vi.mock('./web-session-tabs-sync/tracking-mappings', () => ({ vi.mock('@/lib/feature-education-telemetry', () => ({ trackTerminalPaneSplit: mocks.trackTerminalPaneSplit })) -vi.mock('@/lib/worktree-runtime-owner', () => ({ - getRuntimeEnvironmentIdForWorktree: mocks.getRuntimeEnvironmentIdForWorktree -})) vi.mock('@/lib/agent-launch-prompt-delivery', () => ({ deliverLaunchPromptToAgentTab: mocks.deliverLaunchPromptToAgentTab, seedNativeChatLaunchDraftForAgentTab: mocks.seedNativeChatLaunchDraftForAgentTab diff --git a/src/renderer/src/runtime/web-runtime-session-tab-activate-close.test.ts b/src/renderer/src/runtime/web-runtime-session-tab-activate-close.test.ts index edbc982b58e..1f7169502af 100644 --- a/src/renderer/src/runtime/web-runtime-session-tab-activate-close.test.ts +++ b/src/renderer/src/runtime/web-runtime-session-tab-activate-close.test.ts @@ -42,7 +42,6 @@ const mocks = vi.hoisted(() => ({ trackTerminalPaneSplit: vi.fn(), deliverLaunchPromptToAgentTab: vi.fn(), seedNativeChatLaunchDraftForAgentTab: vi.fn(), - getRuntimeEnvironmentIdForWorktree: vi.fn(), hasMaterializedWebRuntimeBrowserPage: vi.fn() })) @@ -80,10 +79,6 @@ vi.mock('@/lib/feature-education-telemetry', () => ({ trackTerminalPaneSplit: mocks.trackTerminalPaneSplit })) -vi.mock('@/lib/worktree-runtime-owner', () => ({ - getRuntimeEnvironmentIdForWorktree: mocks.getRuntimeEnvironmentIdForWorktree -})) - vi.mock('@/lib/agent-launch-prompt-delivery', () => ({ deliverLaunchPromptToAgentTab: mocks.deliverLaunchPromptToAgentTab, seedNativeChatLaunchDraftForAgentTab: mocks.seedNativeChatLaunchDraftForAgentTab diff --git a/src/renderer/src/runtime/web-runtime-session-tab-close-undelivered-codes.test.ts b/src/renderer/src/runtime/web-runtime-session-tab-close-undelivered-codes.test.ts index b6ba507f063..3739e91ed3f 100644 --- a/src/renderer/src/runtime/web-runtime-session-tab-close-undelivered-codes.test.ts +++ b/src/renderer/src/runtime/web-runtime-session-tab-close-undelivered-codes.test.ts @@ -31,7 +31,6 @@ const mocks = vi.hoisted(() => ({ trackTerminalPaneSplit: vi.fn(), deliverLaunchPromptToAgentTab: vi.fn(), seedNativeChatLaunchDraftForAgentTab: vi.fn(), - getRuntimeEnvironmentIdForWorktree: vi.fn(), hasMaterializedWebRuntimeBrowserPage: vi.fn() })) @@ -69,10 +68,6 @@ vi.mock('@/lib/feature-education-telemetry', () => ({ trackTerminalPaneSplit: mocks.trackTerminalPaneSplit })) -vi.mock('@/lib/worktree-runtime-owner', () => ({ - getRuntimeEnvironmentIdForWorktree: mocks.getRuntimeEnvironmentIdForWorktree -})) - vi.mock('@/lib/agent-launch-prompt-delivery', () => ({ deliverLaunchPromptToAgentTab: mocks.deliverLaunchPromptToAgentTab, seedNativeChatLaunchDraftForAgentTab: mocks.seedNativeChatLaunchDraftForAgentTab diff --git a/src/renderer/src/runtime/web-runtime-session-tab-move.test.ts b/src/renderer/src/runtime/web-runtime-session-tab-move.test.ts index 4ea4ae504e2..c02af82a55d 100644 --- a/src/renderer/src/runtime/web-runtime-session-tab-move.test.ts +++ b/src/renderer/src/runtime/web-runtime-session-tab-move.test.ts @@ -26,7 +26,6 @@ const mocks = vi.hoisted(() => ({ trackTerminalPaneSplit: vi.fn(), deliverLaunchPromptToAgentTab: vi.fn(), seedNativeChatLaunchDraftForAgentTab: vi.fn(), - getRuntimeEnvironmentIdForWorktree: vi.fn(), hasMaterializedWebRuntimeBrowserPage: vi.fn() })) @@ -64,10 +63,6 @@ vi.mock('@/lib/feature-education-telemetry', () => ({ trackTerminalPaneSplit: mocks.trackTerminalPaneSplit })) -vi.mock('@/lib/worktree-runtime-owner', () => ({ - getRuntimeEnvironmentIdForWorktree: mocks.getRuntimeEnvironmentIdForWorktree -})) - vi.mock('@/lib/agent-launch-prompt-delivery', () => ({ deliverLaunchPromptToAgentTab: mocks.deliverLaunchPromptToAgentTab, seedNativeChatLaunchDraftForAgentTab: mocks.seedNativeChatLaunchDraftForAgentTab diff --git a/src/renderer/src/runtime/web-runtime-session-terminal-host-authority.test.ts b/src/renderer/src/runtime/web-runtime-session-terminal-host-authority.test.ts index 5e148cdeda8..4e43146dd3a 100644 --- a/src/renderer/src/runtime/web-runtime-session-terminal-host-authority.test.ts +++ b/src/renderer/src/runtime/web-runtime-session-terminal-host-authority.test.ts @@ -36,7 +36,6 @@ const mocks = vi.hoisted(() => ({ trackTerminalPaneSplit: vi.fn(), deliverLaunchPromptToAgentTab: vi.fn(), seedNativeChatLaunchDraftForAgentTab: vi.fn(), - getRuntimeEnvironmentIdForWorktree: vi.fn(), hasMaterializedWebRuntimeBrowserPage: vi.fn() })) @@ -74,10 +73,6 @@ vi.mock('@/lib/feature-education-telemetry', () => ({ trackTerminalPaneSplit: mocks.trackTerminalPaneSplit })) -vi.mock('@/lib/worktree-runtime-owner', () => ({ - getRuntimeEnvironmentIdForWorktree: mocks.getRuntimeEnvironmentIdForWorktree -})) - vi.mock('@/lib/agent-launch-prompt-delivery', () => ({ deliverLaunchPromptToAgentTab: mocks.deliverLaunchPromptToAgentTab, seedNativeChatLaunchDraftForAgentTab: mocks.seedNativeChatLaunchDraftForAgentTab diff --git a/src/renderer/src/runtime/web-runtime-session-terminal-legacy-create.test.ts b/src/renderer/src/runtime/web-runtime-session-terminal-legacy-create.test.ts index 3f0d8808ded..1ddafefba8b 100644 --- a/src/renderer/src/runtime/web-runtime-session-terminal-legacy-create.test.ts +++ b/src/renderer/src/runtime/web-runtime-session-terminal-legacy-create.test.ts @@ -28,7 +28,6 @@ const mocks = vi.hoisted(() => ({ trackTerminalPaneSplit: vi.fn(), deliverLaunchPromptToAgentTab: vi.fn(), seedNativeChatLaunchDraftForAgentTab: vi.fn(), - getRuntimeEnvironmentIdForWorktree: vi.fn(), hasMaterializedWebRuntimeBrowserPage: vi.fn() })) @@ -65,10 +64,6 @@ vi.mock('@/lib/feature-education-telemetry', () => ({ trackTerminalPaneSplit: mocks.trackTerminalPaneSplit })) -vi.mock('@/lib/worktree-runtime-owner', () => ({ - getRuntimeEnvironmentIdForWorktree: mocks.getRuntimeEnvironmentIdForWorktree -})) - vi.mock('@/lib/agent-launch-prompt-delivery', () => ({ deliverLaunchPromptToAgentTab: mocks.deliverLaunchPromptToAgentTab, seedNativeChatLaunchDraftForAgentTab: mocks.seedNativeChatLaunchDraftForAgentTab diff --git a/src/renderer/src/runtime/web-runtime-session-terminal-pane-delegation.test.ts b/src/renderer/src/runtime/web-runtime-session-terminal-pane-delegation.test.ts index f4b4ae42f57..48b99d758d9 100644 --- a/src/renderer/src/runtime/web-runtime-session-terminal-pane-delegation.test.ts +++ b/src/renderer/src/runtime/web-runtime-session-terminal-pane-delegation.test.ts @@ -32,7 +32,6 @@ const mocks = vi.hoisted(() => ({ trackTerminalPaneSplit: vi.fn(), deliverLaunchPromptToAgentTab: vi.fn(), seedNativeChatLaunchDraftForAgentTab: vi.fn(), - getRuntimeEnvironmentIdForWorktree: vi.fn(), hasMaterializedWebRuntimeBrowserPage: vi.fn() })) @@ -73,10 +72,6 @@ vi.mock('@/lib/feature-education-telemetry', () => ({ trackTerminalPaneSplit: mocks.trackTerminalPaneSplit })) -vi.mock('@/lib/worktree-runtime-owner', () => ({ - getRuntimeEnvironmentIdForWorktree: mocks.getRuntimeEnvironmentIdForWorktree -})) - vi.mock('@/lib/agent-launch-prompt-delivery', () => ({ deliverLaunchPromptToAgentTab: mocks.deliverLaunchPromptToAgentTab, seedNativeChatLaunchDraftForAgentTab: mocks.seedNativeChatLaunchDraftForAgentTab diff --git a/src/renderer/src/runtime/web-runtime-session-terminal-workspace-routing.test.ts b/src/renderer/src/runtime/web-runtime-session-terminal-workspace-routing.test.ts index c1908ab2786..5c646f56f11 100644 --- a/src/renderer/src/runtime/web-runtime-session-terminal-workspace-routing.test.ts +++ b/src/renderer/src/runtime/web-runtime-session-terminal-workspace-routing.test.ts @@ -29,7 +29,6 @@ const mocks = vi.hoisted(() => ({ trackTerminalPaneSplit: vi.fn(), deliverLaunchPromptToAgentTab: vi.fn(), seedNativeChatLaunchDraftForAgentTab: vi.fn(), - getRuntimeEnvironmentIdForWorktree: vi.fn(), hasMaterializedWebRuntimeBrowserPage: vi.fn() })) @@ -64,10 +63,6 @@ vi.mock('@/lib/feature-education-telemetry', () => ({ trackTerminalPaneSplit: mocks.trackTerminalPaneSplit })) -vi.mock('@/lib/worktree-runtime-owner', () => ({ - getRuntimeEnvironmentIdForWorktree: mocks.getRuntimeEnvironmentIdForWorktree -})) - vi.mock('@/lib/agent-launch-prompt-delivery', () => ({ deliverLaunchPromptToAgentTab: mocks.deliverLaunchPromptToAgentTab, seedNativeChatLaunchDraftForAgentTab: mocks.seedNativeChatLaunchDraftForAgentTab diff --git a/src/renderer/src/runtime/web-runtime-session.test.ts b/src/renderer/src/runtime/web-runtime-session.test.ts index f735f9bfd5a..1e2964eef76 100644 --- a/src/renderer/src/runtime/web-runtime-session.test.ts +++ b/src/renderer/src/runtime/web-runtime-session.test.ts @@ -31,7 +31,7 @@ const mocks = vi.hoisted(() => ({ trackTerminalPaneSplit: vi.fn(), deliverLaunchPromptToAgentTab: vi.fn(), seedNativeChatLaunchDraftForAgentTab: vi.fn(), - getRuntimeEnvironmentIdForWorktree: vi.fn(), + runtimeEnvironmentIdWithLegacyFallbackUnlessTwin: vi.fn(), hasMaterializedWebRuntimeBrowserPage: vi.fn(), recoverWebSessionTerminalOrphansBeforeApply: vi.fn( async (_state: unknown, snapshot: unknown) => snapshot @@ -76,8 +76,8 @@ vi.mock('@/lib/feature-education-telemetry', () => ({ })) vi.mock('@/lib/worktree-runtime-owner', () => ({ - getRuntimeEnvironmentIdForWorktree: mocks.getRuntimeEnvironmentIdForWorktree, - runtimeEnvironmentIdWithLegacyFallbackUnlessTwin: mocks.getRuntimeEnvironmentIdForWorktree + runtimeEnvironmentIdWithLegacyFallbackUnlessTwin: + mocks.runtimeEnvironmentIdWithLegacyFallbackUnlessTwin })) vi.mock('@/lib/agent-launch-prompt-delivery', () => ({