diff --git a/src/main/runtime/orca-runtime-tests/repository-project-operations.spec.ts b/src/main/runtime/orca-runtime-tests/repository-project-operations.spec.ts index 8c9f018f97a..babf21eac28 100644 --- a/src/main/runtime/orca-runtime-tests/repository-project-operations.spec.ts +++ b/src/main/runtime/orca-runtime-tests/repository-project-operations.spec.ts @@ -635,7 +635,7 @@ describe('OrcaRuntimeService', () => { .catch((error: unknown) => error) expect(() => runtime.addSshRepo({ connectionId: 'openclaw' })).toThrow( - 'SSH connection "openclaw" not found or not connected' + "This Orca server can't add projects on its SSH hosts." ) // Why: the defect was a silent local clone/probe recorded as remote, not a bad message, diff --git a/src/main/runtime/runtime-project-host-setup-controller.test.ts b/src/main/runtime/runtime-project-host-setup-controller.test.ts index 3349ff52097..d2ecb757276 100644 --- a/src/main/runtime/runtime-project-host-setup-controller.test.ts +++ b/src/main/runtime/runtime-project-host-setup-controller.test.ts @@ -1,5 +1,8 @@ import { describe, expect, it, vi } from 'vitest' -import { RuntimeProjectHostSetupController } from './runtime-project-host-setup-controller' +import { + RuntimeProjectHostSetupController, + SERVER_SSH_PROJECT_ADD_UNAVAILABLE_MESSAGE +} from './runtime-project-host-setup-controller' import { getProjectHostSetupForRepo } from '../../shared/project-host-setup-lookup' import { projectHostSetupProjectionFromRepos } from '../../shared/project-host-setup-projection' import type { Repo } from '../../shared/repo-types' @@ -78,11 +81,11 @@ describe('RuntimeProjectHostSetupController host routing', () => { expect(addRepo).not.toHaveBeenCalled() }) - it('keeps repo.addRemote failing with the not-connected text', () => { + it('refuses repo.addRemote by saying where the project can be added instead', () => { const { controller, addRepo } = makeController() expect(() => controller.addSshRepo({ connectionId: TARGET_ID })).toThrow( - `SSH connection "${TARGET_ID}" not found or not connected` + SERVER_SSH_PROJECT_ADD_UNAVAILABLE_MESSAGE ) expect(addRepo).not.toHaveBeenCalled() }) diff --git a/src/main/runtime/runtime-project-host-setup-controller.ts b/src/main/runtime/runtime-project-host-setup-controller.ts index 4f9c61e1367..1388c3aaef3 100644 --- a/src/main/runtime/runtime-project-host-setup-controller.ts +++ b/src/main/runtime/runtime-project-host-setup-controller.ts @@ -63,6 +63,9 @@ function assertCloneHostIsSupported(hostId: ExecutionHostId | null | undefined): ) } +export const SERVER_SSH_PROJECT_ADD_UNAVAILABLE_MESSAGE = + "This Orca server can't add projects on its SSH hosts. Add the SSH host on your computer to open its projects." + function sshTargetNotServedError(connectionId: string): SshHostNotServedError { return new SshHostNotServedError(connectionId, sshTargetNotConnectedMessage(connectionId)) } @@ -124,12 +127,15 @@ export class RuntimeProjectHostSetupController { return this.completeSetup(args, repo, !knownRepoIds.has(repo.id)) } - /** `repo.addRemote` stays registered for older clients; this process serves no SSH target. */ + /** + * `repo.addRemote` stays registered for older clients, which still offer it; this process serves + * no SSH target, so the refusal says where such a project can be added instead. + */ addSshRepo(args: { connectionId: string }): Promise { if (!this.deps.getStore()) { throw new Error('runtime_unavailable') } - throw sshTargetNotServedError(args.connectionId) + throw new SshHostNotServedError(args.connectionId, SERVER_SSH_PROJECT_ADD_UNAVAILABLE_MESSAGE) } async setupClone(args: ProjectHostSetupCloneArgs): Promise { diff --git a/src/renderer/src/components/sidebar/AddRepoDialogStepContent.tsx b/src/renderer/src/components/sidebar/AddRepoDialogStepContent.tsx index fe3e6a1ca0f..fc553cbc9b4 100644 --- a/src/renderer/src/components/sidebar/AddRepoDialogStepContent.tsx +++ b/src/renderer/src/components/sidebar/AddRepoDialogStepContent.tsx @@ -156,7 +156,7 @@ export function AddRepoDialogStepContent({ hostSelector={hostSelector} actionsDisabled={actionsDisabled} browseHostKind={browseHostKind} - serverSshProjects={serverSsh?.state} + offersServerSshHosts={serverSsh !== null} onBrowse={onBrowse} onOpenCloneStep={onOpenCloneStep} onOpenRemoteStep={onOpenRemoteStep} diff --git a/src/renderer/src/components/sidebar/AddRepoStartSteps.test.tsx b/src/renderer/src/components/sidebar/AddRepoStartSteps.test.tsx index eeec4d2ddf1..c9edb002dee 100644 --- a/src/renderer/src/components/sidebar/AddRepoStartSteps.test.tsx +++ b/src/renderer/src/components/sidebar/AddRepoStartSteps.test.tsx @@ -140,31 +140,26 @@ describe('AddRepoLocalStartStep', () => { expect(model.description).toBe('Existing Git repository or folder on this host') }) - it.each([ - ['supported', false, 'Open a project folder from an SSH host this server connects to'], - ['unsupported', true, 'Update this server to add projects on its SSH hosts'], - ['unknown', true, 'Open a project folder from an SSH host this server connects to'] - ] as const)( - "offers a server's own SSH hosts only when the server supports it (%s)", - (serverSshProjects, disabled, description) => { - const { secondaryActions } = getAddRepoLocalStartActions({ - browseHostKind: 'runtime', - serverSshProjects, - onBrowse: vi.fn(), - onOpenCloneStep: vi.fn(), - onOpenRemoteStep: vi.fn(), - onOpenCreateStep: vi.fn() - }) - const remote = secondaryActions.find((action) => action.kind === 'remote') + it("explains instead of offering a project on a paired server's SSH host", () => { + const { secondaryActions } = getAddRepoLocalStartActions({ + browseHostKind: 'runtime', + offersServerSshHosts: true, + onBrowse: vi.fn(), + onOpenCloneStep: vi.fn(), + onOpenRemoteStep: vi.fn(), + onOpenCreateStep: vi.fn() + }) + const remote = secondaryActions.find((action) => action.kind === 'remote') - expect(secondaryActions.map((action) => action.kind)).toEqual(['clone', 'remote', 'create']) - expect(remote).toMatchObject({ - title: "Project on this server's SSH host", - description, - disabled - }) - } - ) + expect(secondaryActions.map((action) => action.kind)).toEqual(['clone', 'remote', 'create']) + // Why: the server refuses repo.addRemote, so the row must never open the step that calls it. + expect(remote).toMatchObject({ + title: "Project on this server's SSH host", + description: + 'Not available from a server. Add the SSH host on this computer to open its projects.', + disabled: true + }) + }) it('focuses Browse folder when the default Add Project step opens', async () => { const { container, root } = await renderLocalStartStepDom() diff --git a/src/renderer/src/components/sidebar/AddRepoStartSteps.tsx b/src/renderer/src/components/sidebar/AddRepoStartSteps.tsx index 3828b580897..c4fd9945e29 100644 --- a/src/renderer/src/components/sidebar/AddRepoStartSteps.tsx +++ b/src/renderer/src/components/sidebar/AddRepoStartSteps.tsx @@ -68,7 +68,7 @@ type AddRepoLocalStartStepProps = { hostSelector?: ReactNode actionsDisabled?: boolean browseHostKind?: 'local' | 'runtime' - serverSshProjects?: 'supported' | 'unsupported' | 'unknown' + offersServerSshHosts?: boolean onBrowse: () => void onOpenCloneStep: () => void onOpenRemoteStep: () => void @@ -85,7 +85,7 @@ export function AddRepoLocalStartStep({ hostSelector, actionsDisabled = false, browseHostKind = 'local', - serverSshProjects, + offersServerSshHosts, onBrowse, onOpenCloneStep, onOpenRemoteStep, @@ -101,7 +101,7 @@ export function AddRepoLocalStartStep({ onOpenRemoteStep, onOpenCreateStep, browseHostKind, - serverSshProjects + offersServerSshHosts }) // The white fill + ⏎ chip is a roving selection indicator, not a fixed "primary" badge: diff --git a/src/renderer/src/components/sidebar/add-repo-local-start-actions.ts b/src/renderer/src/components/sidebar/add-repo-local-start-actions.ts index 96ae9f98230..a709949d8a6 100644 --- a/src/renderer/src/components/sidebar/add-repo-local-start-actions.ts +++ b/src/renderer/src/components/sidebar/add-repo-local-start-actions.ts @@ -8,8 +8,8 @@ export type AddRepoLocalStartActionHandlers = { onOpenRemoteStep: () => void onOpenCreateStep: () => void browseHostKind?: 'local' | 'runtime' - /** A paired server's own SSH hosts; `unsupported` when it predates managing them. */ - serverSshProjects?: 'supported' | 'unsupported' | 'unknown' + /** A paired server is selected, whose own SSH hosts the dialog explains instead of offering. */ + offersServerSshHosts?: boolean } export type AddRepoLocalStartAction = { @@ -27,7 +27,7 @@ export function getAddRepoLocalStartActions({ onOpenRemoteStep, onOpenCreateStep, browseHostKind = 'local', - serverSshProjects + offersServerSshHosts = false }: AddRepoLocalStartActionHandlers): { primaryAction: AddRepoLocalStartAction secondaryActions: AddRepoLocalStartAction[] @@ -52,7 +52,9 @@ export function getAddRepoLocalStartActions({ onClick: onBrowse } - const remote = serverSshProjects + // Why disabled: a server registers no project on its SSH hosts (each runs its own managed + // server), so the row only says where that project can be added instead. + const remote = offersServerSshHosts ? { kind: 'remote' as const, icon: Monitor, @@ -60,18 +62,11 @@ export function getAddRepoLocalStartActions({ 'auto.components.sidebar.add.repo.local.start.actions.serverSshTitle', "Project on this server's SSH host" ), - description: - serverSshProjects === 'unsupported' - ? translate( - 'auto.components.sidebar.add.repo.local.start.actions.serverSshUpdate', - 'Update this server to add projects on its SSH hosts' - ) - : translate( - 'auto.components.sidebar.add.repo.local.start.actions.serverSshDescription', - 'Open a project folder from an SSH host this server connects to' - ), - // Why: an old server has no way to do this, and this client must not do it locally. - disabled: serverSshProjects !== 'supported', + description: translate( + 'auto.components.sidebar.add.repo.local.start.actions.serverSshAddOnThisComputer', + 'Not available from a server. Add the SSH host on this computer to open its projects.' + ), + disabled: true, onClick: onOpenRemoteStep } : null diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json index 9aafb85d600..a8e8f07bb66 100644 --- a/src/renderer/src/i18n/locales/en.json +++ b/src/renderer/src/i18n/locales/en.json @@ -6227,8 +6227,7 @@ "7edb8ebe24": "Clone from URL", "a6c20dca96": "Open a project from an SSH target", "serverSshTitle": "Project on this server's SSH host", - "serverSshDescription": "Open a project folder from an SSH host this server connects to", - "serverSshUpdate": "Update this server to add projects on its SSH hosts", + "serverSshAddOnThisComputer": "Not available from a server. Add the SSH host on this computer to open its projects.", "fb4fc5380e": "Local project, Git repo, or folder with many repos", "2281fdc8c7": "Browse folder", "runtimeBrowseDescription": "Existing Git repository or folder on this host" diff --git a/src/renderer/src/i18n/locales/es.json b/src/renderer/src/i18n/locales/es.json index 00b16929359..7e0b5865798 100644 --- a/src/renderer/src/i18n/locales/es.json +++ b/src/renderer/src/i18n/locales/es.json @@ -5990,8 +5990,7 @@ "7edb8ebe24": "Clonar desde URL", "a6c20dca96": "Abrir un proyecto desde un destino SSH", "serverSshTitle": "Proyecto en un host SSH de este servidor", - "serverSshDescription": "Abre una carpeta de proyecto de un host SSH al que se conecta este servidor", - "serverSshUpdate": "Actualiza este servidor para añadir proyectos en sus hosts SSH", + "serverSshAddOnThisComputer": "No disponible desde un servidor. Añade el host SSH en este ordenador para abrir sus proyectos.", "fb4fc5380e": "Proyecto local, repositorio de Git o carpeta con muchos repositorios", "2281fdc8c7": "Explorar carpeta", "runtimeBrowseDescription": "Repositorio Git o carpeta existente en este host" diff --git a/src/renderer/src/i18n/locales/fr.json b/src/renderer/src/i18n/locales/fr.json index 92d5c61cc08..57cc98d0558 100644 --- a/src/renderer/src/i18n/locales/fr.json +++ b/src/renderer/src/i18n/locales/fr.json @@ -6103,8 +6103,7 @@ "7edb8ebe24": "Cloner depuis une URL", "a6c20dca96": "Ouvrir un projet depuis une cible SSH", "serverSshTitle": "Projet sur un hôte SSH de ce serveur", - "serverSshDescription": "Ouvrir un dossier de projet depuis un hôte SSH auquel ce serveur se connecte", - "serverSshUpdate": "Mettez ce serveur à jour pour ajouter des projets sur ses hôtes SSH", + "serverSshAddOnThisComputer": "Indisponible depuis un serveur. Ajoutez l’hôte SSH sur cet ordinateur pour ouvrir ses projets.", "fb4fc5380e": "Projet local, dépôt Git ou dossier contenant plusieurs dépôts", "2281fdc8c7": "Parcourir le dossier", "runtimeBrowseDescription": "Dépôt Git existant ou dossier sur cette machine" diff --git a/src/renderer/src/i18n/locales/ja.json b/src/renderer/src/i18n/locales/ja.json index 071548504fd..63acba07bf6 100644 --- a/src/renderer/src/i18n/locales/ja.json +++ b/src/renderer/src/i18n/locales/ja.json @@ -5975,8 +5975,7 @@ "7edb8ebe24": "URL からクローン", "a6c20dca96": "SSH ターゲットからプロジェクトを開く", "serverSshTitle": "このサーバーの SSH ホスト上のプロジェクト", - "serverSshDescription": "このサーバーが接続する SSH ホストのプロジェクトフォルダーを開きます", - "serverSshUpdate": "SSH ホストにプロジェクトを追加するには、このサーバーを更新してください", + "serverSshAddOnThisComputer": "サーバーからは利用できません。プロジェクトを開くには、このコンピューターに SSH ホストを追加してください。", "fb4fc5380e": "ローカルプロジェクト、Git リポジトリ、または多数のリポジトリを含むフォルダー", "2281fdc8c7": "フォルダーを参照", "runtimeBrowseDescription": "このホスト上の既存の Git リポジトリまたはフォルダー" diff --git a/src/renderer/src/i18n/locales/ko.json b/src/renderer/src/i18n/locales/ko.json index cfe3e66b67c..01db6f208ab 100644 --- a/src/renderer/src/i18n/locales/ko.json +++ b/src/renderer/src/i18n/locales/ko.json @@ -5975,8 +5975,7 @@ "7edb8ebe24": "URL에서 복제", "a6c20dca96": "SSH 대상에서 프로젝트 열기", "serverSshTitle": "이 서버의 SSH 호스트에 있는 프로젝트", - "serverSshDescription": "이 서버가 연결하는 SSH 호스트의 프로젝트 폴더를 엽니다", - "serverSshUpdate": "SSH 호스트에 프로젝트를 추가하려면 이 서버를 업데이트하세요", + "serverSshAddOnThisComputer": "서버에서는 사용할 수 없습니다. 프로젝트를 열려면 이 컴퓨터에 SSH 호스트를 추가하세요.", "fb4fc5380e": "로컬 프로젝트, Git repo 또는 repos가 많은 폴더", "2281fdc8c7": "폴더 찾아보기", "runtimeBrowseDescription": "이 호스트의 기존 Git 리포지토리 또는 폴더" diff --git a/src/renderer/src/i18n/locales/zh.json b/src/renderer/src/i18n/locales/zh.json index 0e3e01917f4..33430f894f0 100644 --- a/src/renderer/src/i18n/locales/zh.json +++ b/src/renderer/src/i18n/locales/zh.json @@ -5975,8 +5975,7 @@ "7edb8ebe24": "从 URL 克隆", "a6c20dca96": "从 SSH 目标打开项目", "serverSshTitle": "此服务器 SSH 主机上的项目", - "serverSshDescription": "从此服务器连接的 SSH 主机打开项目文件夹", - "serverSshUpdate": "更新此服务器后才能在其 SSH 主机上添加项目", + "serverSshAddOnThisComputer": "无法从服务器使用。请在此电脑上添加该 SSH 主机以打开其项目。", "fb4fc5380e": "本地项目、Git 仓库或包含多个仓库的文件夹", "2281fdc8c7": "浏览文件夹", "runtimeBrowseDescription": "此主机上的现有 Git 仓库或文件夹"