mirror of
https://github.com/stablyai/orca.git
synced 2026-10-22 00:03:47 +00:00
fix(skills): verify WSL installs with guest paths
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { SkillInstallResult } from '../../shared/skill-install-contract'
|
||||
import type { SkillDiscoveryResult } from '../../shared/skills'
|
||||
|
||||
const { discoverSkillsInWslMock } = vi.hoisted(() => ({
|
||||
discoverSkillsInWslMock: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('./skill-discovery-wsl', () => ({
|
||||
discoverSkillsInWsl: discoverSkillsInWslMock
|
||||
}))
|
||||
|
||||
import { verifySkillInstallDiscovery } from './skill-install-discovery-verification'
|
||||
|
||||
function installResult(paths: string[]): SkillInstallResult {
|
||||
return {
|
||||
operationId: 'operation-wsl',
|
||||
status: 'installed',
|
||||
name: 'review',
|
||||
packageDigest: 'a'.repeat(64),
|
||||
canonicalPath: paths[0],
|
||||
placements: paths.map((path, index) => ({
|
||||
provider: index === 0 ? 'agent-skills' : 'claude',
|
||||
path,
|
||||
topology: index === 0 ? 'canonical-copy' : 'provider-alias',
|
||||
status: 'installed'
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
function discovery(rootPaths: string[]): SkillDiscoveryResult {
|
||||
return {
|
||||
skills: [
|
||||
{
|
||||
id: 'review',
|
||||
name: 'review',
|
||||
description: 'Review',
|
||||
providers: ['agent-skills', 'claude'],
|
||||
sourceKind: 'home',
|
||||
sourceLabel: 'Agent skills home',
|
||||
rootPath: rootPaths[0],
|
||||
rootPaths,
|
||||
directoryPath: `${rootPaths[0]}/review`,
|
||||
skillFilePath: `${rootPaths[0]}/review/SKILL.md`,
|
||||
installed: true,
|
||||
fileCount: 1,
|
||||
updatedAt: null
|
||||
}
|
||||
],
|
||||
sources: [],
|
||||
scannedAt: 1
|
||||
}
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
discoverSkillsInWslMock.mockReset()
|
||||
})
|
||||
|
||||
describe('WSL install discovery verification', () => {
|
||||
it('matches UNC placements against distro-native discovery paths', async () => {
|
||||
discoverSkillsInWslMock.mockResolvedValue(
|
||||
discovery(['/home/alice/.agents/skills', '/home/alice/.claude/skills'])
|
||||
)
|
||||
const result = await verifySkillInstallDiscovery({
|
||||
result: installResult([
|
||||
'\\\\wsl.localhost\\Ubuntu\\home\\alice\\.agents\\skills\\review',
|
||||
'\\\\wsl$\\ubuntu\\home\\alice\\.claude\\skills\\review'
|
||||
]),
|
||||
scope: 'global',
|
||||
homeDirectory: '\\\\wsl.localhost\\Ubuntu\\home\\alice',
|
||||
wslDistro: 'Ubuntu'
|
||||
})
|
||||
|
||||
expect(result.status).toBe('installed')
|
||||
expect(result.placements).not.toContainEqual(expect.objectContaining({ status: 'failed' }))
|
||||
expect(discoverSkillsInWslMock).toHaveBeenCalledWith({
|
||||
distro: 'Ubuntu',
|
||||
homeDir: '/home/alice',
|
||||
cwd: '/home/alice'
|
||||
})
|
||||
})
|
||||
|
||||
it('normalizes Windows-backed workspace paths to drvfs paths', async () => {
|
||||
discoverSkillsInWslMock.mockResolvedValue(discovery(['/mnt/c/work/repo/.agents/skills']))
|
||||
const result = await verifySkillInstallDiscovery({
|
||||
result: installResult(['C:\\work\\repo\\.agents\\skills\\review']),
|
||||
scope: 'workspace',
|
||||
homeDirectory: 'C:\\Users\\alice',
|
||||
workspaceDirectory: 'C:\\work\\repo',
|
||||
wslDistro: 'Ubuntu'
|
||||
})
|
||||
|
||||
expect(result.status).toBe('installed')
|
||||
expect(discoverSkillsInWslMock).toHaveBeenCalledWith({
|
||||
distro: 'Ubuntu',
|
||||
homeDir: '/mnt/c/Users/alice',
|
||||
cwd: '/mnt/c/work/repo'
|
||||
})
|
||||
})
|
||||
|
||||
it('fails verification without scanning a mismatched UNC distro', async () => {
|
||||
const result = await verifySkillInstallDiscovery({
|
||||
result: installResult(['\\\\wsl.localhost\\Debian\\home\\alice\\.agents\\skills\\review']),
|
||||
scope: 'global',
|
||||
homeDirectory: '\\\\wsl.localhost\\Debian\\home\\alice',
|
||||
wslDistro: 'Ubuntu'
|
||||
})
|
||||
|
||||
expect(result).toMatchObject({
|
||||
status: 'failed',
|
||||
errorCategory: 'skill-discovery-verification-failed'
|
||||
})
|
||||
expect(discoverSkillsInWslMock).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -1,14 +1,39 @@
|
||||
import { dirname, posix, resolve } from 'node:path'
|
||||
import type { SkillInstallResult, SkillPlacementResult } from '../../shared/skill-install-contract'
|
||||
import type { SkillDiscoveryResult } from '../../shared/skills'
|
||||
import { parseWslUncPath } from '../../shared/wsl-paths'
|
||||
import { discoverSkills } from './discovery'
|
||||
import { discoverSkillsInWsl } from './skill-discovery-wsl'
|
||||
|
||||
function toWslDiscoveryPath(path: string, distro: string): string {
|
||||
if (path.includes('\0')) {
|
||||
throw new Error('skill-install-wsl-path-invalid')
|
||||
}
|
||||
const parsed = parseWslUncPath(path)
|
||||
if (parsed) {
|
||||
if (parsed.distro.toLocaleLowerCase('en-US') !== distro.toLocaleLowerCase('en-US')) {
|
||||
throw new Error('skill-install-wsl-distro-mismatch')
|
||||
}
|
||||
return posix.normalize(parsed.linuxPath)
|
||||
}
|
||||
const slashed = path.replace(/\\/g, '/')
|
||||
const drive = slashed.match(/^([A-Za-z]):\/(.*)$/)
|
||||
if (drive) {
|
||||
return posix.join('/mnt', drive[1].toLocaleLowerCase('en-US'), drive[2])
|
||||
}
|
||||
if (!posix.isAbsolute(slashed)) {
|
||||
throw new Error('skill-install-wsl-path-invalid')
|
||||
}
|
||||
return posix.normalize(slashed)
|
||||
}
|
||||
|
||||
function normalizedPath(path: string, wslDistro?: string): string {
|
||||
const normalized = wslDistro ? path.replace(/\/+$/, '') : resolve(path)
|
||||
const normalized = wslDistro ? toWslDiscoveryPath(path, wslDistro) : resolve(path)
|
||||
const withoutTrailingSlash =
|
||||
wslDistro && normalized !== '/' ? normalized.replace(/\/+$/, '') : normalized
|
||||
return !wslDistro && process.platform === 'win32'
|
||||
? normalized.toLocaleLowerCase('en-US')
|
||||
: normalized
|
||||
? withoutTrailingSlash.toLocaleLowerCase('en-US')
|
||||
: withoutTrailingSlash
|
||||
}
|
||||
|
||||
function placementIsDiscovered(
|
||||
@@ -19,7 +44,7 @@ function placementIsDiscovered(
|
||||
): boolean {
|
||||
const placementPath = normalizedPath(placement.path, wslDistro)
|
||||
const placementRoot = normalizedPath(
|
||||
wslDistro ? posix.dirname(placement.path) : dirname(placement.path),
|
||||
wslDistro ? posix.dirname(placementPath) : dirname(placement.path),
|
||||
wslDistro
|
||||
)
|
||||
return discovery.skills.some((skill) => {
|
||||
@@ -44,8 +69,8 @@ async function discoverInstalledSkill(input: {
|
||||
if (input.wslDistro) {
|
||||
return discoverSkillsInWsl({
|
||||
distro: input.wslDistro,
|
||||
homeDir: input.homeDirectory,
|
||||
cwd: input.workspaceDirectory ?? input.homeDirectory
|
||||
homeDir: toWslDiscoveryPath(input.homeDirectory, input.wslDistro),
|
||||
cwd: toWslDiscoveryPath(input.workspaceDirectory ?? input.homeDirectory, input.wslDistro)
|
||||
})
|
||||
}
|
||||
return discoverSkills({
|
||||
|
||||
Reference in New Issue
Block a user