fix: handle native Windows skill update flow for Orca setup (#6307)
* fix: handle native Windows skill update flow for Orca setup - Rewrite npx skills update <skill> --global to the repo reinstall path on native Windows hosts. - Keep the WSL runtime path unchanged so Linux/WSL setup behavior stays intact. - Update the affected runtime helper tests to cover the Windows fallback. - Adjust the orchestration pane test expectation to match the Windows-host command rewrite. - Scope the change narrowly to the issue path to avoid impacting unrelated skill setup flows. * fix: cover Windows skill update fallbacks --------- Co-authored-by: Jinwoo Hong <73622457+Jinwoo-H@users.noreply.github.com>
This commit is contained in:
parent
4a4d774986
commit
8b32cc00a9
|
|
@ -26,14 +26,12 @@ export function BrowserUseSkillSetupCard(props: {
|
|||
}): JSX.Element {
|
||||
const { compact, terminalHeightPx, skill } = props
|
||||
const activeSkillRuntime = useActiveProjectSkillRuntime()
|
||||
const installCommand =
|
||||
activeSkillRuntime.agentRuntime && !activeSkillRuntime.installDisabledReason
|
||||
? 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 installCommand = !activeSkillRuntime.installDisabledReason
|
||||
? buildSkillCommandForRuntime(ORCA_CLI_SKILL_INSTALL_COMMAND, activeSkillRuntime.agentRuntime)
|
||||
: ORCA_CLI_SKILL_INSTALL_COMMAND
|
||||
const updateCommand = !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')
|
||||
|
|
|
|||
|
|
@ -42,20 +42,18 @@ export function FloatingTerminalOrchestrationDialog({
|
|||
onSetupStateChange
|
||||
}: FloatingTerminalOrchestrationDialogProps): React.JSX.Element {
|
||||
const activeSkillRuntime = useActiveProjectSkillRuntime()
|
||||
const installCommand =
|
||||
activeSkillRuntime.agentRuntime && !activeSkillRuntime.installDisabledReason
|
||||
? 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 installCommand = !activeSkillRuntime.installDisabledReason
|
||||
? buildSkillCommandForRuntime(
|
||||
ORCHESTRATION_SKILL_INSTALL_COMMAND,
|
||||
activeSkillRuntime.agentRuntime
|
||||
)
|
||||
: ORCHESTRATION_SKILL_INSTALL_COMMAND
|
||||
const updateCommand = !activeSkillRuntime.installDisabledReason
|
||||
? buildSkillCommandForRuntime(
|
||||
ORCHESTRATION_SKILL_UPDATE_COMMAND,
|
||||
activeSkillRuntime.agentRuntime
|
||||
)
|
||||
: ORCHESTRATION_SKILL_UPDATE_COMMAND
|
||||
const {
|
||||
installed: orchestrationSkillDetected,
|
||||
loading: orchestrationSkillLoading,
|
||||
|
|
|
|||
|
|
@ -56,14 +56,12 @@ export function BrowserUseSetup({
|
|||
const [cliBusy, setCliBusy] = useState(false)
|
||||
const mountedRef = useMountedRef()
|
||||
const activeSkillRuntime = useActiveProjectSkillRuntime()
|
||||
const browserUseInstallCommand =
|
||||
activeSkillRuntime.agentRuntime && !activeSkillRuntime.installDisabledReason
|
||||
? 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 browserUseInstallCommand = !activeSkillRuntime.installDisabledReason
|
||||
? buildSkillCommandForRuntime(ORCA_CLI_SKILL_INSTALL_COMMAND, activeSkillRuntime.agentRuntime)
|
||||
: ORCA_CLI_SKILL_INSTALL_COMMAND
|
||||
const browserUseUpdateCommand = !activeSkillRuntime.installDisabledReason
|
||||
? buildSkillCommandForRuntime(ORCA_CLI_SKILL_UPDATE_COMMAND, activeSkillRuntime.agentRuntime)
|
||||
: ORCA_CLI_SKILL_UPDATE_COMMAND
|
||||
|
||||
const handleCliStatusChange = useCallback(
|
||||
(nextStatus: CliInstallStatus | null): void => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { describe, expect, it } from 'vitest'
|
||||
import { getDefaultSettings } from '../../../../shared/constants'
|
||||
import { buildAgentFeatureSkillInstallCommand } from '../../../../shared/agent-feature-install-commands'
|
||||
import {
|
||||
buildSkillCommandForRuntime,
|
||||
buildSkillInstallCommandForRuntime,
|
||||
|
|
@ -32,6 +33,38 @@ describe('CliSkillRuntimeSetup runtime helpers', () => {
|
|||
expect(command).toContain('npx skills update orchestration --global')
|
||||
})
|
||||
|
||||
it('reinstalls Windows-host skill updates through the add path', () => {
|
||||
expect(
|
||||
buildSkillCommandForRuntime(
|
||||
'npx skills update orchestration --global',
|
||||
{
|
||||
runtime: 'host',
|
||||
label: 'Windows'
|
||||
},
|
||||
'win32'
|
||||
)
|
||||
).toBe(buildAgentFeatureSkillInstallCommand(['orchestration']))
|
||||
})
|
||||
|
||||
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']))
|
||||
})
|
||||
|
||||
it('keeps non-Windows host skill updates on the update path', () => {
|
||||
expect(
|
||||
buildSkillCommandForRuntime(
|
||||
'npx skills update orchestration --global',
|
||||
{
|
||||
runtime: 'host',
|
||||
label: 'This device'
|
||||
},
|
||||
'linux'
|
||||
)
|
||||
).toBe('npx skills update orchestration --global')
|
||||
})
|
||||
|
||||
it('preserves the selected WSL distro for skill discovery', () => {
|
||||
expect(
|
||||
getSkillDiscoveryTargetForRuntime({
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {
|
|||
buildWslLoginShellCommand,
|
||||
escapeWslShCommandForWindows
|
||||
} from '../../../../shared/wsl-login-shell-command'
|
||||
import { buildAgentFeatureSkillInstallCommand } from '../../../../shared/agent-feature-install-commands'
|
||||
import { toast } from 'sonner'
|
||||
import type { CliInstallStatus } from '../../../../shared/cli-install-types'
|
||||
import {
|
||||
|
|
@ -21,6 +22,11 @@ export type LocalAgentRuntime = {
|
|||
label: string
|
||||
}
|
||||
|
||||
const LOCAL_HOST_AGENT_RUNTIME: LocalAgentRuntime = {
|
||||
runtime: 'host',
|
||||
label: ''
|
||||
}
|
||||
|
||||
export function getHostRuntimeLabel(): string {
|
||||
return navigator.userAgent.includes('Windows') ? 'Windows' : 'This device'
|
||||
}
|
||||
|
|
@ -62,15 +68,64 @@ export function getWslCliDistroRequest(
|
|||
: undefined
|
||||
}
|
||||
|
||||
export function buildSkillCommandForRuntime(command: string, runtime: LocalAgentRuntime): string {
|
||||
if (runtime.runtime !== 'wsl') {
|
||||
export function buildSkillCommandForRuntime(
|
||||
command: string,
|
||||
runtime?: LocalAgentRuntime,
|
||||
currentPlatform = getSkillCommandPlatform()
|
||||
): string {
|
||||
const resolvedRuntime = runtime ?? LOCAL_HOST_AGENT_RUNTIME
|
||||
const normalizedCommand = normalizeWindowsSkillUpdateCommand(
|
||||
command,
|
||||
resolvedRuntime,
|
||||
currentPlatform
|
||||
)
|
||||
if (resolvedRuntime.runtime !== 'wsl') {
|
||||
return normalizedCommand
|
||||
}
|
||||
|
||||
const distroArg = resolvedRuntime.wslDistro?.trim()
|
||||
? ` -d ${quotePowerShellSingle(resolvedRuntime.wslDistro.trim())}`
|
||||
: ''
|
||||
const wslCommand = escapeWslShCommandForWindows(buildWslLoginShellCommand(normalizedCommand))
|
||||
return `wsl.exe${distroArg} -- sh -c ${quotePowerShellSingle(wslCommand)}`
|
||||
}
|
||||
|
||||
function normalizeWindowsSkillUpdateCommand(
|
||||
command: string,
|
||||
runtime: LocalAgentRuntime,
|
||||
currentPlatform: NodeJS.Platform
|
||||
): string {
|
||||
if (runtime.runtime === 'wsl' || currentPlatform !== 'win32') {
|
||||
return command
|
||||
}
|
||||
const distroArg = runtime.wslDistro?.trim()
|
||||
? ` -d ${quotePowerShellSingle(runtime.wslDistro.trim())}`
|
||||
: ''
|
||||
const wslCommand = escapeWslShCommandForWindows(buildWslLoginShellCommand(command))
|
||||
return `wsl.exe${distroArg} -- sh -c ${quotePowerShellSingle(wslCommand)}`
|
||||
|
||||
const trimmedCommand = command.trim()
|
||||
const updateMatch = /^npx\s+skills\s+update\s+([A-Za-z0-9_-]+)\s+--global$/i.exec(trimmedCommand)
|
||||
if (!updateMatch) {
|
||||
return command
|
||||
}
|
||||
|
||||
// Why: the `skills update` subcommand is currently unreliable on native
|
||||
// Windows, while reinstalling from the same repo source is idempotent and
|
||||
// keeps the setup affordance working.
|
||||
return buildAgentFeatureSkillInstallCommand([updateMatch[1]])
|
||||
}
|
||||
|
||||
function getSkillCommandPlatform(): NodeJS.Platform {
|
||||
const platform =
|
||||
typeof window === 'undefined' ? undefined : window.api?.platform?.get?.()?.platform
|
||||
if (platform) {
|
||||
return platform
|
||||
}
|
||||
|
||||
const userAgent = typeof navigator === 'undefined' ? '' : navigator.userAgent
|
||||
if (userAgent.includes('Windows')) {
|
||||
return 'win32'
|
||||
}
|
||||
if (userAgent.includes('Mac')) {
|
||||
return 'darwin'
|
||||
}
|
||||
return 'linux'
|
||||
}
|
||||
|
||||
export function buildSkillInstallCommandForRuntime(
|
||||
|
|
|
|||
|
|
@ -24,20 +24,18 @@ import { translate } from '@/i18n/i18n'
|
|||
|
||||
export function ComputerUseSkillSetupPanel(): React.JSX.Element {
|
||||
const activeSkillRuntime = useActiveProjectSkillRuntime()
|
||||
const installCommand =
|
||||
activeSkillRuntime.agentRuntime && !activeSkillRuntime.installDisabledReason
|
||||
? 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 installCommand = !activeSkillRuntime.installDisabledReason
|
||||
? buildSkillCommandForRuntime(
|
||||
COMPUTER_USE_SKILL_INSTALL_COMMAND,
|
||||
activeSkillRuntime.agentRuntime
|
||||
)
|
||||
: COMPUTER_USE_SKILL_INSTALL_COMMAND
|
||||
const updateCommand = !activeSkillRuntime.installDisabledReason
|
||||
? buildSkillCommandForRuntime(
|
||||
COMPUTER_USE_SKILL_UPDATE_COMMAND,
|
||||
activeSkillRuntime.agentRuntime
|
||||
)
|
||||
: COMPUTER_USE_SKILL_UPDATE_COMMAND
|
||||
const {
|
||||
installed: computerUseSkillDetected,
|
||||
loading: computerUseSkillLoading,
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import {
|
|||
import { cn } from '@/lib/utils'
|
||||
import { useMobileEmulatorAgentSetupState } from '../emulator-pane/use-mobile-emulator-agent-setup-state'
|
||||
import { AgentSkillSetupPanel } from './AgentSkillSetupPanel'
|
||||
import { buildSkillCommandForRuntime } from './CliSkillRuntimeSetup'
|
||||
import { StepBadge } from './BrowserUseStepBadge'
|
||||
import { MobileEmulatorExamples } from './MobileEmulatorExamples'
|
||||
import { Button } from '../ui/button'
|
||||
|
|
@ -25,6 +26,8 @@ const EMULATOR_CLI_COMMANDS = [
|
|||
|
||||
export function MobileEmulatorAgentControlRow(): React.JSX.Element {
|
||||
const setup = useMobileEmulatorAgentSetupState(true)
|
||||
const cliSkillInstallCommand = buildSkillCommandForRuntime(ORCA_CLI_SKILL_INSTALL_COMMAND)
|
||||
const cliSkillUpdateCommand = buildSkillCommandForRuntime(ORCA_CLI_SKILL_UPDATE_COMMAND)
|
||||
|
||||
const handleEnableCli = async (): Promise<void> => {
|
||||
await setup.handleEnableCli()
|
||||
|
|
@ -130,8 +133,8 @@ export function MobileEmulatorAgentControlRow(): React.JSX.Element {
|
|||
'auto.components.settings.MobileEmulatorAgentControlRow.d94ca6a623',
|
||||
'Enables agents to use Orca CLI commands, including mobile emulator control.'
|
||||
)}
|
||||
command={ORCA_CLI_SKILL_INSTALL_COMMAND}
|
||||
installedCommand={ORCA_CLI_SKILL_UPDATE_COMMAND}
|
||||
command={cliSkillInstallCommand}
|
||||
installedCommand={cliSkillUpdateCommand}
|
||||
terminalTitle="Orca CLI skill setup"
|
||||
terminalAriaLabel="Orca CLI skill install terminal"
|
||||
terminalWorktreeId="settings-mobile-emulator-orca-cli-skill-terminal"
|
||||
|
|
|
|||
|
|
@ -3,13 +3,13 @@
|
|||
import { act, type ReactNode } from 'react'
|
||||
import { createRoot, type Root } from 'react-dom/client'
|
||||
import { renderToStaticMarkup } from 'react-dom/server'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
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 UPDATE_COMMAND = 'npx skills update orchestration --global'
|
||||
const UPDATE_COMMAND = INSTALL_COMMAND
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
dialogProps: [] as Record<string, unknown>[],
|
||||
|
|
@ -97,6 +97,31 @@ async function renderPane(): Promise<HTMLDivElement> {
|
|||
}
|
||||
|
||||
describe('OrchestrationPane', () => {
|
||||
beforeEach(() => {
|
||||
Object.defineProperty(window, 'api', {
|
||||
configurable: true,
|
||||
value: {
|
||||
...window.api,
|
||||
platform: {
|
||||
get: () => ({ platform: 'win32', osRelease: 'test' })
|
||||
},
|
||||
wsl: {
|
||||
isAvailable: vi.fn().mockResolvedValue(false),
|
||||
listDistros: vi.fn().mockResolvedValue([])
|
||||
},
|
||||
pwsh: {
|
||||
isAvailable: vi.fn().mockResolvedValue(false)
|
||||
},
|
||||
gitBash: {
|
||||
isAvailable: vi.fn().mockResolvedValue(false)
|
||||
},
|
||||
runtime: {
|
||||
getStatus: vi.fn().mockResolvedValue({ hostPlatform: 'win32' })
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
if (root) {
|
||||
await act(async () => {
|
||||
|
|
|
|||
|
|
@ -44,20 +44,18 @@ export function OrchestrationPane(): React.JSX.Element {
|
|||
const [selectedExampleId, setSelectedExampleId] = useState<string | null>(null)
|
||||
const [skillPromptOpen, setSkillPromptOpen] = useState(false)
|
||||
const activeSkillRuntime = useActiveProjectSkillRuntime()
|
||||
const orchestrationInstallCommand =
|
||||
activeSkillRuntime.agentRuntime && !activeSkillRuntime.installDisabledReason
|
||||
? 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 orchestrationInstallCommand = !activeSkillRuntime.installDisabledReason
|
||||
? buildSkillCommandForRuntime(
|
||||
ORCHESTRATION_SKILL_INSTALL_COMMAND,
|
||||
activeSkillRuntime.agentRuntime
|
||||
)
|
||||
: ORCHESTRATION_SKILL_INSTALL_COMMAND
|
||||
const orchestrationUpdateCommand = !activeSkillRuntime.installDisabledReason
|
||||
? buildSkillCommandForRuntime(
|
||||
ORCHESTRATION_SKILL_UPDATE_COMMAND,
|
||||
activeSkillRuntime.agentRuntime
|
||||
)
|
||||
: ORCHESTRATION_SKILL_UPDATE_COMMAND
|
||||
|
||||
const {
|
||||
installed: orchestrationSkillDetected,
|
||||
|
|
|
|||
|
|
@ -25,20 +25,18 @@ export function OrchestrationSetupCard(props: {
|
|||
}): JSX.Element {
|
||||
const { compact, terminalHeightPx, skill } = props
|
||||
const activeSkillRuntime = useActiveProjectSkillRuntime()
|
||||
const installCommand =
|
||||
activeSkillRuntime.agentRuntime && !activeSkillRuntime.installDisabledReason
|
||||
? 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 installCommand = !activeSkillRuntime.installDisabledReason
|
||||
? buildSkillCommandForRuntime(
|
||||
ORCHESTRATION_SKILL_INSTALL_COMMAND,
|
||||
activeSkillRuntime.agentRuntime
|
||||
)
|
||||
: ORCHESTRATION_SKILL_INSTALL_COMMAND
|
||||
const updateCommand = !activeSkillRuntime.installDisabledReason
|
||||
? buildSkillCommandForRuntime(
|
||||
ORCHESTRATION_SKILL_UPDATE_COMMAND,
|
||||
activeSkillRuntime.agentRuntime
|
||||
)
|
||||
: ORCHESTRATION_SKILL_UPDATE_COMMAND
|
||||
|
||||
const setupPanel = (
|
||||
<AgentSkillSetupPanel
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ const updateCapableCallers = new Map<string, readonly string[]>([
|
|||
],
|
||||
[
|
||||
'src/renderer/src/components/settings/MobileEmulatorAgentControlRow.tsx',
|
||||
['ORCA_CLI_SKILL_UPDATE_COMMAND', 'installedCommand={ORCA_CLI_SKILL_UPDATE_COMMAND}']
|
||||
['ORCA_CLI_SKILL_UPDATE_COMMAND', 'installedCommand={cliSkillUpdateCommand}']
|
||||
]
|
||||
])
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue