orca/src/renderer/src/components/settings/CliSkillRuntimeSetup.tsx

217 lines
6.5 KiB
TypeScript

import type { GlobalSettings } from '../../../../shared/types'
import {
deriveGlobalWindowsRuntimeDefaultFromLegacySettings,
normalizeGlobalWindowsRuntimeDefault
} from '../../../../shared/project-execution-runtime'
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 {
isOrcaCliAvailableOnPath,
showOrcaCliRegistrationPromptToast
} from '@/lib/agent-skill-cli-prerequisite'
import { translate } from '@/i18n/i18n'
export type LocalAgentRuntime = {
runtime: 'host' | 'wsl'
wslDistro?: string | null
label: string
}
const LOCAL_HOST_AGENT_RUNTIME: LocalAgentRuntime = {
runtime: 'host',
label: ''
}
export function getHostRuntimeLabel(): string {
return navigator.userAgent.includes('Windows') ? 'Windows' : 'This device'
}
export function getSelectedAgentRuntime(
settings: GlobalSettings,
wslSupportedPlatform: boolean,
wslAvailable: boolean,
wslCapabilitiesLoading: boolean
): LocalAgentRuntime {
const defaultRuntime = normalizeGlobalWindowsRuntimeDefault(
settings.localWindowsRuntimeDefault ??
deriveGlobalWindowsRuntimeDefaultFromLegacySettings(settings, {
wslAvailable: wslCapabilitiesLoading ? undefined : wslAvailable
}).defaultRuntime
)
if (wslSupportedPlatform && defaultRuntime.kind === 'wsl') {
const selectedDistro = defaultRuntime.distro?.trim() || null
return {
runtime: 'wsl',
wslDistro: selectedDistro,
label: selectedDistro
? `WSL ${selectedDistro}`
: translate('auto.components.settings.CliSkillRuntimeSetup.c47127f222', 'WSL default')
}
}
return { runtime: 'host', label: getHostRuntimeLabel() }
}
function quotePowerShellSingle(value: string): string {
return `'${value.replaceAll("'", "''")}'`
}
export function getWslCliDistroRequest(
runtime?: LocalAgentRuntime
): { distro: string } | undefined {
return runtime?.runtime === 'wsl' && runtime.wslDistro?.trim()
? { distro: runtime.wslDistro.trim() }
: undefined
}
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 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(
command: string,
runtime: LocalAgentRuntime
): string {
return buildSkillCommandForRuntime(command, runtime)
}
export function getSkillDiscoveryTargetForRuntime(
runtime: LocalAgentRuntime
): { runtime: 'wsl'; wslDistro?: string | null } | undefined {
return runtime.runtime === 'wsl'
? { runtime: 'wsl', wslDistro: runtime.wslDistro ?? null }
: undefined
}
export function getAgentSkillTerminalShellOverride(
currentPlatform: string,
settings: GlobalSettings,
runtime: LocalAgentRuntime
): string | undefined {
if (currentPlatform !== 'win32') {
return undefined
}
if (runtime.runtime === 'wsl') {
return 'powershell.exe'
}
return settings.terminalWindowsShell.toLowerCase() === 'wsl.exe' ? 'powershell.exe' : undefined
}
export async function ensureWslCliAvailableForAgentSkillTerminal(
runtime?: LocalAgentRuntime
): Promise<CliInstallStatus | null> {
const args = getWslCliDistroRequest(runtime)
try {
const status = await window.api.cli.getWslInstallStatus(args)
if (!status.supported) {
toast.warning(
translate(
'auto.components.settings.CliSkillRuntimeSetup.775a4cfbb8',
'WSL shell command registration is unavailable'
),
{
description:
status.detail ??
translate(
'auto.components.settings.CliSkillRuntimeSetup.fc0fcf72fd',
'Register the WSL shell command before skill setup.'
)
}
)
return status
}
if (status.state !== 'installed' || !status.pathConfigured) {
await showOrcaCliRegistrationPromptToast()
const next = await window.api.cli.installWsl(args)
if (!isOrcaCliAvailableOnPath(next)) {
toast.warning(
translate(
'auto.components.settings.CliSkillRuntimeSetup.3728a94fb6',
'WSL shell command needs attention'
),
{
description:
next.detail ??
translate(
'auto.components.settings.CliSkillRuntimeSetup.fc0fcf72fd',
'Register the WSL shell command before skill setup.'
)
}
)
}
return next
}
return status
} catch (error) {
toast.error(
error instanceof Error
? error.message
: translate(
'auto.components.settings.CliSkillRuntimeSetup.0ed08febc5',
'Failed to register the WSL shell command.'
)
)
return null
}
}