[P2] fix(skills): keep the npx preflight in the forced-PowerShell setup terminal on Windows (#11814)

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Neil 2026-07-31 14:03:26 -07:00 committed by GitHub
parent 81fee4d6a4
commit ea40c87315
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 115 additions and 7 deletions

View File

@ -3,7 +3,10 @@ import { toast } from 'sonner'
import { Button } from '@/components/ui/button'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
import { OnboardingInlineCommandTerminal } from '@/components/onboarding/OnboardingInlineCommandTerminal'
import { buildSkillCommandForRuntime } from '@/components/settings/CliSkillRuntimeSetup'
import {
buildSkillCommandForRuntime,
buildSkillSetupTerminalCommand
} from '@/components/settings/CliSkillRuntimeSetup'
import { ORCA_CLI_ORCHESTRATION_SKILL_INSTALL_COMMAND } from '@/lib/agent-feature-install-commands'
import { useActiveProjectSkillRuntime } from '@/hooks/useActiveProjectSkillRuntime'
import { translate } from '@/i18n/i18n'
@ -18,6 +21,11 @@ export function CliSkillSetupTerminal(): React.JSX.Element {
ORCA_CLI_ORCHESTRATION_SKILL_INSTALL_COMMAND,
activeSkillRuntime.installDisabledReason ? undefined : activeSkillRuntime.agentRuntime
)
// The copied string stays as built; only what we execute is adapted.
const setupTerminalCommand = buildSkillSetupTerminalCommand(
skillCommand,
activeSkillRuntime.terminalShellOverride
)
const handleCopySkillCommand = async (): Promise<void> => {
try {
@ -70,7 +78,7 @@ export function CliSkillSetupTerminal(): React.JSX.Element {
</Tooltip>
</div>
<OnboardingInlineCommandTerminal
command={skillCommand}
command={setupTerminalCommand}
title={translate(
'auto.components.feature.tips.CliSkillSetupTerminal.84e9576dac',
'Skill setup'

View File

@ -37,6 +37,7 @@ vi.mock('@/hooks/useInstalledAgentSkills', () => ({
vi.mock('@/components/settings/CliSkillRuntimeSetup', () => ({
buildSkillCommandForRuntime: (command: string) => command,
buildSkillSetupTerminalCommand: (command: string) => command,
ensureWslCliAvailableForAgentSkillTerminal: vi.fn(),
getWslCliDistroRequest: () => undefined
}))

View File

@ -5,6 +5,7 @@ import { IntegrationStatusPill } from '../integration-status-pill'
import { SkillFreshnessStatusPill } from '../skills/SkillFreshnessStatusPill'
import { OnboardingInlineCommandTerminal } from '../onboarding/OnboardingInlineCommandTerminal'
import { AgentSkillSetupFailureNotice } from './AgentSkillSetupFailureNotice'
import { buildSkillSetupTerminalCommand } from './CliSkillRuntimeSetup'
import type { AgentSkillSetupPanelProps } from './agent-skill-setup-panel-props'
import { Button } from '../ui/button'
import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip'
@ -386,10 +387,11 @@ export function AgentSkillSetupPanel({
</TooltipContent>
</Tooltip>
</div>
{/* The copied string above stays as built; only what we run is adapted. */}
<OnboardingInlineCommandTerminal
key={terminalAttempt}
worktreeId={terminalWorktreeId}
command={openTerminalCommand}
command={buildSkillSetupTerminalCommand(openTerminalCommand, terminalShellOverride)}
title={terminalTitle}
description={translate(
'auto.components.settings.AgentSkillSetupPanel.runCommandDescription',

View File

@ -10,6 +10,7 @@ import { useAppStore } from '@/store'
import {
buildSkillCommandForRuntime,
buildSkillInstallCommandForRuntime,
buildSkillSetupTerminalCommand,
getAgentSkillTerminalShellOverride,
getSelectedAgentRuntime,
getSkillDiscoveryTargetForRuntime
@ -272,6 +273,60 @@ describe('CliSkillRuntimeSetup runtime helpers', () => {
}
})
it('keeps the npx preflight in the PowerShell-forced setup terminal', () => {
const installCommand = buildAgentFeatureSkillInstallCommand(['orchestration'])
const windowsHost = { runtime: 'host', label: 'Windows' } as const
const previous = useAppStore.getState()
useAppStore.setState({
settings: { ...getDefaultSettings('/tmp'), terminalWindowsShell: 'git-bash' }
})
try {
const copied = buildSkillCommandForRuntime(installCommand, windowsHost, 'win32')
expect(copied).toBe(installCommand)
// Orca forces its own setup terminal to powershell.exe, where cmd.exe works.
expect(buildSkillSetupTerminalCommand(copied, 'powershell.exe', 'win32')).toBe(
`${windowsNpxPreflightPrefix}${windowsNpxGuidance}) else (${installCommand})"`
)
} finally {
useAppStore.setState({ settings: previous.settings })
}
})
it('does not re-wrap the setup terminal command when no shell override applies', () => {
const installCommand = buildAgentFeatureSkillInstallCommand(['orchestration'])
const previous = useAppStore.getState()
useAppStore.setState({
settings: { ...getDefaultSettings('/tmp'), terminalWindowsShell: 'cmd.exe' }
})
try {
const copied = buildSkillCommandForRuntime(
installCommand,
{ runtime: 'host', label: 'Windows' },
'win32'
)
expect(buildSkillSetupTerminalCommand(copied, undefined, 'win32')).toBe(copied)
// An already-wrapped command must not gain a second preflight.
expect(buildSkillSetupTerminalCommand(copied, 'powershell.exe', 'win32')).toBe(copied)
} finally {
useAppStore.setState({ settings: previous.settings })
}
})
it('leaves WSL and non-Windows setup terminal commands untouched', () => {
const wslCommand = buildSkillCommandForRuntime(
'npx skills add orchestration --global',
{ runtime: 'wsl', wslDistro: 'Ubuntu', label: 'WSL Ubuntu' },
'win32'
)
expect(buildSkillSetupTerminalCommand(wslCommand, 'powershell.exe', 'win32')).toBe(wslCommand)
expect(
buildSkillSetupTerminalCommand('npx skills add orchestration --global', undefined, 'linux')
).toBe('npx skills add orchestration --global')
})
it('keeps the bare reinstall rewrite for POSIX-family Windows skill updates', () => {
const installCommand = buildAgentFeatureSkillInstallCommand(['orchestration'])
const previous = useAppStore.getState()

View File

@ -89,7 +89,11 @@ export function buildSkillCommandForRuntime(
currentPlatform
)
if (resolvedRuntime.runtime !== 'wsl') {
return wrapWindowsSkillCommandWithNpxPrerequisite(normalizedCommand, currentPlatform)
return wrapWindowsSkillCommandWithNpxPrerequisite(
normalizedCommand,
currentPlatform,
'copied-command'
)
}
const distroArg = resolvedRuntime.wslDistro?.trim()
@ -127,9 +131,43 @@ function normalizeWindowsSkillUpdateCommand(
return buildAgentFeatureSkillInstallCommand([updateMatch[1]])
}
/**
* Where a built skill command is going: the user's clipboard (their own shell)
* or the setup terminal Orca spawns itself.
*/
type SkillCommandTarget = 'copied-command' | 'orca-setup-terminal'
/**
* Re-adds the npx preflight for Orca's own setup terminal, which
* `getAgentSkillTerminalShellOverride` forces onto powershell.exe. The copied
* string stays bare for POSIX-family shells; only the executed one is wrapped.
*/
export function buildSkillSetupTerminalCommand(
copiedCommand: string,
terminalShellOverride: string | undefined,
currentPlatform = getSkillCommandPlatform()
): string {
if (!isSetupTerminalForcedToPowerShell(terminalShellOverride)) {
return copiedCommand
}
return wrapWindowsSkillCommandWithNpxPrerequisite(
copiedCommand,
currentPlatform,
'orca-setup-terminal'
)
}
function isSetupTerminalForcedToPowerShell(terminalShellOverride: string | undefined): boolean {
const trimmedOverride = terminalShellOverride?.trim()
return (
Boolean(trimmedOverride) && resolveWindowsShellStartupFamily(trimmedOverride) === 'powershell'
)
}
function wrapWindowsSkillCommandWithNpxPrerequisite(
command: string,
currentPlatform: NodeJS.Platform
currentPlatform: NodeJS.Platform,
target: SkillCommandTarget
): string {
const trimmedCommand = command.trim()
if (
@ -140,7 +178,7 @@ function wrapWindowsSkillCommandWithNpxPrerequisite(
// Why: the copied command lands in the user's configured shell, and MSYS
// shells rewrite cmd.exe's leading /d /s /c switches into drive paths,
// starting an interactive cmd session instead of running the payload.
isPosixFamilyWindowsShellConfigured() ||
(target === 'copied-command' && isPosixFamilyWindowsShellConfigured()) ||
!/^npx\s+skills\s+(?:add|update)\b/i.test(trimmedCommand)
) {
return command

View File

@ -170,7 +170,11 @@ describe('AgentSkillSetupPanel installed-command call sites', () => {
)
expect(source).toContain('buildSkillCommandForRuntime(')
expect(source).toContain('command={skillCommand}')
// The copied string stays bare for POSIX-family shells; the forced-PowerShell
// setup terminal keeps the npx preflight.
expect(source).toContain('writeClipboardText(skillCommand)')
expect(source).toContain('buildSkillSetupTerminalCommand(')
expect(source).toContain('command={setupTerminalCommand}')
expect(source).toContain('shellOverride={activeSkillRuntime.terminalShellOverride}')
expect(source).not.toContain('command={ORCA_CLI_ORCHESTRATION_SKILL_INSTALL_COMMAND}')
// This terminal auto-pastes with no install gate, so a repair-required runtime