diff --git a/src/main/agent-hooks/restored-subagent-liveness-sweep.ts b/src/main/agent-hooks/restored-subagent-liveness-sweep.ts index 3402ab7bf5b..f183534e64f 100644 --- a/src/main/agent-hooks/restored-subagent-liveness-sweep.ts +++ b/src/main/agent-hooks/restored-subagent-liveness-sweep.ts @@ -1,7 +1,6 @@ import { - LOCAL_EXECUTION_HOST_ID, + getConnectionExecutionHostId, parseExecutionHostId, - toSshExecutionHostId, type ExecutionHostId } from '../../shared/execution-host' import type { FolderWorkspace } from '../../shared/folder-workspace-types' @@ -96,8 +95,7 @@ function resolveDeclaredExecutionHost(owner: ExecutionHostOwner): ExecutionHostI if (owner.executionHostId?.trim()) { return parseExecutionHostId(owner.executionHostId)?.id ?? null } - const connectionId = owner.connectionId?.trim() - return connectionId ? toSshExecutionHostId(connectionId) : LOCAL_EXECUTION_HOST_ID + return getConnectionExecutionHostId(owner.connectionId?.trim()) } /** Resolve persisted workspace ownership; unknown provenance is not local authority. */ diff --git a/src/main/ipc/pty/runtime/inventory-operations.ts b/src/main/ipc/pty/runtime/inventory-operations.ts index 882908a926e..ae72c67b57b 100644 --- a/src/main/ipc/pty/runtime/inventory-operations.ts +++ b/src/main/ipc/pty/runtime/inventory-operations.ts @@ -1,8 +1,7 @@ import { ptyOwnership } from '../provider/ownership-state' import { getProvider, localProvider, registeredPtyProviders } from '../provider/registry' import { - LOCAL_EXECUTION_HOST_ID, - toSshExecutionHostId, + getConnectionExecutionHostId, type ExecutionHostId } from '../../../../shared/execution-host' import type { PtyProcessInfo } from '../../../providers/pty-process-info' @@ -27,9 +26,7 @@ export async function listProcessesWithHostScopeFromRuntimeController( ): Promise<{ processes: PtyProcessInfo[]; hostIds: ExecutionHostId[] }> { const providerSessions = await Promise.all( registeredPtyProviders().map(async ({ provider, connectionId }) => { - const hostId: ExecutionHostId = connectionId - ? toSshExecutionHostId(connectionId) - : LOCAL_EXECUTION_HOST_ID + const hostId = getConnectionExecutionHostId(connectionId) try { return { processes: await (connectionId ? provider.listProcesses(opts) : provider.listProcesses()), diff --git a/src/main/ipc/repos/nested-repo-import-handler.ts b/src/main/ipc/repos/nested-repo-import-handler.ts index 90294ec464a..cd7b2a14d2c 100644 --- a/src/main/ipc/repos/nested-repo-import-handler.ts +++ b/src/main/ipc/repos/nested-repo-import-handler.ts @@ -14,7 +14,7 @@ import { } from '../../project-groups/nested-repo-import' import { createNestedRepoImportTargetResolver } from '../../project-groups/nested-repo-import-target' import { getSshGitProvider } from '../../providers/ssh-git-dispatch' -import { LOCAL_EXECUTION_HOST_ID, toSshExecutionHostId } from '../../../shared/execution-host' +import { getConnectionExecutionHostId } from '../../../shared/execution-host' import { detectRepoIconAndUpstream } from '../../repo-icon-autodetect' import { prepareLocalWorktreeRootForRepo } from '../../worktree-root-preparation' import { getActiveMultiplexer } from '../ssh' @@ -123,9 +123,7 @@ export function registerNestedRepoImportHandler(mainWindow: BrowserWindow, store const detected = await detectRepoIconAndUpstream({ repoPath: importRepoPath, kind: 'git', - executionHostId: args.connectionId - ? toSshExecutionHostId(args.connectionId) - : LOCAL_EXECUTION_HOST_ID + executionHostId: getConnectionExecutionHostId(args.connectionId) }) const repo: Repo = { id: randomUUID(), diff --git a/src/main/runtime/orca-runtime-files-test-harness.ts b/src/main/runtime/orca-runtime-files-test-harness.ts index a1e27fa4973..93710e9b355 100644 --- a/src/main/runtime/orca-runtime-files-test-harness.ts +++ b/src/main/runtime/orca-runtime-files-test-harness.ts @@ -3,9 +3,8 @@ import { awaitRuntimeFileWatcherUnsubscribes, RuntimeFileCommands } from './orca import { resetSshConnectionGenerations } from '../ssh/ssh-connection-generation' import { resetRuntimeFileMocks } from './orca-runtime-files-mock-registry' import { - LOCAL_EXECUTION_HOST_ID, + getConnectionExecutionHostId, normalizeExecutionHostId, - toSshExecutionHostId, type ExecutionHostId } from '../../shared/execution-host' @@ -61,10 +60,7 @@ export function createRuntimeFileCommands(options?: { // Mirrors the real resolver: the worktree's own host outranks the repo row. const runtimeFileTargetExecutionHostId = (): ExecutionHostId => { const connectionId = store.getRepo(worktree.repoId)?.connectionId - return ( - normalizeExecutionHostId(options?.hostId) ?? - (connectionId ? toSshExecutionHostId(connectionId) : LOCAL_EXECUTION_HOST_ID) - ) + return normalizeExecutionHostId(options?.hostId) ?? getConnectionExecutionHostId(connectionId) } const commands = new RuntimeFileCommands({ getRuntimeId: () => 'runtime-1', diff --git a/src/main/runtime/orca-runtime-invalidate-all-handles-for-pty.ts b/src/main/runtime/orca-runtime-invalidate-all-handles-for-pty.ts index 9d6e5d56ca4..96699ebbafe 100644 --- a/src/main/runtime/orca-runtime-invalidate-all-handles-for-pty.ts +++ b/src/main/runtime/orca-runtime-invalidate-all-handles-for-pty.ts @@ -2,11 +2,7 @@ import { OrcaRuntimeWithResolveKnownWorkspaceFileTarget } from './orca-runtime-resolve-known-workspace-file-target' import type { PtyIncarnationId } from '../../shared/pty-incarnation' import { getPtyExecutionHost } from '../../shared/terminal-execution-host' -import { - LOCAL_EXECUTION_HOST_ID, - parseExecutionHostId, - toSshExecutionHostId -} from '../../shared/execution-host' +import { getConnectionExecutionHostId, parseExecutionHostId } from '../../shared/execution-host' export class OrcaRuntimeWithInvalidateAllHandlesForPty extends OrcaRuntimeWithResolveKnownWorkspaceFileTarget { protected invalidateAllHandlesForPty(ptyId: string, preserveHandle?: string): Set { @@ -128,8 +124,7 @@ export class OrcaRuntimeWithInvalidateAllHandlesForPty extends OrcaRuntimeWithRe } const connection = connectionId === undefined ? this.ptysById.get(ptyId)?.connectionId : connectionId - const providerKey = - hostId ?? (connection ? toSshExecutionHostId(connection) : LOCAL_EXECUTION_HOST_ID) + const providerKey = hostId ?? getConnectionExecutionHostId(connection) // A pending census predates this admission or exit, including legacy IDs without an incarnation. this.ptyControllerInventoryGenerationByProvider.set(providerKey, generation) } diff --git a/src/main/runtime/orca-runtime-persist-headless-terminal-title.ts b/src/main/runtime/orca-runtime-persist-headless-terminal-title.ts index 932d6075cd5..72124d6afca 100644 --- a/src/main/runtime/orca-runtime-persist-headless-terminal-title.ts +++ b/src/main/runtime/orca-runtime-persist-headless-terminal-title.ts @@ -9,8 +9,8 @@ import type { import type { ResolvedWorktree } from './runtime-worktree-path-identity' import type { Repo } from '../../shared/repo-types' import { + getConnectionExecutionHostId, LOCAL_EXECUTION_HOST_ID, - toSshExecutionHostId, type ExecutionHostId } from '../../shared/execution-host' import { getLocalProjectWorktreeGitOptions } from '../project-runtime-git-options' @@ -214,9 +214,7 @@ export class OrcaRuntimeWithPersistHeadlessTerminalTitle extends OrcaRuntimeWith // ambiguous one, and it is never hosted by a runtime environment. return { worktree: this.folderWorkspaceToResolvedWorktree(folderScope.folderWorkspace), - executionHostId: folderScope.connectionId - ? toSshExecutionHostId(folderScope.connectionId) - : LOCAL_EXECUTION_HOST_ID + executionHostId: getConnectionExecutionHostId(folderScope.connectionId) } } diff --git a/src/main/runtime/orca-runtime-refresh-pty-worktree-records-with-controller-inventory.ts b/src/main/runtime/orca-runtime-refresh-pty-worktree-records-with-controller-inventory.ts index dde737d3aa9..59247841a7a 100644 --- a/src/main/runtime/orca-runtime-refresh-pty-worktree-records-with-controller-inventory.ts +++ b/src/main/runtime/orca-runtime-refresh-pty-worktree-records-with-controller-inventory.ts @@ -4,6 +4,7 @@ import type { ResolvedWorktree } from './runtime-worktree-path-identity' import type * as PtyControllerContract from './runtime-pty-controller-contract' import { FLOATING_TERMINAL_WORKTREE_ID } from '../../shared/constants' import { + getConnectionExecutionHostId, LOCAL_EXECUTION_HOST_ID, parseExecutionHostId, toSshExecutionHostId @@ -55,7 +56,7 @@ export class OrcaRuntimeWithRefreshPtyWorktreeRecordsWithControllerInventory ext return null } const inventoryGeneration = ++this.ptyControllerInventorySequence - const providerKey = connectionId ? toSshExecutionHostId(connectionId) : LOCAL_EXECUTION_HOST_ID + const providerKey = getConnectionExecutionHostId(connectionId) const livenessObservationAtStart = this.ptyLivenessObservationSequence if (connectionId === undefined) { this.ptyControllerAggregateInventoryGeneration = inventoryGeneration @@ -150,7 +151,7 @@ export class OrcaRuntimeWithRefreshPtyWorktreeRecordsWithControllerInventory ext parseAppSshPtyId(session.id)?.connectionId ?? (typeof connectionId === 'string' ? connectionId : null) const persistedIndexes = getPersistedIndexes( - sessionConnectionId ? toSshExecutionHostId(sessionConnectionId) : LOCAL_EXECUTION_HOST_ID + getConnectionExecutionHostId(sessionConnectionId) ) const controllerIdentity = controllerIdentityByPtyId.get(session.id) const persistedWorktreeId = persistedIndexes.worktreeIdByPtyId.get(session.id) @@ -243,8 +244,7 @@ export class OrcaRuntimeWithRefreshPtyWorktreeRecordsWithControllerInventory ext const ptyHostId = encodedHostId === 'foreign' ? null - : (encodedHostId ?? - (pty.connectionId ? toSshExecutionHostId(pty.connectionId) : LOCAL_EXECUTION_HOST_ID)) + : (encodedHostId ?? getConnectionExecutionHostId(pty.connectionId)) if (!ptyHostId) { continue } diff --git a/src/main/runtime/orca-runtime-resolve-known-workspace-file-target.ts b/src/main/runtime/orca-runtime-resolve-known-workspace-file-target.ts index 69b58c0632a..7f6f29af8a3 100644 --- a/src/main/runtime/orca-runtime-resolve-known-workspace-file-target.ts +++ b/src/main/runtime/orca-runtime-resolve-known-workspace-file-target.ts @@ -1,8 +1,8 @@ // @ts-nocheck -- mechanically split from OrcaRuntimeService; behavior is covered by AST equivalence and characterization tests. import { OrcaRuntimeWithPersistHeadlessTerminalTitle } from './orca-runtime-persist-headless-terminal-title' import { + getConnectionExecutionHostId, LOCAL_EXECUTION_HOST_ID, - toSshExecutionHostId, type ExecutionHostId } from '../../shared/execution-host' import type { ResolvedWorktree } from './runtime-worktree-path-identity' @@ -57,9 +57,7 @@ export class OrcaRuntimeWithResolveKnownWorkspaceFileTarget extends OrcaRuntimeW const worktree = this.folderWorkspaceToResolvedWorktree(folderWorkspace) const target = { worktree, - executionHostId: candidateConnectionId - ? toSshExecutionHostId(candidateConnectionId) - : LOCAL_EXECUTION_HOST_ID + executionHostId: getConnectionExecutionHostId(candidateConnectionId) } targets.set(`${target.executionHostId}\0${worktree.id}`, target) } catch { diff --git a/src/main/runtime/runtime-legacy-worker-terminal-recovery-runner.ts b/src/main/runtime/runtime-legacy-worker-terminal-recovery-runner.ts index 681c6b94986..0715ea82768 100644 --- a/src/main/runtime/runtime-legacy-worker-terminal-recovery-runner.ts +++ b/src/main/runtime/runtime-legacy-worker-terminal-recovery-runner.ts @@ -1,5 +1,5 @@ import { parseAppSshPtyId } from '../../shared/ssh-pty-id' -import { LOCAL_EXECUTION_HOST_ID, toSshExecutionHostId } from '../../shared/execution-host' +import { getConnectionExecutionHostId } from '../../shared/execution-host' import { getPtyExecutionHost } from '../../shared/terminal-execution-host' import type { RuntimeLegacyWorkerTerminalRecoveryController } from './runtime-legacy-worker-terminal-recovery-controller' import { @@ -104,9 +104,7 @@ export async function runLegacyWorkerTerminalRecovery( ).values() ] const inventory = await ports.refreshInventory(resolvedWorktrees, provider.connectionId) - const hostId = provider.connectionId - ? toSshExecutionHostId(provider.connectionId) - : LOCAL_EXECUTION_HOST_ID + const hostId = getConnectionExecutionHostId(provider.connectionId) if (!inventory || !inventory.queriedHostIds.has(hostId)) { provider.entries.forEach(({ candidate }) => deferredDispatchIds.add(candidate.dispatchId)) continue diff --git a/src/main/runtime/runtime-skill-install-authority.ts b/src/main/runtime/runtime-skill-install-authority.ts index 80ce29b0735..1e0498e6e0c 100644 --- a/src/main/runtime/runtime-skill-install-authority.ts +++ b/src/main/runtime/runtime-skill-install-authority.ts @@ -2,12 +2,12 @@ import { homedir } from 'node:os' import { getWslHome } from '../wsl' import { parseWslUncPath } from '../../shared/wsl-paths' import { - type ExecutionHostId, + getConnectionExecutionHostId, getRepoExecutionHostId, LOCAL_EXECUTION_HOST_ID, normalizeExecutionHostId, parseExecutionHostId, - toSshExecutionHostId + type ExecutionHostId } from '../../shared/execution-host' import { getRepoIdFromWorktreeId } from '../../shared/worktree/id' import type { IPtyProvider } from '../providers/types' @@ -124,7 +124,7 @@ export function folderExecutionHostId(folder: { }): ExecutionHostId { return ( normalizeExecutionHostId(folder.executionHostId) ?? - (folder.connectionId ? toSshExecutionHostId(folder.connectionId) : LOCAL_EXECUTION_HOST_ID) + getConnectionExecutionHostId(folder.connectionId) ) } diff --git a/src/main/runtime/runtime-workspace-session-controller.ts b/src/main/runtime/runtime-workspace-session-controller.ts index f6bb0014169..35e38e38175 100644 --- a/src/main/runtime/runtime-workspace-session-controller.ts +++ b/src/main/runtime/runtime-workspace-session-controller.ts @@ -1,8 +1,8 @@ import { - LOCAL_EXECUTION_HOST_ID, + getConnectionExecutionHostId, getRepoExecutionHostId, + LOCAL_EXECUTION_HOST_ID, parseExecutionHostId, - toSshExecutionHostId, type ExecutionHostId } from '../../shared/execution-host' import type { FolderWorkspace } from '../../shared/folder-workspace-types' @@ -43,8 +43,7 @@ export class RuntimeWorkspaceSessionController { } return parsedHostId } - const connectionId = this.deps.resolveFolderConnectionId(workspace) - return connectionId ? toSshExecutionHostId(connectionId) : LOCAL_EXECUTION_HOST_ID + return getConnectionExecutionHostId(this.deps.resolveFolderConnectionId(workspace)) } const resolvedWorktreeId = scope?.type === 'worktree' ? scope.worktreeId : worktreeId const repo = store?.getRepo?.(getRepoIdFromWorktreeId(resolvedWorktreeId)) @@ -156,11 +155,7 @@ export class RuntimeWorkspaceSessionController { ? (parseExecutionHostId(workspace.executionHostId)?.id ?? null) : null const connectionId = explicitHostId ? null : this.deps.resolveFolderConnectionId(workspace) - return [ - workspace.id, - explicitHostId ?? - (connectionId ? toSshExecutionHostId(connectionId) : LOCAL_EXECUTION_HOST_ID) - ] as const + return [workspace.id, explicitHostId ?? getConnectionExecutionHostId(connectionId)] as const }) ) const hostIds = new Set(['local']) diff --git a/src/main/ssh/ssh-connection-generation.ts b/src/main/ssh/ssh-connection-generation.ts index af82a29a674..610d35713a4 100644 --- a/src/main/ssh/ssh-connection-generation.ts +++ b/src/main/ssh/ssh-connection-generation.ts @@ -1,5 +1,5 @@ import { randomBytes } from 'node:crypto' -import { toSshExecutionHostId } from '../../shared/execution-host' +import { getConnectionExecutionHostId } from '../../shared/execution-host' const SESSION_COUNTER_BITS = 13 const SESSION_COUNTER_STRIDE = 2 ** SESSION_COUNTER_BITS @@ -105,7 +105,7 @@ export function assertSshMutationExpectation( expectedGeneration: number | undefined, expectedExecutionHostId?: string ): void { - const actualExecutionHostId = connectionId ? toSshExecutionHostId(connectionId) : 'local' + const actualExecutionHostId = getConnectionExecutionHostId(connectionId) if (expectedExecutionHostId !== undefined && expectedExecutionHostId !== actualExecutionHostId) { throw new Error('Workspace host changed; refresh and try again') } diff --git a/src/renderer/src/components/activity/activity-event-builder-context.ts b/src/renderer/src/components/activity/activity-event-builder-context.ts index 3fdd2584fef..c630e4e8752 100644 --- a/src/renderer/src/components/activity/activity-event-builder-context.ts +++ b/src/renderer/src/components/activity/activity-event-builder-context.ts @@ -2,7 +2,7 @@ import { findIndexedRepoOwnerForHost } from '@/lib/worktree-runtime-owner-index' import { getRemoteRuntimePtyEnvironmentId } from '@/runtime/runtime-terminal-stream' import type { AgentStatusEntry } from '../../../../shared/agent-status-types' import { - LOCAL_EXECUTION_HOST_ID, + getConnectionExecutionHostId, toRuntimeExecutionHostId, toSshExecutionHostId, type ExecutionHostId @@ -126,7 +126,7 @@ function resolveActivityExecutionHostId( return toRuntimeExecutionHostId(runtimeEnvironmentId) } if (entry.connectionId !== undefined) { - return entry.connectionId ? toSshExecutionHostId(entry.connectionId) : LOCAL_EXECUTION_HOST_ID + return getConnectionExecutionHostId(entry.connectionId) } const connectionId = parseAppSshPtyId(terminalPtyId ?? '')?.connectionId return connectionId ? toSshExecutionHostId(connectionId) : undefined diff --git a/src/renderer/src/components/native-chat/native-chat-attachment-upload.ts b/src/renderer/src/components/native-chat/native-chat-attachment-upload.ts index eeabce9ca53..0ca498fa617 100644 --- a/src/renderer/src/components/native-chat/native-chat-attachment-upload.ts +++ b/src/renderer/src/components/native-chat/native-chat-attachment-upload.ts @@ -9,9 +9,9 @@ import { translate } from '@/i18n/i18n' import { extractIpcErrorMessage } from '@/lib/ipc-error' import { findKnownWorktreeById } from '@/store/slices/worktrees/listing/detected-worktree-meta' import { + getConnectionExecutionHostId, parseExecutionHostId, toRuntimeExecutionHostId, - toSshExecutionHostId, type ExecutionHostId } from '../../../../shared/execution-host' import { getConnectionIdFromState } from '@/lib/connection-context' @@ -106,9 +106,7 @@ export function resolveNativeChatAttachmentHost( } const hostId = runtimeId ? toRuntimeExecutionHostId(runtimeId) - : connectionId - ? toSshExecutionHostId(connectionId) - : 'local' + : getConnectionExecutionHostId(connectionId) return findKnownWorktreeById(state, worktreeId, hostId) ? hostId : null } diff --git a/src/renderer/src/components/sidebar/folder-workspace-composer-helpers.ts b/src/renderer/src/components/sidebar/folder-workspace-composer-helpers.ts index 6a73cf1e51e..b5397f2ae4e 100644 --- a/src/renderer/src/components/sidebar/folder-workspace-composer-helpers.ts +++ b/src/renderer/src/components/sidebar/folder-workspace-composer-helpers.ts @@ -9,10 +9,9 @@ import { } from '../../../../shared/new-workspace/workspace-source' import { isPathInsideOrEqual } from '../../../../shared/cross-platform-path' import { + getConnectionExecutionHostId, getRepoExecutionHostId, - LOCAL_EXECUTION_HOST_ID, normalizeExecutionHostId, - toSshExecutionHostId, type ExecutionHostId } from '../../../../shared/execution-host' import { getProjectGroupSubtreeIds } from '../../../../shared/project-groups' @@ -33,9 +32,7 @@ function getProjectGroupExecutionHostId(projectGroup: ProjectGroup): ExecutionHo if (executionHostId) { return executionHostId } - return projectGroup.connectionId - ? toSshExecutionHostId(projectGroup.connectionId) - : LOCAL_EXECUTION_HOST_ID + return getConnectionExecutionHostId(projectGroup.connectionId) } export function getFolderSourceRepos( diff --git a/src/renderer/src/components/sidebar/worktree-list/listing/host-filtering.ts b/src/renderer/src/components/sidebar/worktree-list/listing/host-filtering.ts index 0b8e27c0f12..b3f22320e40 100644 --- a/src/renderer/src/components/sidebar/worktree-list/listing/host-filtering.ts +++ b/src/renderer/src/components/sidebar/worktree-list/listing/host-filtering.ts @@ -1,5 +1,6 @@ import { ALL_EXECUTION_HOSTS_SCOPE, + getConnectionExecutionHostId, normalizeExecutionHostId, parseExecutionHostId, toSshExecutionHostId, @@ -110,7 +111,7 @@ function getProjectGroupExecutionHostIdForFolderPathStatus( if (executionHostId) { return executionHostId } - return group.connectionId ? toSshExecutionHostId(group.connectionId) : 'local' + return getConnectionExecutionHostId(group.connectionId) } export function getFolderPathStatusRouteOptionsForRows({ diff --git a/src/renderer/src/components/terminal-pane/pty-transport.ts b/src/renderer/src/components/terminal-pane/pty-transport.ts index 9f9783d6c1d..49f0613df64 100644 --- a/src/renderer/src/components/terminal-pane/pty-transport.ts +++ b/src/renderer/src/components/terminal-pane/pty-transport.ts @@ -1,4 +1,4 @@ -import { toSshExecutionHostId } from '../../../../shared/execution-host' +import { getConnectionExecutionHostId } from '../../../../shared/execution-host' import { attachIpcPty } from './ipc-pty-attach' import { connectIpcPty } from './ipc-pty-connect' import { createIpcPtySessionHandlers } from './ipc-pty-session-handlers' @@ -296,7 +296,7 @@ export function createIpcPtyTransport(opts: IpcPtyTransportOptions = {}): PtyTra isConnected: () => connected, getPtyId: () => ptyId, getConnectionId: () => connectionId ?? null, - getExecutionHostId: () => (connectionId ? toSshExecutionHostId(connectionId) : 'local'), + getExecutionHostId: () => getConnectionExecutionHostId(connectionId), getRuntimeEnvironmentId: () => null, getLocalSessionMetadata: () => connectionId diff --git a/src/renderer/src/components/terminal-pane/terminal-file-open-routing.ts b/src/renderer/src/components/terminal-pane/terminal-file-open-routing.ts index 30657257c31..78a919d88a7 100644 --- a/src/renderer/src/components/terminal-pane/terminal-file-open-routing.ts +++ b/src/renderer/src/components/terminal-pane/terminal-file-open-routing.ts @@ -16,9 +16,8 @@ import { type TerminalFileContext } from './terminal-file-path-mapping' import { - LOCAL_EXECUTION_HOST_ID, + getConnectionExecutionHostId, toRuntimeExecutionHostId, - toSshExecutionHostId, type ExecutionHostId } from '../../../../shared/execution-host' import { statUserOpenedPath } from '@/lib/user-opened-local-path' @@ -231,9 +230,7 @@ export function openDetectedFilePath( const runtimeOwnerId = fileContext.settings?.activeRuntimeEnvironmentId?.trim() const executionHostId = runtimeOwnerId ? toRuntimeExecutionHostId(runtimeOwnerId) - : fileContext.connectionId - ? toSshExecutionHostId(fileContext.connectionId) - : LOCAL_EXECUTION_HOST_ID + : getConnectionExecutionHostId(fileContext.connectionId) const siblingRoute = findWorkspaceFileRoute(store, executionHostId, mappedFilePath) if (siblingRoute) { targetWorktreeId = siblingRoute.worktreeId diff --git a/src/renderer/src/lib/jira-source-host.ts b/src/renderer/src/lib/jira-source-host.ts index 642a6265628..a52e5c2e2c1 100644 --- a/src/renderer/src/lib/jira-source-host.ts +++ b/src/renderer/src/lib/jira-source-host.ts @@ -1,8 +1,8 @@ import { + getConnectionExecutionHostId, LOCAL_EXECUTION_HOST_ID, normalizeExecutionHostId, parseExecutionHostId, - toSshExecutionHostId, type ExecutionHostId } from '../../../shared/execution-host' @@ -13,9 +13,7 @@ export function resolveJiraSourceHostId(args: { }): ExecutionHostId { const groupHostId = normalizeExecutionHostId(args.groupExecutionHostId) ?? - (args.groupConnectionId - ? toSshExecutionHostId(args.groupConnectionId) - : LOCAL_EXECUTION_HOST_ID) + getConnectionExecutionHostId(args.groupConnectionId) const hostId = normalizeExecutionHostId(args.workspaceHostId) ?? groupHostId return parseExecutionHostId(hostId)?.kind === 'ssh' ? LOCAL_EXECUTION_HOST_ID : hostId } diff --git a/src/renderer/src/lib/new-workspace-project-options.ts b/src/renderer/src/lib/new-workspace-project-options.ts index 72c69cd3286..88e9006c46c 100644 --- a/src/renderer/src/lib/new-workspace-project-options.ts +++ b/src/renderer/src/lib/new-workspace-project-options.ts @@ -4,12 +4,7 @@ import type { Project, ProjectHostSetup } from '../../../shared/project-types' import type { Repo } from '../../../shared/repo-types' import { isClipboardTextByteLengthOverLimit } from '../../../shared/clipboard-text' import type { ExecutionHostRegistryEntry } from '../../../shared/execution-host-registry' -import { - LOCAL_EXECUTION_HOST_ID, - parseExecutionHostId, - toSshExecutionHostId, - type ExecutionHostId -} from '../../../shared/execution-host' +import { getRepoExecutionHostId, type ExecutionHostId } from '../../../shared/execution-host' import { getDuplicateProjectDetailsById, type ProjectSetupDirectory @@ -154,12 +149,7 @@ function getProjectGroupDetail(group: ProjectGroup): string { } export function getNewWorkspaceProjectGroupHostId(group: ProjectGroup): ExecutionHostId { - const executionHost = parseExecutionHostId(group.executionHostId) - if (executionHost) { - return executionHost.id - } - const connectionId = group.connectionId?.trim() - return connectionId ? toSshExecutionHostId(connectionId) : LOCAL_EXECUTION_HOST_ID + return getRepoExecutionHostId(group) } /** diff --git a/src/renderer/src/lib/worktree-runtime-owner-index.ts b/src/renderer/src/lib/worktree-runtime-owner-index.ts index df5aa8551eb..dd574b0ffc3 100644 --- a/src/renderer/src/lib/worktree-runtime-owner-index.ts +++ b/src/renderer/src/lib/worktree-runtime-owner-index.ts @@ -6,7 +6,6 @@ import { getRepoExecutionHostId, parseExecutionHostId, toRuntimeExecutionHostId, - toSshExecutionHostId, type ExecutionHostId } from '../../../shared/execution-host' @@ -58,12 +57,7 @@ export function getCatalogOwnerHostId(owner: { connectionId?: string | null executionHostId?: string | null }): ExecutionHostId { - const explicitHost = parseExecutionHostId(owner.executionHostId) - if (explicitHost) { - return explicitHost.id - } - const connectionId = owner.connectionId?.trim() - return connectionId ? toSshExecutionHostId(connectionId) : 'local' + return getRepoExecutionHostId(owner) } function buildCatalogOwnerIndex< diff --git a/src/renderer/src/store/slices/project-group-owner-routing.ts b/src/renderer/src/store/slices/project-group-owner-routing.ts index 78da4385a53..67d3a793bba 100644 --- a/src/renderer/src/store/slices/project-group-owner-routing.ts +++ b/src/renderer/src/store/slices/project-group-owner-routing.ts @@ -1,10 +1,9 @@ import type { GlobalSettings } from '../../../../shared/global-settings-types' import type { ProjectGroup } from '../../../../shared/project-group-types' import { + getRepoExecutionHostId, LOCAL_EXECUTION_HOST_ID, - normalizeExecutionHostId, parseExecutionHostId, - toSshExecutionHostId, type ExecutionHostId } from '../../../../shared/execution-host' import { findIndexedProjectGroupOwner } from '@/lib/worktree-runtime-owner-index' @@ -21,12 +20,7 @@ type ProjectGroupOwnerRoutingState = { // Why: persisted rows predate host stamping and may carry padded/unparseable ids; normalize so // routing and catalog identity agree on the same owner host. export function getProjectGroupHostId(group: ProjectGroupHostParts): ExecutionHostId { - const executionHostId = normalizeExecutionHostId(group.executionHostId) - if (executionHostId) { - return executionHostId - } - const connectionId = group.connectionId?.trim() - return connectionId ? toSshExecutionHostId(connectionId) : LOCAL_EXECUTION_HOST_ID + return getRepoExecutionHostId(group) } export function catalogOwnsHost(catalogHostId: string, rowHostId: string): boolean { diff --git a/src/shared/execution-host.test.ts b/src/shared/execution-host.test.ts index fba1d1fee8d..c290193071b 100644 --- a/src/shared/execution-host.test.ts +++ b/src/shared/execution-host.test.ts @@ -2,6 +2,7 @@ import { afterEach, describe, expect, it, vi } from 'vitest' import { ALL_EXECUTION_HOSTS_SCOPE, LOCAL_EXECUTION_HOST_ID, + getConnectionExecutionHostId, getExecutionHostLabel, getLocalExecutionHostLabel, getRepoExecutionHostId, @@ -97,6 +98,12 @@ describe('execution host identity', () => { ) }) + it('maps a legacy connection id to its host without trimming it', () => { + expect(getConnectionExecutionHostId(null)).toBe('local') + expect(getConnectionExecutionHostId('')).toBe('local') + expect(getConnectionExecutionHostId('a b')).toBe('ssh:a%20b') + }) + it('derives repo ownership from SSH connection ids', () => { expect(getRepoExecutionHostId({ connectionId: null })).toBe(LOCAL_EXECUTION_HOST_ID) expect(getRepoExecutionHostId({ connectionId: 'ssh-target-1' })).toBe('ssh:ssh-target-1') diff --git a/src/shared/execution-host.ts b/src/shared/execution-host.ts index 7c9f3857d07..0a6d6956b21 100644 --- a/src/shared/execution-host.ts +++ b/src/shared/execution-host.ts @@ -54,6 +54,13 @@ export function toSshExecutionHostId(targetId: string): `ssh:${string}` { return `ssh:${encodeURIComponent(targetId)}` } +/** Legacy `connectionId` spelling: absent means this machine, present means that SSH target. */ +export function getConnectionExecutionHostId( + connectionId: string | null | undefined +): ExecutionHostId { + return connectionId ? toSshExecutionHostId(connectionId) : LOCAL_EXECUTION_HOST_ID +} + export function toRuntimeExecutionHostId(environmentId: string): `runtime:${string}` { return `runtime:${encodeURIComponent(environmentId)}` } @@ -165,8 +172,7 @@ export function getRepoExecutionHostId(repo: { if (executionHostId) { return executionHostId } - const connectionId = normalizeHostPart(repo.connectionId) - return connectionId ? toSshExecutionHostId(connectionId) : LOCAL_EXECUTION_HOST_ID + return getConnectionExecutionHostId(normalizeHostPart(repo.connectionId)) } export function getSshTargetIdForExecutionHost( diff --git a/src/shared/folder-workspace-worktree.ts b/src/shared/folder-workspace-worktree.ts index 903f4f9b92a..99bfb644268 100644 --- a/src/shared/folder-workspace-worktree.ts +++ b/src/shared/folder-workspace-worktree.ts @@ -1,7 +1,11 @@ import type { FolderWorkspace } from './folder-workspace-types' import type { Worktree } from './worktree/types' import { folderWorkspaceKey } from './workspace-scope' -import { parseExecutionHostId, toSshExecutionHostId, type ExecutionHostId } from './execution-host' +import { + getConnectionExecutionHostId, + parseExecutionHostId, + type ExecutionHostId +} from './execution-host' import { composeWorktreeHostIdentity } from './worktree/host-qualified-identity' import { normalizeWorkspaceCreatorProvenance } from './workspace-creator-provenance' import { getWorkspaceAttachments } from './workspace-attachments' @@ -32,10 +36,7 @@ export function projectGroupIdFromRepoId(repoId: string | null | undefined): str function getFolderWorkspaceWorktreeHostId( workspace: Pick ): ExecutionHostId { - return ( - workspace.executionHostId ?? - (workspace.connectionId ? toSshExecutionHostId(workspace.connectionId) : 'local') - ) + return workspace.executionHostId ?? getConnectionExecutionHostId(workspace.connectionId) } export function getFolderWorkspaceHostIdentity( diff --git a/src/shared/task-source-context.ts b/src/shared/task-source-context.ts index f6d2ca1532a..1ae7636d6eb 100644 --- a/src/shared/task-source-context.ts +++ b/src/shared/task-source-context.ts @@ -1,10 +1,10 @@ import { + getRepoExecutionHostId, LOCAL_EXECUTION_HOST_ID, - type ExecutionHostId, normalizeExecutionHostId, parseExecutionHostId, toRuntimeExecutionHostId, - toSshExecutionHostId + type ExecutionHostId } from './execution-host' import { areTaskProviderIdentitiesEqual, @@ -113,7 +113,7 @@ export function buildTaskSourceContextFromRepo(args: { return normalizeTaskSourceContext({ provider: args.provider, projectId: args.projectId, - hostId: getRepoHostId(args.repo), + hostId: getRepoExecutionHostId(args.repo), repoId: args.repo.id, projectHostSetupId: args.projectHostSetupId, providerIdentity: args.providerIdentity, @@ -195,15 +195,6 @@ export function buildWorkspaceRunContext(args: { } } -function getRepoHostId(repo: Pick): ExecutionHostId { - const explicit = normalizeExecutionHostId(repo.executionHostId) - if (explicit) { - return explicit - } - const connectionId = normalizeNonEmptyString(repo.connectionId) - return connectionId ? toSshExecutionHostId(connectionId) : LOCAL_EXECUTION_HOST_ID -} - function normalizeTaskProvider(value: unknown): TaskProvider | null { switch (value) { case 'github':