test(skills): cover paired runtime installation

This commit is contained in:
Jinwoo-H
2026-08-11 12:17:10 -04:00
parent 9e79a325bb
commit 8be6301811
6 changed files with 392 additions and 58 deletions
@@ -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()
})
})
@@ -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<unknown>
}
function isDownloadTransportFailure(response: RuntimeRpcResponse<unknown>): boolean {
function isDirectDownloadUnavailable(
response: RuntimeRpcResponse<unknown>,
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)
}
+3 -2
View File
@@ -141,7 +141,8 @@ export async function launchPairedWebClient(
export async function launchPairedElectronClient(
offer: RuntimeDesktopPairingOffer,
testInfo: TestInfo,
name: string
name: string,
options: { extraEnv?: Record<string, string> } = {}
): Promise<PairedElectronClient> {
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')
@@ -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<SshSkillCloudFixture> {
const root = await mkdtemp(join(tmpdir(), 'orca-ssh-skill-cloud-'))
export async function startRemoteSkillCloudFixture(): Promise<RemoteSkillCloudFixture> {
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<SshSkillCloudFixture>
})
await new Promise<void>((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<void> {
export async function stopRemoteSkillCloudFixture(fixture: RemoteSkillCloudFixture): Promise<void> {
await new Promise<void>((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<void> {
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,
+277
View File
@@ -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<string, string> {
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<void> {
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<string> {
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<string> {
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<string> {
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<void> {
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<void> {
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' })))
}
+26 -26
View File
@@ -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<void> {
@@ -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<void> {
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(