From 8be630181119c31aa2cfac158fe6877903c0d684 Mon Sep 17 00:00:00 2001 From: Jinwoo-H Date: Tue, 11 Aug 2026 12:17:10 -0400 Subject: [PATCH] test(skills): cover paired runtime installation --- .../skill-remote-install-service.test.ts | 43 +++ .../skills/skill-remote-install-service.ts | 25 +- tests/e2e/helpers/paired-electron-client.ts | 5 +- ...xture.ts => remote-skill-cloud-fixture.ts} | 48 +-- tests/e2e/paired-skill-installation.spec.ts | 277 ++++++++++++++++++ tests/e2e/ssh-skill-installation.spec.ts | 52 ++-- 6 files changed, 392 insertions(+), 58 deletions(-) rename tests/e2e/helpers/{ssh-skill-cloud-fixture.ts => remote-skill-cloud-fixture.ts} (66%) create mode 100644 tests/e2e/paired-skill-installation.spec.ts diff --git a/src/main/skills/skill-remote-install-service.test.ts b/src/main/skills/skill-remote-install-service.test.ts index f9e5510a512..2cb93f054b2 100644 --- a/src/main/skills/skill-remote-install-service.test.ts +++ b/src/main/skills/skill-remote-install-service.test.ts @@ -138,4 +138,47 @@ describe('installSkillOnRemoteRuntime', () => { ).rejects.toThrow('skill-install-remote-runtime_error') expect(cleanup).toHaveBeenCalledOnce() }) + + it('uses the client for a configured development origin rejected by the host', async () => { + const cleanup = vi.fn(async () => undefined) + mocks.callRuntimeEnvironment + .mockResolvedValueOnce({ + id: 'rpc-1', + ok: false, + error: { code: 'runtime_error', message: 'skill-download-origin-rejected' } + }) + .mockResolvedValueOnce(success(result)) + mocks.transferSkillPackageToRuntime.mockResolvedValue({ uploadId: 'upload-1', cleanup }) + + await expect( + installSkillOnRemoteRuntime({ + userDataPath: '/state', + environmentId: 'environment-1', + request, + capabilities: ['skills.install.v1', 'skills.upload.v1'], + requireHttps: false + }) + ).resolves.toEqual(result) + expect(mocks.transferSkillPackageToRuntime).toHaveBeenCalledOnce() + expect(cleanup).toHaveBeenCalledOnce() + }) + + it('preserves packaged-host policy rejection without attempting transfer', async () => { + mocks.callRuntimeEnvironment.mockResolvedValue({ + id: 'rpc-1', + ok: false, + error: { code: 'runtime_error', message: 'skill-download-origin-rejected' } + }) + + await expect( + installSkillOnRemoteRuntime({ + userDataPath: '/state', + environmentId: 'environment-1', + request, + capabilities: ['skills.install.v1', 'skills.upload.v1'], + requireHttps: true + }) + ).rejects.toThrow('skill-install-remote-runtime_error') + expect(mocks.transferSkillPackageToRuntime).not.toHaveBeenCalled() + }) }) diff --git a/src/main/skills/skill-remote-install-service.ts b/src/main/skills/skill-remote-install-service.ts index baa42cb7354..6cf49784a60 100644 --- a/src/main/skills/skill-remote-install-service.ts +++ b/src/main/skills/skill-remote-install-service.ts @@ -12,6 +12,10 @@ import { SkillInstallFailureSchema } from '../../shared/skill-install-failure' import { SkillInstallOperationError } from './skill-install-operation-error' const DIRECT_DOWNLOAD_FAILURE = 'skill-download-transport-failed' +const DEVELOPMENT_DOWNLOAD_POLICY_FAILURES = new Set([ + 'skill-download-url-rejected', + 'skill-download-origin-rejected' +]) async function install( userDataPath: string, @@ -27,14 +31,23 @@ async function install( )) as RuntimeRpcResponse } -function isDownloadTransportFailure(response: RuntimeRpcResponse): boolean { +function isDirectDownloadUnavailable( + response: RuntimeRpcResponse, + requireHttps: boolean +): boolean { const structured = response.ok === false ? SkillInstallFailureSchema.safeParse(response.error.data) : null + if (response.ok === true) { + return false + } + const codes = [ + response.error.code, + response.error.message, + ...(structured?.success === true ? [structured.data.code] : []) + ] return ( - response.ok === false && - (response.error.code === DIRECT_DOWNLOAD_FAILURE || - response.error.message === DIRECT_DOWNLOAD_FAILURE || - (structured?.success === true && structured.data.code === DIRECT_DOWNLOAD_FAILURE)) + codes.includes(DIRECT_DOWNLOAD_FAILURE) || + (!requireHttps && codes.some((code) => DEVELOPMENT_DOWNLOAD_POLICY_FAILURES.has(code))) ) } @@ -60,7 +73,7 @@ export async function installSkillOnRemoteRuntime(input: { throw new Error('skill-install-remote-ingress-invalid') } const direct = await install(input.userDataPath, input.environmentId, input.request) - if (!isDownloadTransportFailure(direct)) { + if (!isDirectDownloadUnavailable(direct, input.requireHttps)) { if (direct.ok !== true) { throw remoteFailure(direct) } diff --git a/tests/e2e/helpers/paired-electron-client.ts b/tests/e2e/helpers/paired-electron-client.ts index 07bb467780f..75bb8dce5d0 100644 --- a/tests/e2e/helpers/paired-electron-client.ts +++ b/tests/e2e/helpers/paired-electron-client.ts @@ -141,7 +141,8 @@ export async function launchPairedWebClient( export async function launchPairedElectronClient( offer: RuntimeDesktopPairingOffer, testInfo: TestInfo, - name: string + name: string, + options: { extraEnv?: Record } = {} ): Promise { const userDataDir = mkdtempSync(path.join(os.tmpdir(), 'orca-e2e-paired-desktop-')) const directSshProbePath = path.join(userDataDir, 'forbidden-local-ssh-connects.jsonl') @@ -154,7 +155,7 @@ export async function launchPairedElectronClient( const homeIsolation = createElectronHomeIsolation({ inheritedEnv: cleanEnv, launchEnv: {}, - extraEnv: {}, + extraEnv: options.extraEnv ?? {}, userDataDir }) const mainPath = path.join(process.cwd(), 'out', 'main', 'index.js') diff --git a/tests/e2e/helpers/ssh-skill-cloud-fixture.ts b/tests/e2e/helpers/remote-skill-cloud-fixture.ts similarity index 66% rename from tests/e2e/helpers/ssh-skill-cloud-fixture.ts rename to tests/e2e/helpers/remote-skill-cloud-fixture.ts index 2bc6a45ccf5..f1d28d926b7 100644 --- a/tests/e2e/helpers/ssh-skill-cloud-fixture.ts +++ b/tests/e2e/helpers/remote-skill-cloud-fixture.ts @@ -8,13 +8,13 @@ import { } from '../../../src/main/skills/skill-package-creation' import { SKILL_PACKAGE_CONTENT_TYPE } from '../../../src/shared/skill-package-manifest' -export const SSH_SKILL_CLOUD_PORT = Number(process.env.ORCA_E2E_SKILL_CLOUD_PORT ?? '43961') -export const SSH_SKILL_CLOUD_ORIGIN = `http://127.0.0.1:${SSH_SKILL_CLOUD_PORT}` -export const SSH_SKILL_PACKAGE_ID = 'package_ssh_e2e' -export const SSH_SKILL_VERSION_ID = 'version_ssh_e2e' -export const SSH_SKILL_NAME = 'ssh-e2e-skill' +export const REMOTE_SKILL_CLOUD_PORT = Number(process.env.ORCA_E2E_SKILL_CLOUD_PORT ?? '43961') +export const REMOTE_SKILL_CLOUD_ORIGIN = `http://127.0.0.1:${REMOTE_SKILL_CLOUD_PORT}` +export const REMOTE_SKILL_PACKAGE_ID = 'package_remote_e2e' +export const REMOTE_SKILL_VERSION_ID = 'version_remote_e2e' +export const REMOTE_SKILL_NAME = 'remote-e2e-skill' -export type SshSkillCloudFixture = { +export type RemoteSkillCloudFixture = { archive: CreatedSkillPackage bytes: Buffer requests: { method: string; path: string; body: unknown }[] @@ -22,25 +22,25 @@ export type SshSkillCloudFixture = { server: Server } -export async function startSshSkillCloudFixture(): Promise { - const root = await mkdtemp(join(tmpdir(), 'orca-ssh-skill-cloud-')) +export async function startRemoteSkillCloudFixture(): Promise { + const root = await mkdtemp(join(tmpdir(), 'orca-remote-skill-cloud-')) const source = join(root, 'source') await mkdir(source) await writeFile( join(source, 'SKILL.md'), - '---\nname: ssh-e2e-skill\ndescription: SSH relay integration\n---\n\n# SSH E2E\n' + '---\nname: remote-e2e-skill\ndescription: Remote installation integration\n---\n\n# Remote E2E\n' ) const archive = await createSkillPackageArchive({ sourceDirectory: source, archivePath: join(root, 'package.tar.gz'), - packageId: SSH_SKILL_PACKAGE_ID, - versionId: SSH_SKILL_VERSION_ID, + packageId: REMOTE_SKILL_PACKAGE_ID, + versionId: REMOTE_SKILL_VERSION_ID, createdAt: '2026-08-11T12:00:00.000Z' }) const bytes = await readFile(archive.archivePath) - const requests: SshSkillCloudFixture['requests'] = [] + const requests: RemoteSkillCloudFixture['requests'] = [] const server = createServer((request, response) => { - void handleSshSkillCloudRequest({ request, response, archive, bytes, requests }).catch( + void handleRemoteSkillCloudRequest({ request, response, archive, bytes, requests }).catch( (error) => { response.writeHead(500, { 'content-type': 'application/json' }) response.end(JSON.stringify({ code: 'fixture_failed', message: String(error) })) @@ -49,24 +49,24 @@ export async function startSshSkillCloudFixture(): Promise }) await new Promise((resolve, reject) => { server.once('error', reject) - server.listen(SSH_SKILL_CLOUD_PORT, '127.0.0.1', resolve) + server.listen(REMOTE_SKILL_CLOUD_PORT, '127.0.0.1', resolve) }) return { archive, bytes, requests, root, server } } -export async function stopSshSkillCloudFixture(fixture: SshSkillCloudFixture): Promise { +export async function stopRemoteSkillCloudFixture(fixture: RemoteSkillCloudFixture): Promise { await new Promise((resolve) => fixture.server.close(() => resolve())) await rm(fixture.root, { recursive: true, force: true }) } -async function handleSshSkillCloudRequest(input: { +async function handleRemoteSkillCloudRequest(input: { request: IncomingMessage response: ServerResponse archive: CreatedSkillPackage bytes: Buffer - requests: SshSkillCloudFixture['requests'] + requests: RemoteSkillCloudFixture['requests'] }): Promise { - const path = new URL(input.request.url ?? '/', SSH_SKILL_CLOUD_ORIGIN).pathname + const path = new URL(input.request.url ?? '/', REMOTE_SKILL_CLOUD_ORIGIN).pathname if (input.request.method === 'GET' && path === '/package.tar.gz') { input.requests.push({ method: 'GET', path, body: null }) input.response.writeHead(200, { @@ -79,7 +79,7 @@ async function handleSshSkillCloudRequest(input: { if ( input.request.method === 'POST' && path === - `/v1/skill-packages/${SSH_SKILL_PACKAGE_ID}/versions/${SSH_SKILL_VERSION_ID}/download-grants` + `/v1/skill-packages/${REMOTE_SKILL_PACKAGE_ID}/versions/${REMOTE_SKILL_VERSION_ID}/download-grants` ) { const body = JSON.parse(await readRequestBody(input.request)) as unknown input.requests.push({ method: 'POST', path, body }) @@ -94,14 +94,14 @@ async function handleSshSkillCloudRequest(input: { function downloadGrant(archive: CreatedSkillPackage, compressedBytes: number) { return { grant: { - url: `${SSH_SKILL_CLOUD_ORIGIN}/package.tar.gz`, + url: `${REMOTE_SKILL_CLOUD_ORIGIN}/package.tar.gz`, expiresAt: '2099-01-01T00:00:00.000Z' }, version: { - packageId: SSH_SKILL_PACKAGE_ID, - versionId: SSH_SKILL_VERSION_ID, - name: SSH_SKILL_NAME, - description: 'SSH relay integration', + packageId: REMOTE_SKILL_PACKAGE_ID, + versionId: REMOTE_SKILL_VERSION_ID, + name: REMOTE_SKILL_NAME, + description: 'Remote installation integration', packageDigest: archive.manifest.packageDigest, archiveSha256: archive.archiveSha256, compressedBytes, diff --git a/tests/e2e/paired-skill-installation.spec.ts b/tests/e2e/paired-skill-installation.spec.ts new file mode 100644 index 00000000000..ac31a81e2f1 --- /dev/null +++ b/tests/e2e/paired-skill-installation.spec.ts @@ -0,0 +1,277 @@ +import { existsSync, mkdtempSync, readFileSync, rmSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import type { Page } from '@stablyai/playwright-test' +import type { SkillInstallDestination } from '../../src/shared/skill-install-contract' +import { expect, test } from './helpers/orca-app' +import { + createRuntimeDesktopPairingOffer, + launchPairedElectronClient, + type PairedElectronClient +} from './helpers/paired-electron-client' +import { + launchHeadlessPairedRuntimeHost, + type HeadlessPairedRuntimeHost +} from './helpers/headless-paired-runtime-host' +import { + REMOTE_SKILL_CLOUD_ORIGIN, + REMOTE_SKILL_NAME, + REMOTE_SKILL_PACKAGE_ID, + REMOTE_SKILL_VERSION_ID, + startRemoteSkillCloudFixture, + stopRemoteSkillCloudFixture, + type RemoteSkillCloudFixture +} from './helpers/remote-skill-cloud-fixture' + +let cloud: RemoteSkillCloudFixture | null = null + +test.beforeAll(async () => { + cloud = await startRemoteSkillCloudFixture() +}) + +test.afterAll(async () => { + if (cloud) { + await stopRemoteSkillCloudFixture(cloud) + cloud = null + } +}) + +test('installs on a headed desktop runtime without a local fallback', async ({ + electronApp, + orcaPage, + testRepoPath +}, testInfo) => { + test.setTimeout(240_000) + const fixture = requireCloudFixture() + const requestStart = fixture.requests.length + const folderRoot = mkdtempSync(join(tmpdir(), 'orca-paired-skill-folder-')) + let client: PairedElectronClient | null = null + try { + const hostHome = await electronApp.evaluate(({ app }) => app.getPath('home')) + const worktreeId = await activeWorktreeId(orcaPage) + const folderWorkspaceId = await createHostFolderWorkspace(orcaPage, folderRoot) + client = await launchPairedElectronClient( + await createRuntimeDesktopPairingOffer(orcaPage), + testInfo, + 'Skill installation client', + { extraEnv: cloudClientEnvironment() } + ) + await connectCloud(client.page) + const clientHome = await client.app.evaluate(({ app }) => app.getPath('home')) + + await installPreviewRemove(client.page, client.environmentId, { scope: 'global' }, hostHome) + await installPreviewRemove( + client.page, + client.environmentId, + { scope: 'workspace', worktreeId }, + testRepoPath + ) + await installPreviewRemove( + client.page, + client.environmentId, + { scope: 'workspace', folderWorkspaceId }, + folderRoot + ) + + expect(existsSync(skillPath(clientHome))).toBe(false) + await expectManagedInstalls(client.page, client.environmentId, []) + expectCloudRequests(fixture, requestStart, 3) + } finally { + await client?.dispose() + rmSync(skillPath(testRepoPath), { recursive: true, force: true }) + rmSync(folderRoot, { recursive: true, force: true }) + } +}) + +test('installs on a headless serve runtime through the same contract', async ({ + testRepoPath +}, testInfo) => { + test.setTimeout(240_000) + const fixture = requireCloudFixture() + const requestStart = fixture.requests.length + const host = await launchHeadlessPairedRuntimeHost() + let client: PairedElectronClient | null = null + try { + client = await launchPairedElectronClient(host.offer, testInfo, 'Headless skill client', { + extraEnv: cloudClientEnvironment() + }) + await connectCloud(client.page) + const hostHome = await host.app.evaluate(({ app }) => app.getPath('home')) + const clientHome = await client.app.evaluate(({ app }) => app.getPath('home')) + const worktreeId = await addHeadlessHostWorktree(host, testRepoPath) + + await installPreviewRemove(client.page, client.environmentId, { scope: 'global' }, hostHome) + await installPreviewRemove( + client.page, + client.environmentId, + { scope: 'workspace', worktreeId }, + testRepoPath + ) + + expect(existsSync(skillPath(clientHome))).toBe(false) + await expectManagedInstalls(client.page, client.environmentId, []) + expectCloudRequests(fixture, requestStart, 2) + } finally { + await client?.dispose() + await host.dispose() + rmSync(skillPath(testRepoPath), { recursive: true, force: true }) + } +}) + +function cloudClientEnvironment(): Record { + return { + ORCA_ARTIFACTS_API_URL: REMOTE_SKILL_CLOUD_ORIGIN, + ORCA_CLOUD_API_URL: REMOTE_SKILL_CLOUD_ORIGIN, + ORCA_CLOUD_CLIENT_ID: 'skills-e2e-client', + ORCA_CLOUD_DEV_AUTH: '1', + ORCA_CLOUD_ALLOW_PLAINTEXT_SESSION: '1', + ORCA_SKILL_PACKAGE_DOWNLOAD_ORIGINS: REMOTE_SKILL_CLOUD_ORIGIN + } +} + +async function connectCloud(page: Page): Promise { + const auth = await page.evaluate(() => window.api.orcaProfiles.connectCurrent()) + expect(auth.status).toBe('connected') +} + +function requireCloudFixture(): RemoteSkillCloudFixture { + if (!cloud) { + throw new Error('skill Cloud fixture unavailable') + } + return cloud +} + +function skillPath(root: string): string { + return join(root, '.agents', 'skills', REMOTE_SKILL_NAME) +} + +async function activeWorktreeId(page: Page): Promise { + const worktreeId = await page.evaluate(() => window.__store?.getState().activeWorktreeId ?? null) + if (!worktreeId) { + throw new Error('headed host worktree unavailable') + } + return worktreeId +} + +async function createHostFolderWorkspace(page: Page, folderPath: string): Promise { + return page.evaluate(async (path) => { + const group = await window.api.projectGroups.create({ + name: 'Paired skill folder E2E', + parentPath: path + }) + const workspace = await window.api.folderWorkspaces.create({ + projectGroupId: group.id, + name: 'Paired skill folder E2E', + folderPath: path + }) + return workspace.id + }, folderPath) +} + +async function addHeadlessHostWorktree( + host: HeadlessPairedRuntimeHost, + repoPath: string +): Promise { + const added = await host.client.call<{ repo: { id: string } }>('repo.add', { + path: repoPath, + kind: 'git' + }) + let worktreeId = '' + await expect + .poll(async () => { + const listed = await host.client.call<{ worktrees: { id: string }[] }>('worktree.list', { + repo: `id:${added.result.repo.id}` + }) + worktreeId = listed.result.worktrees[0]?.id ?? '' + return worktreeId + }) + .not.toBe('') + return worktreeId +} + +async function installPreviewRemove( + page: Page, + environmentId: string, + destination: SkillInstallDestination, + root: string +): Promise { + const operation = await page.evaluate( + ({ destination, environmentId, packageId, versionId }) => + window.api.skills.installPackageVersion({ + packageId, + versionId, + environmentId, + destination + }), + { + destination, + environmentId, + packageId: REMOTE_SKILL_PACKAGE_ID, + versionId: REMOTE_SKILL_VERSION_ID + } + ) + expect(operation, JSON.stringify(operation, null, 2)).toMatchObject({ + status: 'ok', + value: { status: 'installed', name: REMOTE_SKILL_NAME } + }) + expect(readFileSync(join(skillPath(root), 'SKILL.md'), 'utf8')).toContain('# Remote E2E') + + const fixture = requireCloudFixture() + const preview = await page.evaluate( + ({ destination, environmentId, name, packageIdentity }) => + window.api.skills.previewInstall({ + environmentId, + package: packageIdentity, + name, + destination + }), + { + destination, + environmentId, + name: REMOTE_SKILL_NAME, + packageIdentity: { + packageId: fixture.archive.manifest.packageId, + versionId: fixture.archive.manifest.versionId, + packageDigest: fixture.archive.manifest.packageDigest, + archiveSha256: fixture.archive.archiveSha256, + compressedBytes: fixture.bytes.length + } + } + ) + expect(preview).toMatchObject({ status: 'ok', value: { currentState: 'unchanged' } }) + + const removed = await page.evaluate( + ({ destination, environmentId, name }) => + window.api.skills.removeInstall({ environmentId, name, destination }), + { destination, environmentId, name: REMOTE_SKILL_NAME } + ) + expect(removed).toMatchObject({ status: 'ok', value: { status: 'removed' } }) + expect(existsSync(skillPath(root))).toBe(false) +} + +async function expectManagedInstalls( + page: Page, + environmentId: string, + expected: unknown[] +): Promise { + const installs = await page.evaluate( + (id) => window.api.skills.listManagedInstalls(id), + environmentId + ) + expect(installs).toMatchObject({ status: 'ok', value: expected }) +} + +function expectCloudRequests( + fixture: RemoteSkillCloudFixture, + start: number, + expectedInstalls: number +): void { + const requests = fixture.requests.slice(start) + expect(requests.filter((request) => request.method === 'POST')).toHaveLength(expectedInstalls) + expect(requests.filter((request) => request.path === '/package.tar.gz')).toHaveLength( + expectedInstalls + ) + expect( + requests.filter((request) => request.method === 'POST').map((request) => request.body) + ).toEqual(Array.from({ length: expectedInstalls }, () => ({ installTarget: 'remote' }))) +} diff --git a/tests/e2e/ssh-skill-installation.spec.ts b/tests/e2e/ssh-skill-installation.spec.ts index 97fd8d37697..a102478fb62 100644 --- a/tests/e2e/ssh-skill-installation.spec.ts +++ b/tests/e2e/ssh-skill-installation.spec.ts @@ -10,40 +10,40 @@ import { import { connectDockerSshRelayTarget } from './helpers/docker-ssh-relay-connection' import { waitForActiveWorktree, waitForSessionReady } from './helpers/store' import { - SSH_SKILL_CLOUD_ORIGIN, - SSH_SKILL_NAME, - SSH_SKILL_PACKAGE_ID, - SSH_SKILL_VERSION_ID, - startSshSkillCloudFixture, - stopSshSkillCloudFixture, - type SshSkillCloudFixture -} from './helpers/ssh-skill-cloud-fixture' + REMOTE_SKILL_CLOUD_ORIGIN, + REMOTE_SKILL_NAME, + REMOTE_SKILL_PACKAGE_ID, + REMOTE_SKILL_VERSION_ID, + startRemoteSkillCloudFixture, + stopRemoteSkillCloudFixture, + type RemoteSkillCloudFixture +} from './helpers/remote-skill-cloud-fixture' const RUN_DOCKER_SSH = process.env.ORCA_E2E_SSH_DOCKER === '1' const REMOTE_FOLDER = '/tmp/orca-skill-folder-workspace' -let cloud: SshSkillCloudFixture | null = null +let cloud: RemoteSkillCloudFixture | null = null test.use({ orcaAppExtraEnv: { - ORCA_ARTIFACTS_API_URL: SSH_SKILL_CLOUD_ORIGIN, - ORCA_CLOUD_API_URL: SSH_SKILL_CLOUD_ORIGIN, + ORCA_ARTIFACTS_API_URL: REMOTE_SKILL_CLOUD_ORIGIN, + ORCA_CLOUD_API_URL: REMOTE_SKILL_CLOUD_ORIGIN, ORCA_CLOUD_CLIENT_ID: 'skills-e2e-client', ORCA_CLOUD_DEV_AUTH: '1', ORCA_CLOUD_ALLOW_PLAINTEXT_SESSION: '1', - ORCA_SKILL_PACKAGE_DOWNLOAD_ORIGINS: SSH_SKILL_CLOUD_ORIGIN + ORCA_SKILL_PACKAGE_DOWNLOAD_ORIGINS: REMOTE_SKILL_CLOUD_ORIGIN } }) test.beforeAll(async () => { - cloud = await startSshSkillCloudFixture() + cloud = await startRemoteSkillCloudFixture() }) test.afterAll(async () => { if (!cloud) { return } - await stopSshSkillCloudFixture(cloud) + await stopRemoteSkillCloudFixture(cloud) cloud = null }) @@ -74,7 +74,7 @@ test.describe('SSH skill installation', () => { orcaPage, target, globalDestination, - '/root/.agents/skills/ssh-e2e-skill' + '/root/.agents/skills/remote-e2e-skill' ) const globalInstalls = await orcaPage.evaluate( (environmentId) => window.api.skills.listManagedInstalls(environmentId), @@ -84,7 +84,7 @@ test.describe('SSH skill installation', () => { status: 'ok', value: [ { - name: SSH_SKILL_NAME, + name: REMOTE_SKILL_NAME, destination: { scope: 'global', executionTarget: { kind: 'ssh', connectionId: remote.targetId } @@ -97,14 +97,14 @@ test.describe('SSH skill installation', () => { orcaPage, target, globalDestination, - '/root/.agents/skills/ssh-e2e-skill' + '/root/.agents/skills/remote-e2e-skill' ) const worktreeDestination: SkillInstallDestination = { scope: 'workspace', worktreeId: remote.worktreeId } - const worktreePath = '/tmp/orca-docker-relay-perf-repo/.agents/skills/ssh-e2e-skill' + const worktreePath = '/tmp/orca-docker-relay-perf-repo/.agents/skills/remote-e2e-skill' await installAndVerify(orcaPage, target, worktreeDestination, worktreePath) await removeAndVerify(orcaPage, target, worktreeDestination, worktreePath) @@ -113,7 +113,7 @@ test.describe('SSH skill installation', () => { scope: 'workspace', folderWorkspaceId } - const folderPath = `${REMOTE_FOLDER}/.agents/skills/ssh-e2e-skill` + const folderPath = `${REMOTE_FOLDER}/.agents/skills/remote-e2e-skill` await installAndVerify(orcaPage, target, folderDestination, folderPath) await removeAndVerify(orcaPage, target, folderDestination, folderPath) @@ -136,7 +136,7 @@ test.describe('SSH skill installation', () => { }) }) -function requireCloudFixture(): SshSkillCloudFixture { +function requireCloudFixture(): RemoteSkillCloudFixture { if (!cloud) { throw new Error('skill Cloud fixture unavailable') } @@ -158,16 +158,16 @@ async function installAndVerify( }), { destination, - packageId: SSH_SKILL_PACKAGE_ID, - versionId: SSH_SKILL_VERSION_ID + packageId: REMOTE_SKILL_PACKAGE_ID, + versionId: REMOTE_SKILL_VERSION_ID } ) expect(operation, JSON.stringify(operation, null, 2)).toMatchObject({ status: 'ok', - value: { status: 'installed', name: SSH_SKILL_NAME } + value: { status: 'installed', name: REMOTE_SKILL_NAME } }) const contents = execDockerSshRelayTargetCommand(target, `cat ${remotePath}/SKILL.md`) - expect(contents).toContain('# SSH E2E') + expect(contents).toContain('# Remote E2E') } async function previewUnchanged(page: Page, destination: SkillInstallDestination): Promise { @@ -181,7 +181,7 @@ async function previewUnchanged(page: Page, destination: SkillInstallDestination }), { destination, - name: SSH_SKILL_NAME, + name: REMOTE_SKILL_NAME, packageIdentity: { packageId: fixture.archive.manifest.packageId, versionId: fixture.archive.manifest.versionId, @@ -202,7 +202,7 @@ async function removeAndVerify( ): Promise { const operation = await page.evaluate( ({ destination, name }) => window.api.skills.removeInstall({ name, destination }), - { destination, name: SSH_SKILL_NAME } + { destination, name: REMOTE_SKILL_NAME } ) expect(operation).toMatchObject({ status: 'ok', value: { status: 'removed' } }) expect(execDockerSshRelayTargetCommand(target, `test ! -e ${remotePath} && echo removed`)).toBe(