Update installed Orca skills without reinstalling (#5755)
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
parent
f80704cc60
commit
f1384597f7
|
|
@ -1,5 +1,8 @@
|
|||
import type { JSX } from 'react'
|
||||
import { ORCA_CLI_SKILL_INSTALL_COMMAND } from '@/lib/agent-feature-install-commands'
|
||||
import {
|
||||
ORCA_CLI_SKILL_INSTALL_COMMAND,
|
||||
ORCA_CLI_SKILL_UPDATE_COMMAND
|
||||
} from '@/lib/agent-feature-install-commands'
|
||||
import {
|
||||
AGENT_SKILL_CLI_PREREQUISITE_NOTICE,
|
||||
ensureOrcaCliAvailableForAgentSkillTerminal
|
||||
|
|
@ -9,7 +12,7 @@ import type { InstalledAgentSkillState } from '@/hooks/useInstalledAgentSkills'
|
|||
import { useActiveProjectSkillRuntime } from '@/hooks/useActiveProjectSkillRuntime'
|
||||
import { AgentSkillSetupPanel } from '@/components/settings/AgentSkillSetupPanel'
|
||||
import {
|
||||
buildSkillInstallCommandForRuntime,
|
||||
buildSkillCommandForRuntime,
|
||||
ensureWslCliAvailableForAgentSkillTerminal,
|
||||
getWslCliDistroRequest
|
||||
} from '@/components/settings/CliSkillRuntimeSetup'
|
||||
|
|
@ -25,11 +28,12 @@ export function BrowserUseSkillSetupCard(props: {
|
|||
const activeSkillRuntime = useActiveProjectSkillRuntime()
|
||||
const installCommand =
|
||||
activeSkillRuntime.agentRuntime && !activeSkillRuntime.installDisabledReason
|
||||
? buildSkillInstallCommandForRuntime(
|
||||
ORCA_CLI_SKILL_INSTALL_COMMAND,
|
||||
activeSkillRuntime.agentRuntime
|
||||
)
|
||||
? buildSkillCommandForRuntime(ORCA_CLI_SKILL_INSTALL_COMMAND, activeSkillRuntime.agentRuntime)
|
||||
: ORCA_CLI_SKILL_INSTALL_COMMAND
|
||||
const updateCommand =
|
||||
activeSkillRuntime.agentRuntime && !activeSkillRuntime.installDisabledReason
|
||||
? buildSkillCommandForRuntime(ORCA_CLI_SKILL_UPDATE_COMMAND, activeSkillRuntime.agentRuntime)
|
||||
: ORCA_CLI_SKILL_UPDATE_COMMAND
|
||||
|
||||
const handleBeforeOpenTerminal = async (): Promise<void> => {
|
||||
useAppStore.getState().recordFeatureInteraction('agent-browser-setup')
|
||||
|
|
@ -51,6 +55,7 @@ export function BrowserUseSkillSetupCard(props: {
|
|||
"Enables agents to navigate and verify pages in Orca's browser."
|
||||
)}
|
||||
command={installCommand}
|
||||
installedCommand={updateCommand}
|
||||
terminalTitle="Browser Use setup"
|
||||
terminalAriaLabel="Browser Use skill install terminal"
|
||||
terminalWorktreeId="feature-wall-browser-use-skill-terminal"
|
||||
|
|
|
|||
|
|
@ -13,7 +13,10 @@ import {
|
|||
AGENT_SKILL_CLI_PREREQUISITE_NOTICE,
|
||||
ensureOrcaCliAvailableForAgentSkillTerminal
|
||||
} from '@/lib/agent-skill-cli-prerequisite'
|
||||
import { ORCHESTRATION_SKILL_INSTALL_COMMAND } from '@/lib/orchestration-install-command'
|
||||
import {
|
||||
ORCHESTRATION_SKILL_INSTALL_COMMAND,
|
||||
ORCHESTRATION_SKILL_UPDATE_COMMAND
|
||||
} from '@/lib/orchestration-install-command'
|
||||
import {
|
||||
GLOBAL_AGENT_SKILL_SOURCE_KINDS,
|
||||
useInstalledAgentSkill
|
||||
|
|
@ -21,7 +24,7 @@ import {
|
|||
import { useActiveProjectSkillRuntime } from '@/hooks/useActiveProjectSkillRuntime'
|
||||
import { useAppStore } from '@/store'
|
||||
import {
|
||||
buildSkillInstallCommandForRuntime,
|
||||
buildSkillCommandForRuntime,
|
||||
ensureWslCliAvailableForAgentSkillTerminal,
|
||||
getWslCliDistroRequest
|
||||
} from '@/components/settings/CliSkillRuntimeSetup'
|
||||
|
|
@ -41,11 +44,18 @@ export function FloatingTerminalOrchestrationDialog({
|
|||
const activeSkillRuntime = useActiveProjectSkillRuntime()
|
||||
const installCommand =
|
||||
activeSkillRuntime.agentRuntime && !activeSkillRuntime.installDisabledReason
|
||||
? buildSkillInstallCommandForRuntime(
|
||||
? buildSkillCommandForRuntime(
|
||||
ORCHESTRATION_SKILL_INSTALL_COMMAND,
|
||||
activeSkillRuntime.agentRuntime
|
||||
)
|
||||
: ORCHESTRATION_SKILL_INSTALL_COMMAND
|
||||
const updateCommand =
|
||||
activeSkillRuntime.agentRuntime && !activeSkillRuntime.installDisabledReason
|
||||
? buildSkillCommandForRuntime(
|
||||
ORCHESTRATION_SKILL_UPDATE_COMMAND,
|
||||
activeSkillRuntime.agentRuntime
|
||||
)
|
||||
: ORCHESTRATION_SKILL_UPDATE_COMMAND
|
||||
const {
|
||||
installed: orchestrationSkillDetected,
|
||||
loading: orchestrationSkillLoading,
|
||||
|
|
@ -119,6 +129,7 @@ export function FloatingTerminalOrchestrationDialog({
|
|||
'Enables agents to hand off context and coordinate work through Orca.'
|
||||
)}
|
||||
command={installCommand}
|
||||
installedCommand={updateCommand}
|
||||
terminalTitle="Orchestration setup"
|
||||
terminalAriaLabel="Orchestration skill install terminal"
|
||||
terminalWorktreeId="floating-terminal-orchestration-skill-terminal"
|
||||
|
|
|
|||
|
|
@ -1,24 +1,64 @@
|
|||
// @vitest-environment happy-dom
|
||||
|
||||
import { act, type ComponentProps } from 'react'
|
||||
import { createRoot, type Root } from 'react-dom/client'
|
||||
import { renderToStaticMarkup } from 'react-dom/server'
|
||||
import type { ComponentProps } from 'react'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
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 mocks = vi.hoisted(() => ({
|
||||
clipboardWrite: vi.fn(),
|
||||
terminalProps: [] as { command: string; description: string }[],
|
||||
toastError: vi.fn(),
|
||||
toastSuccess: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('sonner', () => ({
|
||||
toast: {
|
||||
error: mocks.toastError,
|
||||
success: mocks.toastSuccess
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('../onboarding/OnboardingInlineCommandTerminal', () => ({
|
||||
OnboardingInlineCommandTerminal: (props: { command: string; description: string }) => {
|
||||
mocks.terminalProps.push(props)
|
||||
return (
|
||||
<div
|
||||
data-testid="inline-command-terminal"
|
||||
data-command={props.command}
|
||||
data-description={props.description}
|
||||
>
|
||||
{props.command}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}))
|
||||
|
||||
function panelProps(
|
||||
overrides: Partial<ComponentProps<typeof AgentSkillSetupPanel>> = {}
|
||||
): ComponentProps<typeof AgentSkillSetupPanel> {
|
||||
return {
|
||||
title: 'CLI skill',
|
||||
description: 'Enables agents to use Orca workflows.',
|
||||
command: INSTALL_COMMAND,
|
||||
terminalTitle: 'CLI skill setup',
|
||||
terminalAriaLabel: 'CLI skill install terminal',
|
||||
terminalWorktreeId: 'settings-cli-skill-terminal',
|
||||
installed: false,
|
||||
loading: false,
|
||||
error: null,
|
||||
onRecheck: vi.fn(),
|
||||
...overrides
|
||||
}
|
||||
}
|
||||
|
||||
function renderPanel(overrides: Partial<ComponentProps<typeof AgentSkillSetupPanel>> = {}): string {
|
||||
return renderToStaticMarkup(
|
||||
<AgentSkillSetupPanel
|
||||
title="CLI skill"
|
||||
description="Enables agents to use Orca workflows."
|
||||
command="npx skills add https://github.com/stablyai/orca --skill orca-cli --global"
|
||||
terminalTitle="CLI skill setup"
|
||||
terminalAriaLabel="CLI skill install terminal"
|
||||
terminalWorktreeId="settings-cli-skill-terminal"
|
||||
installed={false}
|
||||
loading={false}
|
||||
error={null}
|
||||
onRecheck={vi.fn()}
|
||||
{...overrides}
|
||||
/>
|
||||
)
|
||||
return renderToStaticMarkup(<AgentSkillSetupPanel {...panelProps(overrides)} />)
|
||||
}
|
||||
|
||||
function buttonLabels(html: string): string[] {
|
||||
|
|
@ -36,7 +76,79 @@ function buttonMarkupByLabel(html: string, label: string): string | undefined {
|
|||
)
|
||||
}
|
||||
|
||||
let root: Root | null = null
|
||||
let container: HTMLDivElement | null = null
|
||||
|
||||
async function renderInteractivePanel(
|
||||
overrides: Partial<ComponentProps<typeof AgentSkillSetupPanel>> = {}
|
||||
): Promise<HTMLDivElement> {
|
||||
container = document.createElement('div')
|
||||
document.body.appendChild(container)
|
||||
root = createRoot(container)
|
||||
await rerenderInteractivePanel(overrides)
|
||||
return container
|
||||
}
|
||||
|
||||
async function rerenderInteractivePanel(
|
||||
overrides: Partial<ComponentProps<typeof AgentSkillSetupPanel>> = {}
|
||||
): Promise<void> {
|
||||
await act(async () => {
|
||||
root?.render(
|
||||
<TooltipProvider>
|
||||
<AgentSkillSetupPanel {...panelProps(overrides)} />
|
||||
</TooltipProvider>
|
||||
)
|
||||
})
|
||||
await act(async () => {})
|
||||
}
|
||||
|
||||
function findButton(label: string): HTMLButtonElement {
|
||||
const button = Array.from((container ?? document.body).querySelectorAll('button')).find(
|
||||
(candidate) => candidate.textContent?.trim() === label
|
||||
)
|
||||
expect(button).toBeDefined()
|
||||
return button as HTMLButtonElement
|
||||
}
|
||||
|
||||
async function clickButton(label: string): Promise<void> {
|
||||
await act(async () => {
|
||||
findButton(label).dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
||||
})
|
||||
await act(async () => {})
|
||||
}
|
||||
|
||||
describe('AgentSkillSetupPanel', () => {
|
||||
beforeEach(() => {
|
||||
mocks.clipboardWrite.mockReset()
|
||||
mocks.clipboardWrite.mockResolvedValue(undefined)
|
||||
mocks.terminalProps.length = 0
|
||||
mocks.toastError.mockReset()
|
||||
mocks.toastSuccess.mockReset()
|
||||
Object.defineProperty(window, 'api', {
|
||||
configurable: true,
|
||||
value: {
|
||||
cli: {
|
||||
getInstallStatus: vi.fn()
|
||||
},
|
||||
ui: {
|
||||
writeClipboardText: mocks.clipboardWrite
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
if (root) {
|
||||
await act(async () => {
|
||||
root?.unmount()
|
||||
})
|
||||
}
|
||||
root = null
|
||||
container?.remove()
|
||||
container = null
|
||||
Reflect.deleteProperty(window, 'api')
|
||||
})
|
||||
|
||||
it('keeps the install action visible after the skill is detected', () => {
|
||||
const html = renderPanel({ installed: true })
|
||||
|
||||
|
|
@ -65,6 +177,25 @@ describe('AgentSkillSetupPanel', () => {
|
|||
expect(buttonLabels(html)).not.toContain('Install CLI & Skill')
|
||||
})
|
||||
|
||||
it('keeps the installed action label when CLI prerequisites are missing', async () => {
|
||||
await renderInteractivePanel({
|
||||
installed: true,
|
||||
installedCommand: UPDATE_COMMAND,
|
||||
installLabel: 'Install CLI & Skill',
|
||||
preInstallNotice: 'Install the Orca CLI before running agent skill setup.',
|
||||
getPrerequisiteStatus: vi.fn(
|
||||
async () =>
|
||||
({
|
||||
state: 'not_installed'
|
||||
}) as Awaited<ReturnType<typeof window.api.cli.getInstallStatus>>
|
||||
),
|
||||
isPrerequisiteAvailable: () => false
|
||||
})
|
||||
|
||||
expect(findButton('Update').disabled).toBe(false)
|
||||
expect(container?.textContent).not.toContain('Install CLI & Skill')
|
||||
})
|
||||
|
||||
it('can hide install after the skill is detected', () => {
|
||||
const html = renderPanel({ installed: true, showInstallWhenInstalled: false })
|
||||
|
||||
|
|
@ -85,4 +216,62 @@ describe('AgentSkillSetupPanel', () => {
|
|||
|
||||
expect(buttonMarkupByLabel(html, 'Install')).toContain('disabled=""')
|
||||
})
|
||||
|
||||
it('opens not-installed setup with the install command for preview, copy, and terminal', async () => {
|
||||
await renderInteractivePanel({ installedCommand: UPDATE_COMMAND })
|
||||
|
||||
await clickButton('Install')
|
||||
|
||||
expect(container?.textContent).toContain(INSTALL_COMMAND)
|
||||
expect(mocks.terminalProps.at(-1)).toMatchObject({
|
||||
command: INSTALL_COMMAND,
|
||||
description: 'Press Enter to run the command.'
|
||||
})
|
||||
|
||||
await act(async () => {
|
||||
container
|
||||
?.querySelector<HTMLButtonElement>('button[aria-label="Copy command"]')
|
||||
?.dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
||||
})
|
||||
|
||||
expect(mocks.clipboardWrite).toHaveBeenCalledWith(INSTALL_COMMAND)
|
||||
expect(mocks.toastSuccess).toHaveBeenCalledWith('Copied command.')
|
||||
})
|
||||
|
||||
it('opens installed setup with the installed command for preview, copy, and terminal', async () => {
|
||||
await renderInteractivePanel({ installed: true, installedCommand: UPDATE_COMMAND })
|
||||
|
||||
await clickButton('Update')
|
||||
|
||||
expect(container?.textContent).toContain(UPDATE_COMMAND)
|
||||
expect(mocks.terminalProps.at(-1)).toMatchObject({ command: UPDATE_COMMAND })
|
||||
|
||||
await act(async () => {
|
||||
container
|
||||
?.querySelector<HTMLButtonElement>('button[aria-label="Copy command"]')
|
||||
?.dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
||||
})
|
||||
|
||||
expect(mocks.clipboardWrite).toHaveBeenCalledWith(UPDATE_COMMAND)
|
||||
})
|
||||
|
||||
it('keeps an open terminal on the command captured when it opened', async () => {
|
||||
await renderInteractivePanel({ installed: false, installedCommand: UPDATE_COMMAND })
|
||||
await clickButton('Install')
|
||||
|
||||
await rerenderInteractivePanel({ installed: true, installedCommand: UPDATE_COMMAND })
|
||||
|
||||
expect(container?.textContent).toContain(INSTALL_COMMAND)
|
||||
expect(container?.textContent).not.toContain(UPDATE_COMMAND)
|
||||
expect(mocks.terminalProps.at(-1)).toMatchObject({ command: INSTALL_COMMAND })
|
||||
})
|
||||
|
||||
it('falls back to the install command for installed callers without installedCommand', async () => {
|
||||
await renderInteractivePanel({ installed: true })
|
||||
|
||||
await clickButton('Update')
|
||||
|
||||
expect(container?.textContent).toContain(INSTALL_COMMAND)
|
||||
expect(mocks.terminalProps.at(-1)).toMatchObject({ command: INSTALL_COMMAND })
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ type AgentSkillSetupPanelProps = {
|
|||
title: string
|
||||
description: ReactNode
|
||||
command: string
|
||||
installedCommand?: string
|
||||
terminalTitle: string
|
||||
terminalAriaLabel: string
|
||||
terminalWorktreeId: string
|
||||
|
|
@ -51,6 +52,7 @@ export function AgentSkillSetupPanel({
|
|||
title,
|
||||
description,
|
||||
command,
|
||||
installedCommand,
|
||||
terminalTitle,
|
||||
terminalAriaLabel,
|
||||
terminalWorktreeId,
|
||||
|
|
@ -78,6 +80,7 @@ export function AgentSkillSetupPanel({
|
|||
onRecheck
|
||||
}: AgentSkillSetupPanelProps): React.JSX.Element {
|
||||
const [terminalOpen, setTerminalOpen] = useState(false)
|
||||
const [terminalCommand, setTerminalCommand] = useState<string | null>(null)
|
||||
const [preInstallNoticeVisible, setPreInstallNoticeVisible] = useState(
|
||||
Boolean(preInstallNotice && !installed)
|
||||
)
|
||||
|
|
@ -86,7 +89,10 @@ export function AgentSkillSetupPanel({
|
|||
() => (getPrerequisiteStatus ?? window.api.cli.getInstallStatus)(),
|
||||
[getPrerequisiteStatus]
|
||||
)
|
||||
const actionLabel = installed && preInstallNoticeVisible ? installLabel : installedInstallLabel
|
||||
const activeCommand = installed ? (installedCommand ?? command) : command
|
||||
// Why: the inline terminal auto-inserts when its command changes, so keep an
|
||||
// already-open terminal pinned to the command selected by the user's click.
|
||||
const openTerminalCommand = terminalCommand ?? activeCommand
|
||||
|
||||
useEffect(() => {
|
||||
if (!preInstallNotice) {
|
||||
|
|
@ -132,22 +138,19 @@ export function AgentSkillSetupPanel({
|
|||
}
|
||||
}
|
||||
|
||||
const copyInstallCommand = async (): Promise<void> => {
|
||||
const copyActiveCommand = async (): Promise<void> => {
|
||||
try {
|
||||
await window.api.ui.writeClipboardText(command)
|
||||
await window.api.ui.writeClipboardText(openTerminalCommand)
|
||||
toast.success(
|
||||
translate(
|
||||
'auto.components.settings.AgentSkillSetupPanel.378ad26865',
|
||||
'Copied install command.'
|
||||
)
|
||||
translate('auto.components.settings.AgentSkillSetupPanel.copiedCommand', 'Copied command.')
|
||||
)
|
||||
} catch (error) {
|
||||
toast.error(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: translate(
|
||||
'auto.components.settings.AgentSkillSetupPanel.a31e2aa302',
|
||||
'Failed to copy install command.'
|
||||
'auto.components.settings.AgentSkillSetupPanel.failedToCopyCommand',
|
||||
'Failed to copy command.'
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
@ -161,12 +164,14 @@ export function AgentSkillSetupPanel({
|
|||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
const nextCommand = activeCommand
|
||||
void (async () => {
|
||||
try {
|
||||
await onBeforeOpenTerminal?.()
|
||||
await refreshPreInstallNotice()
|
||||
} finally {
|
||||
if (mountedRef.current) {
|
||||
setTerminalCommand(nextCommand)
|
||||
setTerminalOpen(true)
|
||||
}
|
||||
}
|
||||
|
|
@ -175,7 +180,7 @@ export function AgentSkillSetupPanel({
|
|||
disabled={terminalOpen || installDisabled}
|
||||
>
|
||||
<Terminal className="size-3.5" />
|
||||
{installed ? actionLabel : installLabel}
|
||||
{installed ? installedInstallLabel : installLabel}
|
||||
</Button>
|
||||
) : null}
|
||||
{!installed || showRecheckWhenInstalled ? (
|
||||
|
|
@ -274,7 +279,7 @@ export function AgentSkillSetupPanel({
|
|||
>
|
||||
<div className="flex min-w-0 max-w-full items-center gap-2 overflow-hidden rounded-md border border-border bg-muted/35 px-3 py-2">
|
||||
<code className="scrollbar-sleek min-w-0 flex-1 overflow-x-auto whitespace-nowrap font-mono text-xs text-muted-foreground">
|
||||
{command}
|
||||
{openTerminalCommand}
|
||||
</code>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
|
|
@ -284,10 +289,10 @@ export function AgentSkillSetupPanel({
|
|||
size="icon-sm"
|
||||
className="shrink-0"
|
||||
aria-label={translate(
|
||||
'auto.components.settings.AgentSkillSetupPanel.817d3f9f18',
|
||||
'Copy install command'
|
||||
'auto.components.settings.AgentSkillSetupPanel.copyCommandAria',
|
||||
'Copy command'
|
||||
)}
|
||||
onClick={() => void copyInstallCommand()}
|
||||
onClick={() => void copyActiveCommand()}
|
||||
>
|
||||
<Copy className="size-4" />
|
||||
</Button>
|
||||
|
|
@ -302,11 +307,11 @@ export function AgentSkillSetupPanel({
|
|||
</div>
|
||||
<OnboardingInlineCommandTerminal
|
||||
worktreeId={terminalWorktreeId}
|
||||
command={command}
|
||||
command={openTerminalCommand}
|
||||
title={terminalTitle}
|
||||
description={translate(
|
||||
'auto.components.settings.AgentSkillSetupPanel.0b810ec59f',
|
||||
'Press Enter to run the install command.'
|
||||
'auto.components.settings.AgentSkillSetupPanel.runCommandDescription',
|
||||
'Press Enter to run the command.'
|
||||
)}
|
||||
ariaLabel={terminalAriaLabel}
|
||||
terminalHeightPx={terminalHeightPx}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ import { toast } from 'sonner'
|
|||
import type { CliInstallStatus } from '../../../../shared/cli-install-types'
|
||||
import {
|
||||
ORCA_CLI_SKILL_INSTALL_COMMAND,
|
||||
ORCA_CLI_SKILL_NAME
|
||||
ORCA_CLI_SKILL_NAME,
|
||||
ORCA_CLI_SKILL_UPDATE_COMMAND
|
||||
} from '@/lib/agent-feature-install-commands'
|
||||
import {
|
||||
AGENT_SKILL_CLI_PREREQUISITE_NOTICE,
|
||||
|
|
@ -30,7 +31,7 @@ import { BrowserUseSkillStep } from './BrowserUseSkillStep'
|
|||
import { BrowserUseCliStep } from './BrowserUseCliStep'
|
||||
import { BrowserUseCookieImportStep } from './BrowserUseCookieImportStep'
|
||||
import {
|
||||
buildSkillInstallCommandForRuntime,
|
||||
buildSkillCommandForRuntime,
|
||||
ensureWslCliAvailableForAgentSkillTerminal,
|
||||
getWslCliDistroRequest
|
||||
} from './CliSkillRuntimeSetup'
|
||||
|
|
@ -57,11 +58,12 @@ export function BrowserUseSetup({
|
|||
const activeSkillRuntime = useActiveProjectSkillRuntime()
|
||||
const browserUseInstallCommand =
|
||||
activeSkillRuntime.agentRuntime && !activeSkillRuntime.installDisabledReason
|
||||
? buildSkillInstallCommandForRuntime(
|
||||
ORCA_CLI_SKILL_INSTALL_COMMAND,
|
||||
activeSkillRuntime.agentRuntime
|
||||
)
|
||||
? buildSkillCommandForRuntime(ORCA_CLI_SKILL_INSTALL_COMMAND, activeSkillRuntime.agentRuntime)
|
||||
: ORCA_CLI_SKILL_INSTALL_COMMAND
|
||||
const browserUseUpdateCommand =
|
||||
activeSkillRuntime.agentRuntime && !activeSkillRuntime.installDisabledReason
|
||||
? buildSkillCommandForRuntime(ORCA_CLI_SKILL_UPDATE_COMMAND, activeSkillRuntime.agentRuntime)
|
||||
: ORCA_CLI_SKILL_UPDATE_COMMAND
|
||||
|
||||
const handleCliStatusChange = useCallback(
|
||||
(nextStatus: CliInstallStatus | null): void => {
|
||||
|
|
@ -275,6 +277,7 @@ export function BrowserUseSetup({
|
|||
>
|
||||
<BrowserUseSkillStep
|
||||
command={browserUseInstallCommand}
|
||||
installedCommand={browserUseUpdateCommand}
|
||||
skillDetected={skillDetected}
|
||||
skillLoading={skillLoading}
|
||||
skillError={activeSkillRuntime.installDisabledReason ?? skillError}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
import { renderToStaticMarkup } from 'react-dom/server'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { BrowserUseSkillStep } from './BrowserUseSkillStep'
|
||||
|
||||
const capturedPanel = vi.hoisted(() => ({
|
||||
props: null as null | Record<string, unknown>
|
||||
}))
|
||||
|
||||
vi.mock('./AgentSkillSetupPanel', () => ({
|
||||
AgentSkillSetupPanel: (props: Record<string, unknown>) => {
|
||||
capturedPanel.props = props
|
||||
return <div data-testid="browser-use-skill-step" />
|
||||
}
|
||||
}))
|
||||
|
||||
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'
|
||||
|
||||
renderToStaticMarkup(
|
||||
<BrowserUseSkillStep
|
||||
command={bundleInstallCommand}
|
||||
installedCommand={updateCommand}
|
||||
skillDetected
|
||||
skillLoading={false}
|
||||
skillError={null}
|
||||
onRecheck={vi.fn()}
|
||||
/>
|
||||
)
|
||||
|
||||
expect(capturedPanel.props).toEqual(
|
||||
expect.objectContaining({
|
||||
command: bundleInstallCommand,
|
||||
installedCommand: updateCommand,
|
||||
installed: true
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
@ -5,6 +5,7 @@ import { translate } from '@/i18n/i18n'
|
|||
|
||||
type Props = {
|
||||
command: string
|
||||
installedCommand: string
|
||||
skillDetected: boolean
|
||||
skillLoading: boolean
|
||||
skillError: string | null
|
||||
|
|
@ -18,6 +19,7 @@ type Props = {
|
|||
|
||||
export function BrowserUseSkillStep({
|
||||
command,
|
||||
installedCommand,
|
||||
skillDetected,
|
||||
skillLoading,
|
||||
skillError,
|
||||
|
|
@ -40,6 +42,7 @@ export function BrowserUseSkillStep({
|
|||
"Enables agents to navigate and verify pages in Orca's browser."
|
||||
)}
|
||||
command={command}
|
||||
installedCommand={installedCommand}
|
||||
terminalTitle="Browser Use setup"
|
||||
terminalAriaLabel="Browser Use skill install terminal"
|
||||
terminalWorktreeId="settings-browser-use-skill-terminal"
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { useCallback, useMemo } from 'react'
|
||||
import type { CliInstallStatus } from '../../../../shared/cli-install-types'
|
||||
import type { SkillDiscoveryTarget } from '../../../../shared/skills'
|
||||
import type { GlobalSettings } from '../../../../shared/types'
|
||||
import {
|
||||
ORCA_CLI_SKILL_INSTALL_COMMAND,
|
||||
ORCA_CLI_SKILL_NAME
|
||||
ORCA_CLI_SKILL_NAME,
|
||||
ORCA_CLI_SKILL_UPDATE_COMMAND
|
||||
} from '@/lib/agent-feature-install-commands'
|
||||
import {
|
||||
AGENT_SKILL_CLI_PREREQUISITE_NOTICE,
|
||||
|
|
@ -17,10 +17,11 @@ import {
|
|||
} from '@/hooks/useInstalledAgentSkills'
|
||||
import { AgentSkillSetupPanel } from './AgentSkillSetupPanel'
|
||||
import {
|
||||
buildSkillInstallCommandForRuntime,
|
||||
buildSkillCommandForRuntime,
|
||||
ensureWslCliAvailableForAgentSkillTerminal,
|
||||
getAgentSkillTerminalShellOverride,
|
||||
getSelectedAgentRuntime,
|
||||
getSkillDiscoveryTargetForRuntime,
|
||||
getWslCliDistroRequest
|
||||
} from './CliSkillRuntimeSetup'
|
||||
import { Label } from '../ui/label'
|
||||
|
|
@ -48,12 +49,9 @@ export function CliAgentSkillSetup({
|
|||
getSelectedAgentRuntime(settings, wslSupportedPlatform, wslAvailable, wslCapabilitiesLoading),
|
||||
[settings, wslAvailable, wslCapabilitiesLoading, wslSupportedPlatform]
|
||||
)
|
||||
const cliSkillDiscoveryTarget = useMemo<SkillDiscoveryTarget | undefined>(
|
||||
() =>
|
||||
agentRuntime.runtime === 'wsl'
|
||||
? { runtime: 'wsl', wslDistro: agentRuntime.wslDistro }
|
||||
: undefined,
|
||||
[agentRuntime.runtime, agentRuntime.wslDistro]
|
||||
const cliSkillDiscoveryTarget = useMemo(
|
||||
() => getSkillDiscoveryTargetForRuntime(agentRuntime),
|
||||
[agentRuntime]
|
||||
)
|
||||
const {
|
||||
installed: cliSkillDetected,
|
||||
|
|
@ -64,10 +62,14 @@ export function CliAgentSkillSetup({
|
|||
discoveryTarget: cliSkillDiscoveryTarget,
|
||||
sourceKinds: GLOBAL_AGENT_SKILL_SOURCE_KINDS
|
||||
})
|
||||
const cliSkillInstallCommand = buildSkillInstallCommandForRuntime(
|
||||
const cliSkillInstallCommand = buildSkillCommandForRuntime(
|
||||
ORCA_CLI_SKILL_INSTALL_COMMAND,
|
||||
agentRuntime
|
||||
)
|
||||
const cliSkillUpdateCommand = buildSkillCommandForRuntime(
|
||||
ORCA_CLI_SKILL_UPDATE_COMMAND,
|
||||
agentRuntime
|
||||
)
|
||||
const cliSkillTerminalShellOverride = getAgentSkillTerminalShellOverride(
|
||||
currentPlatform,
|
||||
settings,
|
||||
|
|
@ -102,6 +104,7 @@ export function CliAgentSkillSetup({
|
|||
'Enables agents to use Orca workspace, terminal, and progress commands.'
|
||||
)}
|
||||
command={cliSkillInstallCommand}
|
||||
installedCommand={cliSkillUpdateCommand}
|
||||
terminalTitle={translate(
|
||||
'auto.components.settings.CliSection.cliSkillTerminalTitle',
|
||||
'CLI skill setup'
|
||||
|
|
|
|||
|
|
@ -5,23 +5,30 @@ import { CliSection } from './CliSection'
|
|||
|
||||
const capturedPanel = vi.hoisted(() => ({
|
||||
props: null as null | {
|
||||
command: string
|
||||
installedCommand: string
|
||||
getPrerequisiteStatus: () => Promise<unknown>
|
||||
onBeforeOpenTerminal: () => Promise<void>
|
||||
}
|
||||
},
|
||||
useInstalledAgentSkill: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('@/hooks/useInstalledAgentSkills', () => ({
|
||||
GLOBAL_AGENT_SKILL_SOURCE_KINDS: ['global'],
|
||||
useInstalledAgentSkill: () => ({
|
||||
installed: false,
|
||||
loading: false,
|
||||
error: null,
|
||||
refresh: vi.fn()
|
||||
})
|
||||
useInstalledAgentSkill: capturedPanel.useInstalledAgentSkill
|
||||
}))
|
||||
|
||||
capturedPanel.useInstalledAgentSkill.mockReturnValue({
|
||||
installed: false,
|
||||
loading: false,
|
||||
error: null,
|
||||
refresh: vi.fn()
|
||||
})
|
||||
|
||||
vi.mock('./AgentSkillSetupPanel', () => ({
|
||||
AgentSkillSetupPanel: function AgentSkillSetupPanel(props: {
|
||||
command: string
|
||||
installedCommand: string
|
||||
getPrerequisiteStatus: () => Promise<unknown>
|
||||
onBeforeOpenTerminal: () => Promise<void>
|
||||
}) {
|
||||
|
|
@ -75,6 +82,17 @@ describe('CliSection project runtime defaults', () => {
|
|||
await capturedPanel.props?.getPrerequisiteStatus()
|
||||
await capturedPanel.props?.onBeforeOpenTerminal()
|
||||
|
||||
expect(capturedPanel.useInstalledAgentSkill).toHaveBeenCalledWith(
|
||||
'orca-cli',
|
||||
expect.objectContaining({
|
||||
discoveryTarget: { runtime: 'wsl', wslDistro: 'Ubuntu' },
|
||||
sourceKinds: ['global']
|
||||
})
|
||||
)
|
||||
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(getWslInstallStatus).toHaveBeenCalledWith({ distro: 'Ubuntu' })
|
||||
expect(getWslInstallStatus).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@ import { useCallback, useEffect, useMemo, useState } from 'react'
|
|||
import { FolderOpen, RefreshCw } from 'lucide-react'
|
||||
import { toast } from 'sonner'
|
||||
import type { CliInstallStatus } from '../../../../shared/cli-install-types'
|
||||
import type { SkillDiscoveryTarget } from '../../../../shared/skills'
|
||||
import type { GlobalSettings } from '../../../../shared/types'
|
||||
import {
|
||||
ORCA_CLI_SKILL_INSTALL_COMMAND,
|
||||
ORCA_CLI_SKILL_NAME
|
||||
ORCA_CLI_SKILL_NAME,
|
||||
ORCA_CLI_SKILL_UPDATE_COMMAND
|
||||
} from '@/lib/agent-feature-install-commands'
|
||||
import {
|
||||
AGENT_SKILL_CLI_PREREQUISITE_NOTICE,
|
||||
|
|
@ -24,10 +24,11 @@ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../ui/
|
|||
import { AgentSkillSetupPanel } from './AgentSkillSetupPanel'
|
||||
import { CliRegistrationDialog } from './CliRegistrationDialog'
|
||||
import {
|
||||
buildSkillInstallCommandForRuntime,
|
||||
buildSkillCommandForRuntime,
|
||||
ensureWslCliAvailableForAgentSkillTerminal,
|
||||
getAgentSkillTerminalShellOverride,
|
||||
getSelectedAgentRuntime,
|
||||
getSkillDiscoveryTargetForRuntime,
|
||||
getWslCliDistroRequest
|
||||
} from './CliSkillRuntimeSetup'
|
||||
import { WslCliRegistration } from './WslCliRegistration'
|
||||
|
|
@ -85,9 +86,9 @@ export function CliSection({
|
|||
getSelectedAgentRuntime(settings, wslSupportedPlatform, wslAvailable, wslCapabilitiesLoading),
|
||||
[settings, wslAvailable, wslCapabilitiesLoading, wslSupportedPlatform]
|
||||
)
|
||||
const cliSkillDiscoveryTarget = useMemo<SkillDiscoveryTarget | undefined>(
|
||||
() => (agentRuntime.runtime === 'wsl' ? { runtime: 'wsl' } : undefined),
|
||||
[agentRuntime.runtime]
|
||||
const cliSkillDiscoveryTarget = useMemo(
|
||||
() => getSkillDiscoveryTargetForRuntime(agentRuntime),
|
||||
[agentRuntime]
|
||||
)
|
||||
const {
|
||||
installed: cliSkillDetected,
|
||||
|
|
@ -98,10 +99,14 @@ export function CliSection({
|
|||
discoveryTarget: cliSkillDiscoveryTarget,
|
||||
sourceKinds: GLOBAL_AGENT_SKILL_SOURCE_KINDS
|
||||
})
|
||||
const cliSkillInstallCommand = buildSkillInstallCommandForRuntime(
|
||||
const cliSkillInstallCommand = buildSkillCommandForRuntime(
|
||||
ORCA_CLI_SKILL_INSTALL_COMMAND,
|
||||
agentRuntime
|
||||
)
|
||||
const cliSkillUpdateCommand = buildSkillCommandForRuntime(
|
||||
ORCA_CLI_SKILL_UPDATE_COMMAND,
|
||||
agentRuntime
|
||||
)
|
||||
const cliSkillTerminalShellOverride = getAgentSkillTerminalShellOverride(
|
||||
currentPlatform,
|
||||
settings,
|
||||
|
|
@ -366,6 +371,7 @@ export function CliSection({
|
|||
'Enables agents to use Orca workspace, terminal, and progress commands.'
|
||||
)}
|
||||
command={cliSkillInstallCommand}
|
||||
installedCommand={cliSkillUpdateCommand}
|
||||
terminalTitle="CLI skill setup"
|
||||
terminalAriaLabel="CLI skill install terminal"
|
||||
terminalWorktreeId={`settings-cli-skill-terminal-${agentRuntime.runtime}`}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { describe, expect, it } from 'vitest'
|
||||
import { getDefaultSettings } from '../../../../shared/constants'
|
||||
import {
|
||||
buildSkillCommandForRuntime,
|
||||
buildSkillInstallCommandForRuntime,
|
||||
getSelectedAgentRuntime,
|
||||
getSkillDiscoveryTargetForRuntime
|
||||
|
|
@ -19,6 +20,18 @@ describe('CliSkillRuntimeSetup runtime helpers', () => {
|
|||
expect(command).toContain('npx skills add orchestration --global')
|
||||
})
|
||||
|
||||
it('wraps WSL skill updates with the same selected distro login shell', () => {
|
||||
const command = buildSkillCommandForRuntime('npx skills update orchestration --global', {
|
||||
runtime: 'wsl',
|
||||
wslDistro: 'Fedora Remix',
|
||||
label: 'WSL Fedora Remix'
|
||||
})
|
||||
|
||||
expect(command).toContain("wsl.exe -d 'Fedora Remix' -- sh -c")
|
||||
expect(command).toContain('getent passwd')
|
||||
expect(command).toContain('npx skills update orchestration --global')
|
||||
})
|
||||
|
||||
it('preserves the selected WSL distro for skill discovery', () => {
|
||||
expect(
|
||||
getSkillDiscoveryTargetForRuntime({
|
||||
|
|
|
|||
|
|
@ -62,10 +62,7 @@ export function getWslCliDistroRequest(
|
|||
: undefined
|
||||
}
|
||||
|
||||
export function buildSkillInstallCommandForRuntime(
|
||||
command: string,
|
||||
runtime: LocalAgentRuntime
|
||||
): string {
|
||||
export function buildSkillCommandForRuntime(command: string, runtime: LocalAgentRuntime): string {
|
||||
if (runtime.runtime !== 'wsl') {
|
||||
return command
|
||||
}
|
||||
|
|
@ -76,6 +73,13 @@ export function buildSkillInstallCommandForRuntime(
|
|||
return `wsl.exe${distroArg} -- sh -c ${quotePowerShellSingle(wslCommand)}`
|
||||
}
|
||||
|
||||
export function buildSkillInstallCommandForRuntime(
|
||||
command: string,
|
||||
runtime: LocalAgentRuntime
|
||||
): string {
|
||||
return buildSkillCommandForRuntime(command, runtime)
|
||||
}
|
||||
|
||||
export function getSkillDiscoveryTargetForRuntime(
|
||||
runtime: LocalAgentRuntime
|
||||
): { runtime: 'wsl'; wslDistro?: string | null } | undefined {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { MonitorCog } from 'lucide-react'
|
||||
import {
|
||||
COMPUTER_USE_SKILL_INSTALL_COMMAND,
|
||||
COMPUTER_USE_SKILL_NAME
|
||||
COMPUTER_USE_SKILL_NAME,
|
||||
COMPUTER_USE_SKILL_UPDATE_COMMAND
|
||||
} from '@/lib/agent-feature-install-commands'
|
||||
import {
|
||||
AGENT_SKILL_CLI_PREREQUISITE_NOTICE,
|
||||
|
|
@ -15,7 +16,7 @@ import { useActiveProjectSkillRuntime } from '@/hooks/useActiveProjectSkillRunti
|
|||
import { useAppStore } from '@/store'
|
||||
import { AgentSkillSetupPanel } from './AgentSkillSetupPanel'
|
||||
import {
|
||||
buildSkillInstallCommandForRuntime,
|
||||
buildSkillCommandForRuntime,
|
||||
ensureWslCliAvailableForAgentSkillTerminal,
|
||||
getWslCliDistroRequest
|
||||
} from './CliSkillRuntimeSetup'
|
||||
|
|
@ -25,11 +26,18 @@ export function ComputerUseSkillSetupPanel(): React.JSX.Element {
|
|||
const activeSkillRuntime = useActiveProjectSkillRuntime()
|
||||
const installCommand =
|
||||
activeSkillRuntime.agentRuntime && !activeSkillRuntime.installDisabledReason
|
||||
? buildSkillInstallCommandForRuntime(
|
||||
? buildSkillCommandForRuntime(
|
||||
COMPUTER_USE_SKILL_INSTALL_COMMAND,
|
||||
activeSkillRuntime.agentRuntime
|
||||
)
|
||||
: COMPUTER_USE_SKILL_INSTALL_COMMAND
|
||||
const updateCommand =
|
||||
activeSkillRuntime.agentRuntime && !activeSkillRuntime.installDisabledReason
|
||||
? buildSkillCommandForRuntime(
|
||||
COMPUTER_USE_SKILL_UPDATE_COMMAND,
|
||||
activeSkillRuntime.agentRuntime
|
||||
)
|
||||
: COMPUTER_USE_SKILL_UPDATE_COMMAND
|
||||
const {
|
||||
installed: computerUseSkillDetected,
|
||||
loading: computerUseSkillLoading,
|
||||
|
|
@ -48,6 +56,7 @@ export function ComputerUseSkillSetupPanel(): React.JSX.Element {
|
|||
'Enables agents to inspect and operate local desktop apps.'
|
||||
)}
|
||||
command={installCommand}
|
||||
installedCommand={updateCommand}
|
||||
terminalTitle="Computer Use setup"
|
||||
terminalAriaLabel="Computer Use skill install terminal"
|
||||
terminalWorktreeId="settings-computer-use-skill-terminal"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
import { Import, Loader2 } from 'lucide-react'
|
||||
import { ORCA_CLI_SKILL_INSTALL_COMMAND } from '@/lib/agent-feature-install-commands'
|
||||
import {
|
||||
ORCA_CLI_SKILL_INSTALL_COMMAND,
|
||||
ORCA_CLI_SKILL_UPDATE_COMMAND
|
||||
} from '@/lib/agent-feature-install-commands'
|
||||
import {
|
||||
AGENT_SKILL_CLI_PREREQUISITE_NOTICE,
|
||||
ensureOrcaCliAvailableForAgentSkillTerminal
|
||||
|
|
@ -128,6 +131,7 @@ export function MobileEmulatorAgentControlRow(): React.JSX.Element {
|
|||
'Enables agents to use Orca CLI commands, including mobile emulator control.'
|
||||
)}
|
||||
command={ORCA_CLI_SKILL_INSTALL_COMMAND}
|
||||
installedCommand={ORCA_CLI_SKILL_UPDATE_COMMAND}
|
||||
terminalTitle="Orca CLI skill setup"
|
||||
terminalAriaLabel="Orca CLI skill install terminal"
|
||||
terminalWorktreeId="settings-mobile-emulator-orca-cli-skill-terminal"
|
||||
|
|
|
|||
|
|
@ -1,12 +1,57 @@
|
|||
// @vitest-environment happy-dom
|
||||
|
||||
import { act, type ReactNode } from 'react'
|
||||
import { createRoot, type Root } from 'react-dom/client'
|
||||
import { renderToStaticMarkup } from 'react-dom/server'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { afterEach, 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 UPDATE_COMMAND = 'npx skills update orchestration --global'
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
dialogProps: [] as Record<string, unknown>[],
|
||||
panelProps: [] as Record<string, unknown>[],
|
||||
skillInstalled: true
|
||||
}))
|
||||
|
||||
vi.mock('./AgentSkillSetupPanel', () => ({
|
||||
AgentSkillSetupPanel: (
|
||||
props: Record<string, unknown> & { actionHint?: ReactNode; footer?: ReactNode }
|
||||
) => {
|
||||
mocks.panelProps.push(props)
|
||||
return (
|
||||
<section>
|
||||
<h3>{String(props.title)}</h3>
|
||||
<span>{props.installed ? 'Installed' : 'Not installed'}</span>
|
||||
<code>{String(props.command)}</code>
|
||||
<code>{String(props.installedCommand)}</code>
|
||||
<button type="button">{props.installed ? 'Update' : 'Install'}</button>
|
||||
<button type="button">Re-check</button>
|
||||
{props.actionHint}
|
||||
{props.footer}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('./OrchestrationSkillPromptDialog', () => ({
|
||||
OrchestrationSkillPromptDialog: (props: Record<string, unknown>) => {
|
||||
mocks.dialogProps.push(props)
|
||||
return props.open ? (
|
||||
<div data-testid="orchestration-skill-prompt-dialog">
|
||||
<code>{String(props.command)}</code>
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('@/hooks/useInstalledAgentSkills', () => ({
|
||||
GLOBAL_AGENT_SKILL_SOURCE_KINDS: ['home'],
|
||||
useInstalledAgentSkill: () => ({
|
||||
installed: true,
|
||||
installed: mocks.skillInstalled,
|
||||
loading: false,
|
||||
error: null,
|
||||
skills: [
|
||||
|
|
@ -38,14 +83,42 @@ vi.mock('@/hooks/useDetectedAgents', () => ({
|
|||
})
|
||||
}))
|
||||
|
||||
let root: Root | null = null
|
||||
let container: HTMLDivElement | null = null
|
||||
|
||||
async function renderPane(): Promise<HTMLDivElement> {
|
||||
container = document.createElement('div')
|
||||
document.body.appendChild(container)
|
||||
root = createRoot(container)
|
||||
await act(async () => {
|
||||
root?.render(<OrchestrationPane />)
|
||||
})
|
||||
return container
|
||||
}
|
||||
|
||||
describe('OrchestrationPane', () => {
|
||||
afterEach(async () => {
|
||||
if (root) {
|
||||
await act(async () => {
|
||||
root?.unmount()
|
||||
})
|
||||
}
|
||||
root = null
|
||||
container?.remove()
|
||||
container = null
|
||||
mocks.dialogProps.length = 0
|
||||
mocks.panelProps.length = 0
|
||||
mocks.skillInstalled = true
|
||||
})
|
||||
|
||||
it('keeps skill setup visible after install and shows agent coverage plus examples', () => {
|
||||
const markup = renderToStaticMarkup(<OrchestrationPane />)
|
||||
|
||||
expect(markup).toContain('Orchestration skill')
|
||||
expect(markup).toContain('Installed')
|
||||
expect(markup).toContain('Agent coverage')
|
||||
expect(markup).toContain('Copy install command')
|
||||
expect(markup).not.toContain('Prefer your own terminal?')
|
||||
expect(markup).not.toContain('Copy update command')
|
||||
expect(markup).toContain('detected agents')
|
||||
expect(markup).toContain('Gemini')
|
||||
expect(markup).toContain('Ready')
|
||||
|
|
@ -59,4 +132,52 @@ describe('OrchestrationPane', () => {
|
|||
expect(markup).toMatch(/<button\b[^>]*>[\s\S]*?Update[\s\S]*?<\/button>/)
|
||||
expect(markup).toContain('Re-check')
|
||||
})
|
||||
|
||||
it('passes update commands to the main panel without an installed manual-copy path', async () => {
|
||||
const rendered = await renderPane()
|
||||
|
||||
expect(mocks.panelProps.at(-1)).toEqual(
|
||||
expect.objectContaining({
|
||||
command: INSTALL_COMMAND,
|
||||
installedCommand: UPDATE_COMMAND
|
||||
})
|
||||
)
|
||||
|
||||
expect(rendered.textContent).not.toContain('Prefer your own terminal?')
|
||||
expect(rendered.textContent).not.toContain('Copy update command')
|
||||
expect(rendered.textContent).not.toContain('Copy install command')
|
||||
expect(mocks.dialogProps).not.toContainEqual(expect.objectContaining({ mode: 'update' }))
|
||||
expect(mocks.dialogProps).not.toContainEqual(
|
||||
expect.objectContaining({
|
||||
command: UPDATE_COMMAND,
|
||||
open: true
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('keeps first-time manual copy on the install command', async () => {
|
||||
mocks.skillInstalled = false
|
||||
const rendered = await renderPane()
|
||||
|
||||
expect(rendered.textContent).toContain('Prefer your own terminal?')
|
||||
expect(rendered.textContent).toContain('Copy install command')
|
||||
expect(rendered.textContent).not.toContain('Copy update command')
|
||||
|
||||
const copyButton = Array.from(rendered.querySelectorAll('button')).find(
|
||||
(button) => button.textContent === 'Copy install command'
|
||||
)
|
||||
expect(copyButton).toBeDefined()
|
||||
|
||||
await act(async () => {
|
||||
copyButton?.dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
||||
})
|
||||
|
||||
expect(mocks.dialogProps.at(-1)).toEqual(
|
||||
expect.objectContaining({
|
||||
command: INSTALL_COMMAND,
|
||||
open: true
|
||||
})
|
||||
)
|
||||
expect(rendered.textContent).toContain(INSTALL_COMMAND)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -5,7 +5,10 @@ import {
|
|||
AGENT_SKILL_CLI_PREREQUISITE_NOTICE,
|
||||
ensureOrcaCliAvailableForAgentSkillTerminal
|
||||
} from '@/lib/agent-skill-cli-prerequisite'
|
||||
import { ORCHESTRATION_SKILL_INSTALL_COMMAND } from '@/lib/orchestration-install-command'
|
||||
import {
|
||||
ORCHESTRATION_SKILL_INSTALL_COMMAND,
|
||||
ORCHESTRATION_SKILL_UPDATE_COMMAND
|
||||
} from '@/lib/orchestration-install-command'
|
||||
import { getOrchestrationUsageExamples } from '@/lib/orchestration-usage-examples'
|
||||
import {
|
||||
GLOBAL_AGENT_SKILL_SOURCE_KINDS,
|
||||
|
|
@ -18,7 +21,7 @@ import { useAppStore } from '../../store'
|
|||
import { getOrchestrationPaneSearchEntries } from './orchestration-search'
|
||||
import { AgentSkillSetupPanel } from './AgentSkillSetupPanel'
|
||||
import {
|
||||
buildSkillInstallCommandForRuntime,
|
||||
buildSkillCommandForRuntime,
|
||||
ensureWslCliAvailableForAgentSkillTerminal,
|
||||
getWslCliDistroRequest
|
||||
} from './CliSkillRuntimeSetup'
|
||||
|
|
@ -43,11 +46,18 @@ export function OrchestrationPane(): React.JSX.Element {
|
|||
const activeSkillRuntime = useActiveProjectSkillRuntime()
|
||||
const orchestrationInstallCommand =
|
||||
activeSkillRuntime.agentRuntime && !activeSkillRuntime.installDisabledReason
|
||||
? buildSkillInstallCommandForRuntime(
|
||||
? buildSkillCommandForRuntime(
|
||||
ORCHESTRATION_SKILL_INSTALL_COMMAND,
|
||||
activeSkillRuntime.agentRuntime
|
||||
)
|
||||
: ORCHESTRATION_SKILL_INSTALL_COMMAND
|
||||
const orchestrationUpdateCommand =
|
||||
activeSkillRuntime.agentRuntime && !activeSkillRuntime.installDisabledReason
|
||||
? buildSkillCommandForRuntime(
|
||||
ORCHESTRATION_SKILL_UPDATE_COMMAND,
|
||||
activeSkillRuntime.agentRuntime
|
||||
)
|
||||
: ORCHESTRATION_SKILL_UPDATE_COMMAND
|
||||
|
||||
const {
|
||||
installed: orchestrationSkillDetected,
|
||||
|
|
@ -87,6 +97,7 @@ export function OrchestrationPane(): React.JSX.Element {
|
|||
'Enables agents to hand off context and coordinate work through Orca.'
|
||||
)}
|
||||
command={orchestrationInstallCommand}
|
||||
installedCommand={orchestrationUpdateCommand}
|
||||
terminalTitle="Orchestration setup"
|
||||
terminalAriaLabel="Orchestration skill install terminal"
|
||||
terminalWorktreeId="settings-orchestration-skill-terminal"
|
||||
|
|
@ -111,7 +122,8 @@ export function OrchestrationPane(): React.JSX.Element {
|
|||
: ensureOrcaCliAvailableForAgentSkillTerminal())
|
||||
}}
|
||||
actionHint={
|
||||
activeSkillRuntime.installDisabledReason ? null : (
|
||||
// Installed updates stay on the primary panel so there is only one update path.
|
||||
activeSkillRuntime.installDisabledReason || orchestrationSkillDetected ? null : (
|
||||
<p className="text-[12px] leading-snug text-muted-foreground">
|
||||
{translate(
|
||||
'auto.components.settings.OrchestrationPane.832f1f3ee6',
|
||||
|
|
@ -120,7 +132,9 @@ export function OrchestrationPane(): React.JSX.Element {
|
|||
<button
|
||||
type="button"
|
||||
className="font-medium text-foreground underline-offset-2 hover:underline"
|
||||
onClick={() => setSkillPromptOpen(true)}
|
||||
onClick={() => {
|
||||
setSkillPromptOpen(true)
|
||||
}}
|
||||
>
|
||||
{translate(
|
||||
'auto.components.settings.OrchestrationPane.7bc082f4de',
|
||||
|
|
|
|||
|
|
@ -3,12 +3,15 @@ import {
|
|||
AGENT_SKILL_CLI_PREREQUISITE_NOTICE,
|
||||
ensureOrcaCliAvailableForAgentSkillTerminal
|
||||
} from '@/lib/agent-skill-cli-prerequisite'
|
||||
import { ORCHESTRATION_SKILL_INSTALL_COMMAND } from '@/lib/orchestration-install-command'
|
||||
import {
|
||||
ORCHESTRATION_SKILL_INSTALL_COMMAND,
|
||||
ORCHESTRATION_SKILL_UPDATE_COMMAND
|
||||
} from '@/lib/orchestration-install-command'
|
||||
import type { InstalledAgentSkillState } from '@/hooks/useInstalledAgentSkills'
|
||||
import { useActiveProjectSkillRuntime } from '@/hooks/useActiveProjectSkillRuntime'
|
||||
import { AgentSkillSetupPanel } from './AgentSkillSetupPanel'
|
||||
import {
|
||||
buildSkillInstallCommandForRuntime,
|
||||
buildSkillCommandForRuntime,
|
||||
ensureWslCliAvailableForAgentSkillTerminal,
|
||||
getWslCliDistroRequest
|
||||
} from './CliSkillRuntimeSetup'
|
||||
|
|
@ -24,11 +27,18 @@ export function OrchestrationSetupCard(props: {
|
|||
const activeSkillRuntime = useActiveProjectSkillRuntime()
|
||||
const installCommand =
|
||||
activeSkillRuntime.agentRuntime && !activeSkillRuntime.installDisabledReason
|
||||
? buildSkillInstallCommandForRuntime(
|
||||
? buildSkillCommandForRuntime(
|
||||
ORCHESTRATION_SKILL_INSTALL_COMMAND,
|
||||
activeSkillRuntime.agentRuntime
|
||||
)
|
||||
: ORCHESTRATION_SKILL_INSTALL_COMMAND
|
||||
const updateCommand =
|
||||
activeSkillRuntime.agentRuntime && !activeSkillRuntime.installDisabledReason
|
||||
? buildSkillCommandForRuntime(
|
||||
ORCHESTRATION_SKILL_UPDATE_COMMAND,
|
||||
activeSkillRuntime.agentRuntime
|
||||
)
|
||||
: ORCHESTRATION_SKILL_UPDATE_COMMAND
|
||||
|
||||
const setupPanel = (
|
||||
<AgentSkillSetupPanel
|
||||
|
|
@ -42,6 +52,7 @@ export function OrchestrationSetupCard(props: {
|
|||
'Enables agents to hand off context and coordinate work through Orca.'
|
||||
)}
|
||||
command={installCommand}
|
||||
installedCommand={updateCommand}
|
||||
terminalTitle="Orchestration setup"
|
||||
terminalAriaLabel="Orchestration skill install terminal"
|
||||
terminalWorktreeId="feature-wall-orchestration-skill-terminal"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,139 @@
|
|||
import { readdirSync, readFileSync, statSync } from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
const repoRoot = path.resolve(fileURLToPath(new URL('../../../../../', import.meta.url)))
|
||||
const componentsRoot = path.join(repoRoot, 'src/renderer/src/components')
|
||||
|
||||
const updateCapableCallers = new Map<string, readonly string[]>([
|
||||
[
|
||||
'src/renderer/src/components/settings/OrchestrationPane.tsx',
|
||||
['ORCHESTRATION_SKILL_UPDATE_COMMAND', 'installedCommand={orchestrationUpdateCommand}']
|
||||
],
|
||||
[
|
||||
'src/renderer/src/components/settings/OrchestrationSetupCard.tsx',
|
||||
['ORCHESTRATION_SKILL_UPDATE_COMMAND', 'installedCommand={updateCommand}']
|
||||
],
|
||||
[
|
||||
'src/renderer/src/components/floating-terminal/FloatingTerminalOrchestrationDialog.tsx',
|
||||
['ORCHESTRATION_SKILL_UPDATE_COMMAND', 'installedCommand={updateCommand}']
|
||||
],
|
||||
[
|
||||
'src/renderer/src/components/settings/ComputerUseSkillSetupPanel.tsx',
|
||||
['COMPUTER_USE_SKILL_UPDATE_COMMAND', 'installedCommand={updateCommand}']
|
||||
],
|
||||
[
|
||||
'src/renderer/src/components/settings/CliAgentSkillSetup.tsx',
|
||||
['ORCA_CLI_SKILL_UPDATE_COMMAND', 'installedCommand={cliSkillUpdateCommand}']
|
||||
],
|
||||
[
|
||||
'src/renderer/src/components/settings/CliSection.tsx',
|
||||
['ORCA_CLI_SKILL_UPDATE_COMMAND', 'installedCommand={cliSkillUpdateCommand}']
|
||||
],
|
||||
[
|
||||
'src/renderer/src/components/settings/BrowserUsePane.tsx',
|
||||
['ORCA_CLI_SKILL_UPDATE_COMMAND', 'installedCommand={browserUseUpdateCommand}']
|
||||
],
|
||||
[
|
||||
'src/renderer/src/components/settings/BrowserUseSkillStep.tsx',
|
||||
['installedCommand={installedCommand}']
|
||||
],
|
||||
[
|
||||
'src/renderer/src/components/feature-wall/BrowserUseSkillSetupCard.tsx',
|
||||
['ORCA_CLI_SKILL_UPDATE_COMMAND', 'installedCommand={updateCommand}']
|
||||
],
|
||||
[
|
||||
'src/renderer/src/components/sidebar/LinearAgentSkillSetupPrompt.tsx',
|
||||
['LINEAR_TICKETS_SKILL_UPDATE_COMMAND', 'installedCommand={installedCommand}']
|
||||
],
|
||||
[
|
||||
'src/renderer/src/components/sidebar/LinearAgentSkillSetupDialog.tsx',
|
||||
['installedCommand={installedCommand}']
|
||||
],
|
||||
[
|
||||
'src/renderer/src/components/settings/MobileEmulatorAgentControlRow.tsx',
|
||||
['ORCA_CLI_SKILL_UPDATE_COMMAND', 'installedCommand={ORCA_CLI_SKILL_UPDATE_COMMAND}']
|
||||
]
|
||||
])
|
||||
|
||||
const installOnlyCallers = new Map<string, readonly string[]>([
|
||||
[
|
||||
'src/renderer/src/components/emulator-pane/MobileEmulatorAgentSetupGuideSteps.tsx',
|
||||
['showInstallWhenInstalled={!setup.cliSkillInstalled}']
|
||||
]
|
||||
])
|
||||
|
||||
const directPanelCallers = new Set([
|
||||
// BrowserUsePane and LinearAgentSkillSetupPrompt delegate through child setup
|
||||
// components that forward installedCommand and are validated separately above.
|
||||
...[...updateCapableCallers.keys()].filter(
|
||||
(relativePath) =>
|
||||
relativePath !== 'src/renderer/src/components/settings/BrowserUsePane.tsx' &&
|
||||
relativePath !== 'src/renderer/src/components/sidebar/LinearAgentSkillSetupPrompt.tsx'
|
||||
),
|
||||
...installOnlyCallers.keys()
|
||||
])
|
||||
|
||||
function relativeRepoPath(filePath: string): string {
|
||||
return path.relative(repoRoot, filePath).split(path.sep).join('/')
|
||||
}
|
||||
|
||||
function readRepoFile(relativePath: string): string {
|
||||
return readFileSync(path.join(repoRoot, relativePath), 'utf8')
|
||||
}
|
||||
|
||||
function findProductionPanelCallers(dir: string): string[] {
|
||||
const found: string[] = []
|
||||
for (const entry of readdirSync(dir)) {
|
||||
const entryPath = path.join(dir, entry)
|
||||
const stat = statSync(entryPath)
|
||||
if (stat.isDirectory()) {
|
||||
found.push(...findProductionPanelCallers(entryPath))
|
||||
continue
|
||||
}
|
||||
if (!entryPath.endsWith('.tsx') || entryPath.includes('.test.')) {
|
||||
continue
|
||||
}
|
||||
const source = readFileSync(entryPath, 'utf8')
|
||||
if (source.includes('<AgentSkillSetupPanel')) {
|
||||
found.push(relativeRepoPath(entryPath))
|
||||
}
|
||||
}
|
||||
return found.sort()
|
||||
}
|
||||
|
||||
describe('AgentSkillSetupPanel installed-command call sites', () => {
|
||||
it('keeps every update-capable production caller on an explicit single-skill update command', () => {
|
||||
for (const [relativePath, expectedSnippets] of updateCapableCallers) {
|
||||
const source = readRepoFile(relativePath)
|
||||
for (const snippet of expectedSnippets) {
|
||||
expect(source, `${relativePath} should include ${snippet}`).toContain(snippet)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('keeps orchestration installed updates on the primary panel only', () => {
|
||||
const source = readRepoFile('src/renderer/src/components/settings/OrchestrationPane.tsx')
|
||||
|
||||
expect(source).toContain('installedCommand={orchestrationUpdateCommand}')
|
||||
expect(source).not.toContain('Copy update command')
|
||||
expect(source).not.toContain('copyUpdateCommand')
|
||||
})
|
||||
|
||||
it('fails when a production caller can show the default Update action without installedCommand', () => {
|
||||
const productionCallers = findProductionPanelCallers(componentsRoot)
|
||||
|
||||
expect(productionCallers).toEqual([...directPanelCallers].sort())
|
||||
|
||||
for (const [relativePath, expectedSnippets] of installOnlyCallers) {
|
||||
const source = readRepoFile(relativePath)
|
||||
expect(source, `${relativePath} intentionally hides the installed action`).not.toContain(
|
||||
'installedCommand='
|
||||
)
|
||||
for (const snippet of expectedSnippets) {
|
||||
expect(source, `${relativePath} should include ${snippet}`).toContain(snippet)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
@ -25,6 +25,7 @@ type LinearAgentSkillSetupDialogProps = {
|
|||
successDescription: string
|
||||
missingLabel: string
|
||||
command: string
|
||||
installedCommand: string
|
||||
terminalShellOverride?: string
|
||||
installed: boolean
|
||||
loading: boolean
|
||||
|
|
@ -44,6 +45,7 @@ export function LinearAgentSkillSetupDialog({
|
|||
successDescription,
|
||||
missingLabel,
|
||||
command,
|
||||
installedCommand,
|
||||
terminalShellOverride,
|
||||
installed,
|
||||
loading,
|
||||
|
|
@ -124,6 +126,7 @@ export function LinearAgentSkillSetupDialog({
|
|||
)}
|
||||
description={missingLabel}
|
||||
command={command}
|
||||
installedCommand={installedCommand}
|
||||
terminalTitle={translate(
|
||||
'auto.components.sidebar.LinearAgentSkillSetupPrompt.terminalTitle',
|
||||
'Install Linear agent skill'
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ vi.mock('@/lib/agent-skill-cli-prerequisite', () => ({
|
|||
}))
|
||||
|
||||
vi.mock('../settings/CliSkillRuntimeSetup', () => ({
|
||||
buildSkillInstallCommandForRuntime: (
|
||||
buildSkillCommandForRuntime: (
|
||||
command: string,
|
||||
runtime: { runtime: string; wslDistro?: string | null }
|
||||
) =>
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ vi.mock('@/lib/agent-skill-cli-prerequisite', () => ({
|
|||
}))
|
||||
|
||||
vi.mock('../settings/CliSkillRuntimeSetup', () => ({
|
||||
buildSkillInstallCommandForRuntime: (
|
||||
buildSkillCommandForRuntime: (
|
||||
command: string,
|
||||
runtime: { runtime: string; wslDistro?: string | null }
|
||||
) =>
|
||||
|
|
@ -348,6 +348,8 @@ 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 linear-tickets --global'",
|
||||
terminalShellOverride: 'powershell.exe',
|
||||
getPrerequisiteStatus: expect.any(Function)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -8,8 +8,9 @@ import {
|
|||
useInstalledAgentSkill
|
||||
} from '@/hooks/useInstalledAgentSkills'
|
||||
import {
|
||||
LINEAR_TICKETS_SKILL_INSTALL_COMMAND,
|
||||
LINEAR_TICKETS_SKILL_NAME,
|
||||
buildAgentFeatureSkillInstallCommand
|
||||
LINEAR_TICKETS_SKILL_UPDATE_COMMAND
|
||||
} from '@/lib/agent-feature-install-commands'
|
||||
import {
|
||||
ensureOrcaCliAvailableForAgentSkillTerminal,
|
||||
|
|
@ -17,7 +18,7 @@ import {
|
|||
} from '@/lib/agent-skill-cli-prerequisite'
|
||||
import { cn } from '@/lib/utils'
|
||||
import {
|
||||
buildSkillInstallCommandForRuntime,
|
||||
buildSkillCommandForRuntime,
|
||||
ensureWslCliAvailableForAgentSkillTerminal,
|
||||
getWslCliDistroRequest
|
||||
} from '../settings/CliSkillRuntimeSetup'
|
||||
|
|
@ -110,11 +111,11 @@ export function LinearAgentSkillSetupPrompt({
|
|||
sourceKinds: GLOBAL_AGENT_SKILL_SOURCE_KINDS
|
||||
})
|
||||
const command = useMemo(
|
||||
() =>
|
||||
buildSkillInstallCommandForRuntime(
|
||||
buildAgentFeatureSkillInstallCommand([LINEAR_TICKETS_SKILL_NAME]),
|
||||
agentRuntime
|
||||
),
|
||||
() => buildSkillCommandForRuntime(LINEAR_TICKETS_SKILL_INSTALL_COMMAND, agentRuntime),
|
||||
[agentRuntime]
|
||||
)
|
||||
const installedCommand = useMemo(
|
||||
() => buildSkillCommandForRuntime(LINEAR_TICKETS_SKILL_UPDATE_COMMAND, agentRuntime),
|
||||
[agentRuntime]
|
||||
)
|
||||
const terminalShellOverride = getLinearPromptTerminalShellOverride(
|
||||
|
|
@ -277,6 +278,7 @@ export function LinearAgentSkillSetupPrompt({
|
|||
successDescription={successDescription}
|
||||
missingLabel={missingLabel}
|
||||
command={command}
|
||||
installedCommand={installedCommand}
|
||||
terminalShellOverride={terminalShellOverride}
|
||||
installed={skill.installed}
|
||||
loading={showCheckingModal || cliLoading || skill.loading}
|
||||
|
|
|
|||
|
|
@ -4322,15 +4322,19 @@
|
|||
"f97b986b7f": "wsl"
|
||||
},
|
||||
"AgentSkillSetupPanel": {
|
||||
"0b810ec59f": "Press Enter to run the install command.",
|
||||
"0b810ec59f": "Press Enter to run the command.",
|
||||
"ed197f59a2": "Copy command",
|
||||
"817d3f9f18": "Copy install command",
|
||||
"817d3f9f18": "Copy command",
|
||||
"5289300939": "Not installed",
|
||||
"9fcebceb2a": "Installed",
|
||||
"68a468752e": "Checking...",
|
||||
"c689392435": "Re-check",
|
||||
"a31e2aa302": "Failed to copy install command.",
|
||||
"378ad26865": "Copied install command."
|
||||
"a31e2aa302": "Failed to copy command.",
|
||||
"378ad26865": "Copied command.",
|
||||
"copiedCommand": "Copied command.",
|
||||
"failedToCopyCommand": "Failed to copy command.",
|
||||
"copyCommandAria": "Copy command",
|
||||
"runCommandDescription": "Press Enter to run the command."
|
||||
},
|
||||
"AgentsPane": {
|
||||
"d83834f5e6": "Detecting installed agents…",
|
||||
|
|
|
|||
|
|
@ -4322,15 +4322,19 @@
|
|||
"f97b986b7f": "wsl"
|
||||
},
|
||||
"AgentSkillSetupPanel": {
|
||||
"0b810ec59f": "Presione Entrar para ejecutar el comando de instalación.",
|
||||
"0b810ec59f": "Press Enter to run the command.",
|
||||
"ed197f59a2": "comando copiar",
|
||||
"817d3f9f18": "Copiar comando de instalación",
|
||||
"817d3f9f18": "Copy command",
|
||||
"5289300939": "No instalado",
|
||||
"9fcebceb2a": "Instalado",
|
||||
"68a468752e": "De cheques...",
|
||||
"c689392435": "Vuelva a comprobar",
|
||||
"a31e2aa302": "No se pudo copiar el comando de instalación.",
|
||||
"378ad26865": "Comando de instalación copiado."
|
||||
"a31e2aa302": "Failed to copy command.",
|
||||
"378ad26865": "Copied command.",
|
||||
"copiedCommand": "Copied command.",
|
||||
"failedToCopyCommand": "Failed to copy command.",
|
||||
"copyCommandAria": "Copy command",
|
||||
"runCommandDescription": "Press Enter to run the command."
|
||||
},
|
||||
"AgentsPane": {
|
||||
"d83834f5e6": "Detectando agents instalados…",
|
||||
|
|
|
|||
|
|
@ -4307,15 +4307,19 @@
|
|||
"f97b986b7f": "wsl"
|
||||
},
|
||||
"AgentSkillSetupPanel": {
|
||||
"0b810ec59f": "Enter を押してインストール コマンドを実行します。",
|
||||
"0b810ec59f": "Press Enter to run the command.",
|
||||
"ed197f59a2": "コピーコマンド",
|
||||
"817d3f9f18": "インストールコマンドのコピー",
|
||||
"817d3f9f18": "Copy command",
|
||||
"5289300939": "未インストール",
|
||||
"9fcebceb2a": "インストール済み",
|
||||
"68a468752e": "確認中...",
|
||||
"c689392435": "再確認",
|
||||
"a31e2aa302": "インストールコマンドのコピーに失敗しました。",
|
||||
"378ad26865": "インストールコマンドをコピーしました。"
|
||||
"a31e2aa302": "Failed to copy command.",
|
||||
"378ad26865": "Copied command.",
|
||||
"copiedCommand": "Copied command.",
|
||||
"failedToCopyCommand": "Failed to copy command.",
|
||||
"copyCommandAria": "Copy command",
|
||||
"runCommandDescription": "Press Enter to run the command."
|
||||
},
|
||||
"AgentsPane": {
|
||||
"d83834f5e6": "インストールされている agents を検出しています…",
|
||||
|
|
|
|||
|
|
@ -4307,15 +4307,19 @@
|
|||
"f97b986b7f": "wsl"
|
||||
},
|
||||
"AgentSkillSetupPanel": {
|
||||
"0b810ec59f": "Enter를 눌러 설치 명령을 실행하십시오.",
|
||||
"0b810ec59f": "Press Enter to run the command.",
|
||||
"ed197f59a2": "명령 복사",
|
||||
"817d3f9f18": "설치 명령 복사",
|
||||
"817d3f9f18": "Copy command",
|
||||
"5289300939": "설치되지 않음",
|
||||
"9fcebceb2a": "설치됨",
|
||||
"68a468752e": "확인 중...",
|
||||
"c689392435": "재확인",
|
||||
"a31e2aa302": "설치 명령을 복사하지 못했습니다.",
|
||||
"378ad26865": "설치 명령을 복사했습니다."
|
||||
"a31e2aa302": "Failed to copy command.",
|
||||
"378ad26865": "Copied command.",
|
||||
"copiedCommand": "Copied command.",
|
||||
"failedToCopyCommand": "Failed to copy command.",
|
||||
"copyCommandAria": "Copy command",
|
||||
"runCommandDescription": "Press Enter to run the command."
|
||||
},
|
||||
"AgentsPane": {
|
||||
"d83834f5e6": "설치된 agents 감지 중…",
|
||||
|
|
|
|||
|
|
@ -4307,15 +4307,19 @@
|
|||
"f97b986b7f": "wsl"
|
||||
},
|
||||
"AgentSkillSetupPanel": {
|
||||
"0b810ec59f": "按 Enter 运行安装命令。",
|
||||
"0b810ec59f": "Press Enter to run the command.",
|
||||
"ed197f59a2": "复制命令",
|
||||
"817d3f9f18": "复制安装命令",
|
||||
"817d3f9f18": "Copy command",
|
||||
"5289300939": "未安装",
|
||||
"9fcebceb2a": "已安装",
|
||||
"68a468752e": "检查中...",
|
||||
"c689392435": "重新检查",
|
||||
"a31e2aa302": "无法复制安装命令。",
|
||||
"378ad26865": "复制安装命令。"
|
||||
"a31e2aa302": "Failed to copy command.",
|
||||
"378ad26865": "Copied command.",
|
||||
"copiedCommand": "Copied command.",
|
||||
"failedToCopyCommand": "Failed to copy command.",
|
||||
"copyCommandAria": "Copy command",
|
||||
"runCommandDescription": "Press Enter to run the command."
|
||||
},
|
||||
"AgentsPane": {
|
||||
"d83834f5e6": "检测已安装的 Agent...",
|
||||
|
|
|
|||
|
|
@ -1,11 +1,16 @@
|
|||
export {
|
||||
buildAgentFeatureSkillInstallCommand,
|
||||
buildAgentFeatureSkillUpdateCommand,
|
||||
COMPUTER_USE_SKILL_INSTALL_COMMAND,
|
||||
COMPUTER_USE_SKILL_NAME,
|
||||
COMPUTER_USE_SKILL_UPDATE_COMMAND,
|
||||
LINEAR_TICKETS_SKILL_INSTALL_COMMAND,
|
||||
LINEAR_TICKETS_SKILL_NAME,
|
||||
LINEAR_TICKETS_SKILL_UPDATE_COMMAND,
|
||||
ORCA_CLI_SKILL_INSTALL_COMMAND,
|
||||
ORCA_CLI_ORCHESTRATION_SKILL_INSTALL_COMMAND,
|
||||
ORCA_CLI_SKILL_NAME,
|
||||
ORCHESTRATION_SKILL_NAME
|
||||
ORCA_CLI_SKILL_UPDATE_COMMAND,
|
||||
ORCHESTRATION_SKILL_NAME,
|
||||
ORCHESTRATION_SKILL_UPDATE_COMMAND
|
||||
} from '../../../shared/agent-feature-install-commands'
|
||||
|
|
|
|||
|
|
@ -1 +1,4 @@
|
|||
export { ORCHESTRATION_SKILL_INSTALL_COMMAND } from '../../../shared/agent-feature-install-commands'
|
||||
export {
|
||||
ORCHESTRATION_SKILL_INSTALL_COMMAND,
|
||||
ORCHESTRATION_SKILL_UPDATE_COMMAND
|
||||
} from '../../../shared/agent-feature-install-commands'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
buildAgentFeatureSkillInstallCommand,
|
||||
buildAgentFeatureSkillUpdateCommand,
|
||||
COMPUTER_USE_SKILL_UPDATE_COMMAND,
|
||||
LINEAR_TICKETS_SKILL_UPDATE_COMMAND,
|
||||
ORCA_CLI_ORCHESTRATION_SKILL_INSTALL_COMMAND,
|
||||
ORCA_CLI_SKILL_UPDATE_COMMAND,
|
||||
ORCHESTRATION_SKILL_UPDATE_COMMAND
|
||||
} from './agent-feature-install-commands'
|
||||
|
||||
describe('agent feature skill commands', () => {
|
||||
it('builds single-skill update commands', () => {
|
||||
expect(buildAgentFeatureSkillUpdateCommand('orchestration')).toBe(
|
||||
'npx skills update orchestration --global'
|
||||
)
|
||||
})
|
||||
|
||||
it('trims and rejects blank update skill names', () => {
|
||||
expect(buildAgentFeatureSkillUpdateCommand(' orca-cli ')).toBe(
|
||||
'npx skills update orca-cli --global'
|
||||
)
|
||||
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(LINEAR_TICKETS_SKILL_UPDATE_COMMAND).toBe('npx skills update linear-tickets --global')
|
||||
expect(ORCA_CLI_ORCHESTRATION_SKILL_INSTALL_COMMAND).toBe(
|
||||
buildAgentFeatureSkillInstallCommand(['orca-cli', 'orchestration'])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
@ -12,18 +12,35 @@ export function buildAgentFeatureSkillInstallCommand(skillNames: readonly string
|
|||
return `npx skills add ${ORCA_SKILLS_REPOSITORY_URL} --skill ${skillNames.join(' ')} --global`
|
||||
}
|
||||
|
||||
export function buildAgentFeatureSkillUpdateCommand(skillName: string): string {
|
||||
const trimmedSkillName = skillName.trim()
|
||||
if (!trimmedSkillName) {
|
||||
throw new Error('A skill name is required.')
|
||||
}
|
||||
return `npx skills update ${trimmedSkillName} --global`
|
||||
}
|
||||
|
||||
export const ORCA_CLI_SKILL_INSTALL_COMMAND = buildAgentFeatureSkillInstallCommand([
|
||||
ORCA_CLI_SKILL_NAME
|
||||
])
|
||||
|
||||
export const ORCA_CLI_SKILL_UPDATE_COMMAND =
|
||||
buildAgentFeatureSkillUpdateCommand(ORCA_CLI_SKILL_NAME)
|
||||
|
||||
export const COMPUTER_USE_SKILL_INSTALL_COMMAND = buildAgentFeatureSkillInstallCommand([
|
||||
COMPUTER_USE_SKILL_NAME
|
||||
])
|
||||
|
||||
export const COMPUTER_USE_SKILL_UPDATE_COMMAND =
|
||||
buildAgentFeatureSkillUpdateCommand(COMPUTER_USE_SKILL_NAME)
|
||||
|
||||
export const ORCHESTRATION_SKILL_INSTALL_COMMAND = buildAgentFeatureSkillInstallCommand([
|
||||
ORCHESTRATION_SKILL_NAME
|
||||
])
|
||||
|
||||
export const ORCHESTRATION_SKILL_UPDATE_COMMAND =
|
||||
buildAgentFeatureSkillUpdateCommand(ORCHESTRATION_SKILL_NAME)
|
||||
|
||||
export const ORCA_CLI_ORCHESTRATION_SKILL_INSTALL_COMMAND = buildAgentFeatureSkillInstallCommand([
|
||||
ORCA_CLI_SKILL_NAME,
|
||||
ORCHESTRATION_SKILL_NAME
|
||||
|
|
@ -32,3 +49,6 @@ export const ORCA_CLI_ORCHESTRATION_SKILL_INSTALL_COMMAND = buildAgentFeatureSki
|
|||
export const LINEAR_TICKETS_SKILL_INSTALL_COMMAND = buildAgentFeatureSkillInstallCommand([
|
||||
LINEAR_TICKETS_SKILL_NAME
|
||||
])
|
||||
|
||||
export const LINEAR_TICKETS_SKILL_UPDATE_COMMAND =
|
||||
buildAgentFeatureSkillUpdateCommand(LINEAR_TICKETS_SKILL_NAME)
|
||||
|
|
|
|||
Loading…
Reference in New Issue