mirror of
https://github.com/stablyai/orca.git
synced 2026-10-16 16:03:26 +00:00
fix: install agent skills project-scoped so the PromptScript target lands
The in-app skill install/update commands hardcoded --global. The npx skills CLI cannot install the PromptScript target (the one Orca consumes) in global mode: it skips that single target, still prints a success banner, and exits 0, so the skill silently never installs for Orca. Drop --global and add -y to the install command (and drop --global from the update command). The setup terminal runs in the home dir, so a project-scoped install lands in ~/.agents/skills, which discovery already classifies as a 'home' source the feature-setup surfaces detect — no detection change needed. Also update the native-Windows update-normalization regex to match the new project-scoped update form. Fixes #4642 Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -3,6 +3,10 @@ import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { buildSkillDiscoverySources, discoverSkills } from './discovery'
|
||||
import {
|
||||
buildAgentFeatureSkillInstallCommand,
|
||||
ORCHESTRATION_SKILL_NAME
|
||||
} from '../../shared/agent-feature-install-commands'
|
||||
import type { Repo } from '../../shared/types'
|
||||
|
||||
function makeRepo(path: string, connectionId: string | null = null): Repo {
|
||||
@@ -102,6 +106,36 @@ describe('skill discovery', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('classifies a project-scoped install run from home as a home source (regression for #4642)', async () => {
|
||||
// Why: the in-app setup terminal runs in the home dir, so a non-global
|
||||
// `npx skills add ... -y` install lands in ~/.agents/skills. That must be
|
||||
// discovered as a 'home' source so the feature-setup surfaces (which filter
|
||||
// on 'home') flip to "Installed". `--global` skipped this target silently.
|
||||
const installCommand = buildAgentFeatureSkillInstallCommand([ORCHESTRATION_SKILL_NAME])
|
||||
expect(installCommand).not.toContain('--global')
|
||||
|
||||
const root = await mkdtemp(join(tmpdir(), 'orca-skills-'))
|
||||
const home = join(root, 'home')
|
||||
const skillDir = join(home, '.agents', 'skills', ORCHESTRATION_SKILL_NAME)
|
||||
await mkdir(skillDir, { recursive: true })
|
||||
await writeFile(
|
||||
join(skillDir, 'SKILL.md'),
|
||||
[
|
||||
'---',
|
||||
`name: ${ORCHESTRATION_SKILL_NAME}`,
|
||||
'description: Coordinate agents.',
|
||||
'---',
|
||||
''
|
||||
].join('\n')
|
||||
)
|
||||
|
||||
const result = await discoverSkills({ homeDir: home, cwd: home, repos: [] })
|
||||
|
||||
expect(result.skills).toMatchObject([
|
||||
{ name: ORCHESTRATION_SKILL_NAME, sourceKind: 'home', directoryPath: skillDir }
|
||||
])
|
||||
})
|
||||
|
||||
it('does not loop through recursive symlinked skill directories', async () => {
|
||||
const root = await mkdtemp(join(tmpdir(), 'orca-skills-'))
|
||||
const home = join(root, 'home')
|
||||
|
||||
@@ -49,7 +49,7 @@ describe('onboarding feature setup telemetry validation', () => {
|
||||
linear_tickets: false,
|
||||
orchestration: true,
|
||||
selected_count: 2,
|
||||
command: 'npx skills add https://github.com/stablyai/orca --global'
|
||||
command: 'npx skills add https://github.com/stablyai/orca --skill orchestration -y'
|
||||
} as never).ok
|
||||
).toBe(false)
|
||||
expect(
|
||||
|
||||
@@ -119,7 +119,7 @@ describe('onboarding feature setup runner', () => {
|
||||
|
||||
expect(text).toBe(ALL_SKILL_INSTALL_COMMAND)
|
||||
expect(text).toBe(
|
||||
'npx skills add https://github.com/stablyai/orca --skill orca-cli computer-use orchestration orca-linear --global'
|
||||
'npx skills add https://github.com/stablyai/orca --skill orca-cli computer-use orchestration orca-linear -y'
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { AgentSkillSetupPanel } from './AgentSkillSetupPanel'
|
||||
import { TooltipProvider } from '../ui/tooltip'
|
||||
|
||||
const INSTALL_COMMAND = 'npx skills add https://github.com/stablyai/orca --skill orca-cli --global'
|
||||
const UPDATE_COMMAND = 'npx skills update orca-cli --global'
|
||||
const INSTALL_COMMAND = 'npx skills add https://github.com/stablyai/orca --skill orca-cli -y'
|
||||
const UPDATE_COMMAND = 'npx skills update orca-cli'
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
clipboardWrite: vi.fn(),
|
||||
|
||||
@@ -16,8 +16,8 @@ vi.mock('./AgentSkillSetupPanel', () => ({
|
||||
describe('BrowserUseSkillStep', () => {
|
||||
it('forwards a single-skill installed command even when setup installs a bundle', () => {
|
||||
const bundleInstallCommand =
|
||||
'npx skills add https://github.com/stablyai/orca --skill orca-cli orchestration --global'
|
||||
const updateCommand = 'npx skills update orca-cli --global'
|
||||
'npx skills add https://github.com/stablyai/orca --skill orca-cli orchestration -y'
|
||||
const updateCommand = 'npx skills update orca-cli'
|
||||
|
||||
renderToStaticMarkup(
|
||||
<BrowserUseSkillStep
|
||||
|
||||
@@ -92,7 +92,7 @@ describe('CliSection project runtime defaults', () => {
|
||||
expect(capturedPanel.props?.command).toContain("wsl.exe -d 'Ubuntu' -- sh -c")
|
||||
expect(capturedPanel.props?.command).toContain('npx skills add')
|
||||
expect(capturedPanel.props?.installedCommand).toContain("wsl.exe -d 'Ubuntu' -- sh -c")
|
||||
expect(capturedPanel.props?.installedCommand).toContain('npx skills update orca-cli --global')
|
||||
expect(capturedPanel.props?.installedCommand).toContain('npx skills update orca-cli')
|
||||
expect(getWslInstallStatus).toHaveBeenCalledWith({ distro: 'Ubuntu' })
|
||||
expect(getWslInstallStatus).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
|
||||
describe('CliSkillRuntimeSetup runtime helpers', () => {
|
||||
it('wraps WSL skill installs in the selected distro login shell', () => {
|
||||
const command = buildSkillInstallCommandForRuntime('npx skills add orchestration --global', {
|
||||
const command = buildSkillInstallCommandForRuntime('npx skills add orchestration -y', {
|
||||
runtime: 'wsl',
|
||||
wslDistro: 'Ubuntu',
|
||||
label: 'WSL Ubuntu'
|
||||
@@ -18,11 +18,11 @@ describe('CliSkillRuntimeSetup runtime helpers', () => {
|
||||
|
||||
expect(command).toContain("wsl.exe -d 'Ubuntu' -- sh -c")
|
||||
expect(command).toContain('getent passwd')
|
||||
expect(command).toContain('npx skills add orchestration --global')
|
||||
expect(command).toContain('npx skills add orchestration -y')
|
||||
})
|
||||
|
||||
it('wraps WSL skill updates with the same selected distro login shell', () => {
|
||||
const command = buildSkillCommandForRuntime('npx skills update orchestration --global', {
|
||||
const command = buildSkillCommandForRuntime('npx skills update orchestration', {
|
||||
runtime: 'wsl',
|
||||
wslDistro: 'Fedora Remix',
|
||||
label: 'WSL Fedora Remix'
|
||||
@@ -30,13 +30,13 @@ describe('CliSkillRuntimeSetup runtime helpers', () => {
|
||||
|
||||
expect(command).toContain("wsl.exe -d 'Fedora Remix' -- sh -c")
|
||||
expect(command).toContain('getent passwd')
|
||||
expect(command).toContain('npx skills update orchestration --global')
|
||||
expect(command).toContain('npx skills update orchestration')
|
||||
})
|
||||
|
||||
it('reinstalls Windows-host skill updates through the add path', () => {
|
||||
expect(
|
||||
buildSkillCommandForRuntime(
|
||||
'npx skills update orchestration --global',
|
||||
'npx skills update orchestration',
|
||||
{
|
||||
runtime: 'host',
|
||||
label: 'Windows'
|
||||
@@ -47,22 +47,22 @@ describe('CliSkillRuntimeSetup runtime helpers', () => {
|
||||
})
|
||||
|
||||
it('treats missing runtime as a Windows host fallback for skill updates', () => {
|
||||
expect(
|
||||
buildSkillCommandForRuntime('npx skills update orca-cli --global', undefined, 'win32')
|
||||
).toBe(buildAgentFeatureSkillInstallCommand(['orca-cli']))
|
||||
expect(buildSkillCommandForRuntime('npx skills update orca-cli', undefined, 'win32')).toBe(
|
||||
buildAgentFeatureSkillInstallCommand(['orca-cli'])
|
||||
)
|
||||
})
|
||||
|
||||
it('keeps non-Windows host skill updates on the update path', () => {
|
||||
expect(
|
||||
buildSkillCommandForRuntime(
|
||||
'npx skills update orchestration --global',
|
||||
'npx skills update orchestration',
|
||||
{
|
||||
runtime: 'host',
|
||||
label: 'This device'
|
||||
},
|
||||
'linux'
|
||||
)
|
||||
).toBe('npx skills update orchestration --global')
|
||||
).toBe('npx skills update orchestration')
|
||||
})
|
||||
|
||||
it('preserves the selected WSL distro for skill discovery', () => {
|
||||
|
||||
@@ -100,7 +100,7 @@ function normalizeWindowsSkillUpdateCommand(
|
||||
}
|
||||
|
||||
const trimmedCommand = command.trim()
|
||||
const updateMatch = /^npx\s+skills\s+update\s+([A-Za-z0-9_-]+)\s+--global$/i.exec(trimmedCommand)
|
||||
const updateMatch = /^npx\s+skills\s+update\s+([A-Za-z0-9_-]+)$/i.exec(trimmedCommand)
|
||||
if (!updateMatch) {
|
||||
return command
|
||||
}
|
||||
|
||||
@@ -7,8 +7,9 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { getOrchestrationUsageExamples } from '@/lib/orchestration-usage-examples'
|
||||
import { OrchestrationPane } from './OrchestrationPane'
|
||||
|
||||
const INSTALL_COMMAND =
|
||||
'npx skills add https://github.com/stablyai/orca --skill orchestration --global'
|
||||
const INSTALL_COMMAND = 'npx skills add https://github.com/stablyai/orca --skill orchestration -y'
|
||||
// Why: on the win32 host this pane runs against, the update command is
|
||||
// normalized back into the install command, so both match the install form.
|
||||
const UPDATE_COMMAND = INSTALL_COMMAND
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
|
||||
@@ -347,8 +347,7 @@ describe('LinearAgentSkillSetupPrompt', () => {
|
||||
expect(document.body.textContent).toContain("wsl.exe -d 'Fedora' -- bash -lc 'npx skills add")
|
||||
expect(mocks.panelProps.at(-1)).toEqual(
|
||||
expect.objectContaining({
|
||||
installedCommand:
|
||||
"wsl.exe -d 'Fedora' -- bash -lc 'npx skills update orca-linear --global'",
|
||||
installedCommand: "wsl.exe -d 'Fedora' -- bash -lc 'npx skills update orca-linear'",
|
||||
terminalShellOverride: 'powershell.exe',
|
||||
getPrerequisiteStatus: expect.any(Function)
|
||||
})
|
||||
|
||||
+3
-3
@@ -156,7 +156,7 @@ describe('LinearAgentSkillSetupPrompt update command', () => {
|
||||
await renderPrompt()
|
||||
|
||||
expect(mocks.panelProps.at(-1)).toEqual(
|
||||
expect.objectContaining({ installedCommand: 'npx skills update orca-linear --global' })
|
||||
expect.objectContaining({ installedCommand: 'npx skills update orca-linear' })
|
||||
)
|
||||
})
|
||||
|
||||
@@ -166,7 +166,7 @@ describe('LinearAgentSkillSetupPrompt update command', () => {
|
||||
await renderPrompt()
|
||||
|
||||
expect(mocks.panelProps.at(-1)).toEqual(
|
||||
expect.objectContaining({ installedCommand: 'npx skills update linear-tickets --global' })
|
||||
expect.objectContaining({ installedCommand: 'npx skills update linear-tickets' })
|
||||
)
|
||||
})
|
||||
|
||||
@@ -176,7 +176,7 @@ describe('LinearAgentSkillSetupPrompt update command', () => {
|
||||
await renderPrompt()
|
||||
|
||||
expect(mocks.panelProps.at(-1)).toEqual(
|
||||
expect.objectContaining({ installedCommand: 'npx skills update orca-linear --global' })
|
||||
expect.objectContaining({ installedCommand: 'npx skills update orca-linear' })
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -115,6 +115,28 @@ describe('hasInstalledAgentSkill', () => {
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
it('detects a project-scoped install that lands in the home dir (regression for #4642)', () => {
|
||||
// Why: the setup terminal runs in the home dir, so a non-global install
|
||||
// writes to ~/.agents/skills with sourceKind 'home' — the feature-setup
|
||||
// surfaces' filter must still report it installed.
|
||||
expect(
|
||||
hasInstalledAgentSkill(
|
||||
[
|
||||
skill({
|
||||
name: 'orchestration',
|
||||
sourceKind: 'home',
|
||||
sourceLabel: 'Agent skills home',
|
||||
rootPath: '/Users/test/.agents/skills',
|
||||
directoryPath: '/Users/test/.agents/skills/orchestration',
|
||||
skillFilePath: '/Users/test/.agents/skills/orchestration/SKILL.md'
|
||||
})
|
||||
],
|
||||
'orchestration',
|
||||
{ sourceKinds: GLOBAL_AGENT_SKILL_SOURCE_KINDS }
|
||||
)
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
it('matches installed skills by any accepted name', () => {
|
||||
expect(
|
||||
hasInstalledAgentSkillNamed(
|
||||
|
||||
@@ -11,25 +11,39 @@ import {
|
||||
} from './agent-feature-install-commands'
|
||||
|
||||
describe('agent feature skill commands', () => {
|
||||
// Why: `--global` silently skips the PromptScript target Orca consumes while
|
||||
// exiting 0, so the install/update commands must stay project-scoped.
|
||||
it('builds project-scoped single-skill install commands', () => {
|
||||
expect(buildAgentFeatureSkillInstallCommand(['orchestration'])).toBe(
|
||||
'npx skills add https://github.com/stablyai/orca --skill orchestration -y'
|
||||
)
|
||||
expect(buildAgentFeatureSkillInstallCommand(['orca-cli', 'orchestration'])).toBe(
|
||||
'npx skills add https://github.com/stablyai/orca --skill orca-cli orchestration -y'
|
||||
)
|
||||
})
|
||||
|
||||
it('never emits the --global flag that masks the PromptScript failure', () => {
|
||||
expect(buildAgentFeatureSkillInstallCommand(['orchestration'])).not.toContain('--global')
|
||||
expect(buildAgentFeatureSkillUpdateCommand('orchestration')).not.toContain('--global')
|
||||
})
|
||||
|
||||
it('builds single-skill update commands', () => {
|
||||
expect(buildAgentFeatureSkillUpdateCommand('orchestration')).toBe(
|
||||
'npx skills update orchestration --global'
|
||||
'npx skills update orchestration'
|
||||
)
|
||||
})
|
||||
|
||||
it('trims and rejects blank update skill names', () => {
|
||||
expect(buildAgentFeatureSkillUpdateCommand(' orca-cli ')).toBe(
|
||||
'npx skills update orca-cli --global'
|
||||
)
|
||||
expect(buildAgentFeatureSkillUpdateCommand(' orca-cli ')).toBe('npx skills update orca-cli')
|
||||
expect(() => buildAgentFeatureSkillUpdateCommand(' ')).toThrow('A skill name is required.')
|
||||
})
|
||||
|
||||
it('exports single-skill update constants without changing install bundles', () => {
|
||||
expect(ORCA_CLI_SKILL_UPDATE_COMMAND).toBe('npx skills update orca-cli --global')
|
||||
expect(COMPUTER_USE_SKILL_UPDATE_COMMAND).toBe('npx skills update computer-use --global')
|
||||
expect(ORCHESTRATION_SKILL_UPDATE_COMMAND).toBe('npx skills update orchestration --global')
|
||||
expect(ORCA_LINEAR_SKILL_UPDATE_COMMAND).toBe('npx skills update orca-linear --global')
|
||||
expect(LINEAR_TICKETS_SKILL_UPDATE_COMMAND).toBe('npx skills update linear-tickets --global')
|
||||
expect(ORCA_CLI_SKILL_UPDATE_COMMAND).toBe('npx skills update orca-cli')
|
||||
expect(COMPUTER_USE_SKILL_UPDATE_COMMAND).toBe('npx skills update computer-use')
|
||||
expect(ORCHESTRATION_SKILL_UPDATE_COMMAND).toBe('npx skills update orchestration')
|
||||
expect(ORCA_LINEAR_SKILL_UPDATE_COMMAND).toBe('npx skills update orca-linear')
|
||||
expect(LINEAR_TICKETS_SKILL_UPDATE_COMMAND).toBe('npx skills update linear-tickets')
|
||||
expect(ORCA_CLI_ORCHESTRATION_SKILL_INSTALL_COMMAND).toBe(
|
||||
buildAgentFeatureSkillInstallCommand(['orca-cli', 'orchestration'])
|
||||
)
|
||||
|
||||
@@ -11,7 +11,12 @@ export function buildAgentFeatureSkillInstallCommand(skillNames: readonly string
|
||||
if (skillNames.length === 0) {
|
||||
throw new Error('At least one skill name is required.')
|
||||
}
|
||||
return `npx skills add ${ORCA_SKILLS_REPOSITORY_URL} --skill ${skillNames.join(' ')} --global`
|
||||
// Why: `--global` skips the PromptScript target (the one Orca consumes) yet
|
||||
// still exits 0, so installs silently never land. Project-scoped installs to
|
||||
// <cwd>/.agents/skills succeed for every target; the setup terminal runs in
|
||||
// the home dir, so this writes to ~/.agents/skills (a scanned 'home' source).
|
||||
// `-y` keeps the auto-run terminal non-interactive.
|
||||
return `npx skills add ${ORCA_SKILLS_REPOSITORY_URL} --skill ${skillNames.join(' ')} -y`
|
||||
}
|
||||
|
||||
export function buildAgentFeatureSkillUpdateCommand(skillName: string): string {
|
||||
@@ -19,7 +24,9 @@ export function buildAgentFeatureSkillUpdateCommand(skillName: string): string {
|
||||
if (!trimmedSkillName) {
|
||||
throw new Error('A skill name is required.')
|
||||
}
|
||||
return `npx skills update ${trimmedSkillName} --global`
|
||||
// Why: must stay project-scoped to match the install above; a `--global`
|
||||
// update would target an install that never happened.
|
||||
return `npx skills update ${trimmedSkillName}`
|
||||
}
|
||||
|
||||
export const ORCA_CLI_SKILL_INSTALL_COMMAND = buildAgentFeatureSkillInstallCommand([
|
||||
|
||||
Reference in New Issue
Block a user