fix(openclaw): stabilize windows install and pairing

This commit is contained in:
晴天
2026-06-07 03:28:10 +08:00
parent a458f77c35
commit e16ff2baee
21 changed files with 912 additions and 131 deletions

View File

@@ -49,6 +49,20 @@ function readBoundCliPath(panelConfig) {
return String(panelConfig?.openclawCliPath || '').trim()
}
function quoteCommandPath(path) {
return String(path || '').replace(/"/g, '\\"')
}
function openclawInstallDirForCommand(path) {
const value = String(path || '').trim()
if (!value) return ''
const normalized = value.replace(/\\/g, '/')
if (/\/openclaw(?:\.cmd|\.exe|\.bat|\.ps1|\.js)?$/i.test(normalized)) {
return value.replace(/[\\/][^\\/]+$/, '')
}
return value
}
let _foreignGatewayPromptKey = ''
export function isForeignGatewayService(service) {
@@ -236,14 +250,15 @@ export async function showGatewayConflictGuidance({ error = null, service = null
/** 根据安装来源返回卸载命令 */
function uninstallCommandForSource(source, path) {
const isWin = navigator.platform?.startsWith('Win') || navigator.userAgent?.includes('Windows')
if (source === 'standalone') {
const isWin = navigator.platform?.startsWith('Win') || navigator.userAgent?.includes('Windows')
const p = escapeHtml(path || '')
const p = quoteCommandPath(openclawInstallDirForCommand(path))
return isWin ? `rmdir /s /q "${p}"` : `rm -rf "${p}"`
}
if (source === 'npm-official' || source === 'official') return 'npm uninstall -g openclaw'
// npm-zh, npm-global, and others
return 'npm uninstall -g @qingchencloud/openclaw-zh'
if (source === 'npm-zh' || source === 'npm-global') return 'npm uninstall -g @qingchencloud/openclaw-zh'
const p = quoteCommandPath(path)
return isWin ? `del /f /q "${p}"` : `rm -f "${p}"`
}
/**
@@ -259,6 +274,11 @@ export async function showInstallationCleanup({ onRefresh = null } = {}) {
const installations = dedupeOpenclawInstallations(Array.isArray(versionInfo?.all_installations) ? versionInfo.all_installations : [])
const boundPath = readBoundCliPath(panelConfig)
const currentPath = versionInfo?.cli_path || ''
const hasActiveBoundInstall = installations.some(inst =>
inst?.active
&& boundPath
&& openclawInstallationIdentity({ path: inst.path }) === openclawInstallationIdentity({ path: boundPath })
)
const sourceLabel = (src) => cliSourceLabel(src)
@@ -277,16 +297,21 @@ export async function showInstallationCleanup({ onRefresh = null } = {}) {
const uninstallCmd = uninstallCommandForSource(inst.source, inst.path)
// 操作区:非活跃安装显示卸载命令 + 复制按钮;活跃的显示绑定按钮
// 操作区:所有未绑定安装都可以绑定;非活跃安装额外显示清理命令。
let actions = ''
if (isActive && !isBound) {
actions = `<button class="btn btn-primary btn-xs cleanup-bind-btn" data-path="${escapeHtml(inst.path)}" style="margin-top:8px">${t('services.cleanupBindThis')}</button>`
} else if (!isActive) {
const shouldOfferBind = !isBound && (!hasActiveBoundInstall || isActive)
const bindButton = shouldOfferBind
? `<button class="btn btn-primary btn-xs cleanup-bind-btn" data-path="${escapeHtml(inst.path)}">${t('services.cleanupBindThis')}</button>`
: ''
if (!isActive) {
actions = `
<div style="margin-top:8px;display:flex;gap:6px;align-items:center;flex-wrap:wrap">
${bindButton}
<code style="flex:1;min-width:0;font-size:11px;padding:4px 8px;background:var(--bg-tertiary);border-radius:4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;user-select:all" title="${escapeHtml(uninstallCmd)}">${escapeHtml(uninstallCmd)}</code>
<button class="btn btn-secondary btn-xs cleanup-copy-cmd" data-cmd="${escapeHtml(uninstallCmd)}" style="flex-shrink:0">${t('services.cleanupCopyCmd')}</button>
</div>`
} else if (bindButton) {
actions = `<div style="margin-top:8px">${bindButton}</div>`
}
return `

View File

@@ -590,17 +590,10 @@ export class WsClient {
const result = await api.autoPairDevice()
console.log('[ws] 配对结果:', result)
// 配对后桌面端需要 reload Gateway 使 allowedOrigins 生效Web/headless 不能隐式重载反代后的服务
if (isTauriRuntime()) {
try {
await api.reloadGateway()
console.log('[ws] Gateway 已重载')
} catch (e) {
console.warn('[ws] reloadGateway 失败(非致命):', e)
}
} else {
console.log('[ws] Web/headless 模式跳过自动 reload Gateway')
}
// 这里只修配对文件,不自动重启 Gateway
// Windows 上手动启动的 Gateway 会被 restart/stop 打断,表现为“启动后一会就停止”。
// Gateway 对设备配对文件按连接读取;如遇 origin 配置变更,交由用户手动重启。
console.log('[ws] 自动配对文件已修复,跳过自动重启 Gateway')
// 修复 #160: 不调用 reconnect()(它会重置 _autoPairAttempts 导致无限循环),
// 而是直接重连一次。如果仍然失败_autoPairAttempts 不会被重置,不会再次触发自动修复。