mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 00:57:45 +00:00
refactor(renderer): one twin check, one worktree-copy scan, drop stale resolver mocks (#30579)
- isTwinWorkspace replaces four inline resolveOwner(...).kind === 'ambiguous' checks - someWorktreeCopy shared by isUnprovenServerSshRoute and hasHostEvidence - file-explorer owner: drop the unreachable second unresolved guard and the stale comment - native chat owner: drop a null ternary the helper already handles - tests: delete stale getRuntimeEnvironmentIdForWorktree mocks (29 suites), one shared local+ssh twin fixture, file:// open-target tests next to their module, reset mocks.twin
This commit is contained in:
@@ -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'
|
||||
}))
|
||||
|
||||
-1
@@ -35,7 +35,6 @@ vi.mock('@/store', async () => {
|
||||
}
|
||||
})
|
||||
vi.mock('@/lib/worktree-runtime-owner', () => ({
|
||||
getRuntimeEnvironmentIdForWorktree: () => null,
|
||||
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin: () => null,
|
||||
getExecutionHostIdForWorktree: () => mocks.executionHostId
|
||||
}))
|
||||
|
||||
+2
-14
@@ -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',
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
@@ -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'
|
||||
}))
|
||||
|
||||
@@ -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 })
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
-2
@@ -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'
|
||||
}))
|
||||
|
||||
-1
@@ -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'
|
||||
}))
|
||||
|
||||
|
||||
-2
@@ -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'
|
||||
}))
|
||||
|
||||
-1
@@ -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'
|
||||
}))
|
||||
|
||||
|
||||
@@ -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<typeof WorktreeRuntimeOwnerModule>()),
|
||||
getRuntimeEnvironmentIdForWorktree: () => null
|
||||
}))
|
||||
|
||||
vi.mock('@/runtime/runtime-rpc-client', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof RuntimeRpcClientModule>()),
|
||||
runtimeEnvironmentSupportsCapability: vi.fn(async () => true)
|
||||
|
||||
@@ -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' }
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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<ReturnType<typeof setTimeout> | null>(null)
|
||||
// Why: runtime searches can finish out of order; ids keep stale results
|
||||
// from overwriting the newest query state.
|
||||
|
||||
-2
@@ -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
|
||||
}))
|
||||
|
||||
@@ -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}`
|
||||
}))
|
||||
|
||||
@@ -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
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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', () => ({
|
||||
|
||||
@@ -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<WorktreeOperationRouteState, 'worktreesByRepo' | 'detectedWorktreesByRepo'>,
|
||||
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)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -22,8 +22,6 @@ vi.mock('@/store', () => ({
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('./worktree-runtime-owner', () => ({ getRuntimeEnvironmentIdForWorktree: () => null }))
|
||||
|
||||
vi.mock('./agent-draft-readiness', () => ({
|
||||
waitForAgentDraftInputReady: async () => true
|
||||
}))
|
||||
|
||||
@@ -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
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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',
|
||||
|
||||
@@ -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<string, TwinWorktreeRow[]> } {
|
||||
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' }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)))
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
-4
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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', () => ({
|
||||
|
||||
Reference in New Issue
Block a user