feat(hermes-install): diagnose network failures and add optional Git mirror (#273)

- Detect git/network failure patterns (failed to connect, could not resolve host,
  unable to access, etc.) in install/update output and append a clear hint
  pointing users to the proxy or mirror settings instead of leaving them with
  raw multi-line git stderr.
- Add optional 'Hermes Install Mirror' setting (panelConfig.gitMirror): when set,
  install/upgrade injects GIT_CONFIG_COUNT/KEY_0/VALUE_0 to rewrite
  https://github.com/ via the mirror prefix at process scope only — the user's
  global ~/.gitconfig is never touched.
- Surface the new mirror field in Settings (works for both engines), with
  zh-CN/en/zh-TW copy and a hint explaining how it interacts with the install
  flow.
This commit is contained in:
晴天
2026-05-14 01:46:55 +08:00
parent e9cd2c6059
commit 7d4a423df0
4 changed files with 214 additions and 10 deletions

View File

@@ -168,6 +168,54 @@ function sanitizeHermesInstallOutput(text = '') {
.replaceAll('NousResearch/hermes-agent', 'hermes-agent')
}
// 读取 panel config (~/.openclaw/clawpanel.json) 中的 gitMirror 前缀。
// 为空/未设置 → 返回 '' 不启用镜像。
function gitMirrorPrefix() {
try {
const cfgPath = path.join(DEFAULT_OPENCLAW_DIR, 'clawpanel.json')
if (!fs.existsSync(cfgPath)) return ''
const raw = fs.readFileSync(cfgPath, 'utf8')
const cfg = JSON.parse(raw)
const v = String(cfg?.gitMirror || '').trim()
return v
} catch {
return ''
}
}
// 返回一个 env 添加包,含 GIT_CONFIG_COUNT/KEY/VALUE 临时重写。
// 未配置镜像 → 返回空对象。
function gitMirrorEnv() {
let mirror = gitMirrorPrefix()
if (!mirror) return {}
if (!mirror.endsWith('/')) mirror += '/'
return {
GIT_CONFIG_COUNT: '1',
GIT_CONFIG_KEY_0: `url.${mirror}https://github.com/.insteadOf`,
GIT_CONFIG_VALUE_0: 'https://github.com/',
}
}
// 判断输出是否命中 「网络无法访问」 类失败,命中返回建议文案。
function diagnoseHermesInstallError(text = '') {
const lower = String(text || '').toLowerCase()
const hits = [
'failed to connect to github.com',
'could not connect to server',
'failed to clone',
'unable to access',
'git operation failed',
'connection timed out',
'connection refused',
'network is unreachable',
'could not resolve host',
]
if (!hits.some(h => lower.includes(h))) return null
return '⚠ 检测到安装过程中无法访问外部 Git 服务。请任选一项重试:'
+ '\n 1) 在「设置 → 网络代理」配置可用代理后重试;'
+ '\n 2) 在「设置 → Hermes 安装镜像」填入可用的 Git 镜像前缀。'
}
let _hermesGwProcess = null
const __dev_dirname = path.dirname(fileURLToPath(import.meta.url))
@@ -6884,13 +6932,18 @@ const handlers = {
? ['pip', 'install', pkg]
: ['tool', 'install', '--force', pkg, '--python', '3.11', '--with', 'croniter']
const result = spawnSync(uv, installArgs, {
env: { ...process.env, PATH: hermesEnhancedPath(), GIT_TERMINAL_PROMPT: '0' },
env: { ...process.env, PATH: hermesEnhancedPath(), GIT_TERMINAL_PROMPT: '0', ...gitMirrorEnv() },
timeout: 600000,
windowsHide: true,
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'pipe'],
})
if (result.status !== 0) throw new Error(`安装失败: ${sanitizeHermesInstallOutput((result.stderr || '').trim())}`)
if (result.status !== 0) {
const cleaned = sanitizeHermesInstallOutput((result.stderr || '').trim())
const hint = diagnoseHermesInstallError(cleaned)
if (hint) throw new Error(`安装失败: ${cleaned}\n\n${hint}`)
throw new Error(`安装失败: ${cleaned}`)
}
// 3. 验证
const ver = runHermesSilent('hermes', ['version'])
if (ver.ok) return ver.stdout
@@ -8243,10 +8296,15 @@ const handlers = {
const uv = fs.existsSync(uvPath) ? uvPath : 'uv'
const pkg = 'hermes-agent[web] @ git+https://github.com/NousResearch/hermes-agent.git'
const result = spawnSync(uv, ['tool', 'install', '--reinstall', pkg, '--python', '3.11', '--with', 'croniter'], {
env: { ...process.env, PATH: hermesEnhancedPath(), GIT_TERMINAL_PROMPT: '0' },
env: { ...process.env, PATH: hermesEnhancedPath(), GIT_TERMINAL_PROMPT: '0', ...gitMirrorEnv() },
timeout: 600000, windowsHide: true, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'],
})
if (result.status !== 0) throw new Error(`升级失败: ${sanitizeHermesInstallOutput((result.stderr || '').trim())}`)
if (result.status !== 0) {
const cleaned = sanitizeHermesInstallOutput((result.stderr || '').trim())
const hint = diagnoseHermesInstallError(cleaned)
if (hint) throw new Error(`升级失败: ${cleaned}\n\n${hint}`)
throw new Error(`升级失败: ${cleaned}`)
}
return '升级完成'
},