mirror of
https://github.com/stablyai/orca.git
synced 2026-10-23 08:03:39 +00:00
fix(skills): honor HERMES_HOME when discovering Hermes skills
A relocated Hermes profile (hermes -p <profile>) moves the whole tree, so the default ~/.hermes path discovered nothing.
This commit is contained in:
@@ -53,6 +53,7 @@ beforeEach(() => {
|
||||
|
||||
afterEach(() => {
|
||||
unavailableRootPath = null
|
||||
vi.unstubAllEnvs()
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
@@ -509,6 +510,38 @@ describe('skill discovery', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('discovers Hermes Skills under a relocated HERMES_HOME profile', async () => {
|
||||
const root = await mkdtemp(join(tmpdir(), 'orca-skills-'))
|
||||
const home = join(root, 'home')
|
||||
const hermesHome = join(root, 'profiles', 'coder')
|
||||
const skillDir = join(hermesHome, 'skills', 'research')
|
||||
await mkdir(skillDir, { recursive: true })
|
||||
await writeFile(
|
||||
join(skillDir, 'SKILL.md'),
|
||||
['---', 'name: profile-research', 'description: Research sources.', '---', ''].join('\n')
|
||||
)
|
||||
vi.stubEnv('HERMES_HOME', hermesHome)
|
||||
|
||||
const result = await discoverSkills({
|
||||
homeDir: home,
|
||||
cwd: join(root, 'missing-cwd')
|
||||
})
|
||||
|
||||
expect(result.skills).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
name: 'profile-research',
|
||||
sourceLabel: 'Hermes home',
|
||||
directoryPath: skillDir
|
||||
})
|
||||
])
|
||||
)
|
||||
expect(result.sources.find((source) => source.id === 'home-hermes')).toMatchObject({
|
||||
path: join(hermesHome, 'skills'),
|
||||
exists: true
|
||||
})
|
||||
})
|
||||
|
||||
it('discovers worktree .agents skill symlinks from the requested cwd', async () => {
|
||||
const root = await mkdtemp(join(tmpdir(), 'orca-skills-'))
|
||||
const home = join(root, 'home')
|
||||
|
||||
@@ -11,7 +11,10 @@ import type { AgentType } from '../../shared/agent-status-types'
|
||||
import type { Repo } from '../../shared/repo-types'
|
||||
import { getRepoExecutionHostId, LOCAL_EXECUTION_HOST_ID } from '../../shared/execution-host'
|
||||
import type { SkillProviderRootOverrides } from './skill-provider-destinations'
|
||||
import { resolveEnvironmentSkillProviderRoots } from './skill-provider-runtime-roots'
|
||||
import {
|
||||
resolveEnvironmentHermesSkillsRoot,
|
||||
resolveEnvironmentSkillProviderRoots
|
||||
} from './skill-provider-runtime-roots'
|
||||
|
||||
export type SkillScanRoot = Omit<SkillDiscoverySource, 'exists' | 'skippedReason'>
|
||||
type SkillDiscoveryPathApi = Pick<typeof posix, 'basename' | 'join'>
|
||||
@@ -77,6 +80,12 @@ export function buildSkillDiscoverySources(
|
||||
const cwd = args.cwd ?? process.cwd()
|
||||
const providerRootOverrides =
|
||||
args.providerRootOverrides ?? (args.pathApi ? {} : resolveEnvironmentSkillProviderRoots())
|
||||
// Why: HERMES_HOME moves the whole profile tree, so the default home path
|
||||
// finds nothing for `hermes -p <profile>`. Only this process's own host can
|
||||
// read it — a custom pathApi means the home belongs to another host.
|
||||
const hermesSkillsRoot =
|
||||
(args.pathApi ? null : resolveEnvironmentHermesSkillsRoot()) ??
|
||||
pathApi.join(home, '.hermes', 'skills')
|
||||
const roots: SkillScanRoot[] = [
|
||||
source(
|
||||
'home-codex',
|
||||
@@ -144,14 +153,7 @@ export function buildSkillDiscoverySources(
|
||||
['agent-skills'],
|
||||
'omp'
|
||||
),
|
||||
source(
|
||||
'home-hermes',
|
||||
'Hermes home',
|
||||
pathApi.join(home, '.hermes', 'skills'),
|
||||
'home',
|
||||
['agent-skills'],
|
||||
'hermes'
|
||||
),
|
||||
source('home-hermes', 'Hermes home', hermesSkillsRoot, 'home', ['agent-skills'], 'hermes'),
|
||||
source(
|
||||
'home-prime-agent',
|
||||
'Prime Agent home',
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { join } from 'node:path'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
resolveEnvironmentHermesSkillsRoot,
|
||||
resolveEnvironmentSkillProviderRoots,
|
||||
resolveWslGrokSkillProviderRoot,
|
||||
withClaudeSkillProviderRoot
|
||||
@@ -30,6 +31,14 @@ describe('skill provider runtime roots', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('maps a relocated HERMES_HOME to its skill root and ignores relative ones', () => {
|
||||
expect(resolveEnvironmentHermesSkillsRoot({ HERMES_HOME: join('/srv', 'hermes') })).toBe(
|
||||
join('/srv', 'hermes', 'skills')
|
||||
)
|
||||
expect(resolveEnvironmentHermesSkillsRoot({ HERMES_HOME: '../hermes' })).toBeNull()
|
||||
expect(resolveEnvironmentHermesSkillsRoot({})).toBeNull()
|
||||
})
|
||||
|
||||
it('maps the WSL login shell GROK_HOME to a host-readable skill root', async () => {
|
||||
await expect(
|
||||
resolveWslGrokSkillProviderRoot('Ubuntu-24.04', async () => '/srv/grok\n')
|
||||
|
||||
@@ -37,6 +37,16 @@ export function resolveEnvironmentSkillProviderRoots(
|
||||
}
|
||||
}
|
||||
|
||||
// Why: `HERMES_HOME` relocates a whole Hermes profile tree (`hermes -p coder`),
|
||||
// and the rest of Orca already treats it as authoritative. Hermes is not an
|
||||
// install provider, so it stays out of `SkillProviderRootOverrides`.
|
||||
export function resolveEnvironmentHermesSkillsRoot(
|
||||
env: NodeJS.ProcessEnv = process.env
|
||||
): string | null {
|
||||
const hermesHome = normalizedRoot(env.HERMES_HOME)
|
||||
return hermesHome ? join(hermesHome, 'skills') : null
|
||||
}
|
||||
|
||||
export function withClaudeSkillProviderRoot(
|
||||
roots: SkillProviderRootOverrides,
|
||||
claudeConfigDirectory: string | null | undefined
|
||||
|
||||
Reference in New Issue
Block a user