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:
Siddiqui Qamar 2026-06-25 20:16:26 -03:00 committed by GitHub
parent 4a4d774986
commit 8b32cc00a9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 188 additions and 84 deletions

View File

@ -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')

View File

@ -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,

View File

@ -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 => {

View File

@ -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({

View File

@ -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(

View File

@ -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,

View File

@ -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"

View File

@ -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 () => {

View File

@ -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,

View File

@ -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

View File

@ -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}']
]
])