mirror of
https://github.com/qingchencloud/clawpanel.git
synced 2026-05-06 20:02:49 +08:00
feat(hermes): auto-heal platforms.api_server.enabled before gateway start (Step 5)
Close the G7 gap from the v3 integration design. Scenario: A user upgrades Hermes (or manually edits config.yaml and drops the api_server platform block). The next `gateway start` silently produces a Gateway that doesn't expose /v1/runs, and ClawPanel's chat/agent flows fail with confusing errors. Solution: Run a pre-start guardian that checks `platforms.api_server.enabled: true` and auto-heals the file when missing, with a timestamped backup so users can always roll back. Backend (src-tauri/src/commands/hermes.rs): - New pure helpers `config_has_api_server_enabled(raw)` and `patch_yaml_ensure_api_server(raw)` working directly on YAML strings (no serde_yaml dependency needed — we only touch 3 structural keys). - `ensure_api_server_enabled(app)` wraps the helpers with filesystem I/O: reads config.yaml, writes `config.yaml.bak-<epoch>` on mutation, writes the patched content back, emits `hermes-config-patched` so the frontend can show a transparent toast. - Called from `hermes_gateway_action` on every `start` action. If config.yaml doesn't exist, the guardian is a no-op (configure_hermes creates a compliant file on first run). - 7 new unit tests covering: truthy value variants (true/yes/on/1), missing/disabled/commented scenarios, no-op when healthy, appending a full platforms block, injecting into existing platforms, replacing an explicitly disabled api_server subtree. Web mode (scripts/dev-api.js): - Mirrors the three helpers as `_hermesConfigHasApiServerEnabled`, `_hermesPatchYamlEnsureApiServer`, `_hermesEnsureApiServerEnabled`. - Called in `hermes_gateway_action` start path. - Logs to console.warn instead of emitting a Tauri event. Frontend (src/engines/hermes/pages/dashboard.js): - Dashboard subscribes to `hermes-config-patched` and surfaces a toast (6s duration) so the user knows the auto-heal happened and where the backup lives. Verified: cargo fmt / cargo clippy -D warnings / cargo test all green (12 hermes tests pass total: 5 provider registry + 7 guardian). `node --check scripts/dev-api.js` passes. `npm run build` green.
This commit is contained in:
@@ -6692,6 +6692,11 @@ const handlers = {
|
||||
const enhanced = hermesEnhancedPath()
|
||||
const port = hermesGatewayPort()
|
||||
if (action === 'start') {
|
||||
// Guardian: ensure platforms.api_server.enabled:true before start.
|
||||
// Mirrors Rust's ensure_api_server_enabled (see hermes.rs).
|
||||
try { this._hermesEnsureApiServerEnabled() } catch (e) {
|
||||
console.warn('[hermes guardian] patch failed:', e.message || e)
|
||||
}
|
||||
// 检测是否已运行
|
||||
const alive = await _tcpProbe('127.0.0.1', port, 300)
|
||||
if (alive) return 'Gateway 已在运行'
|
||||
@@ -6845,6 +6850,98 @@ const handlers = {
|
||||
return []
|
||||
},
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// api_server guardian (Step 5) — mirror of Rust's config_has_api_server_enabled
|
||||
// + patch_yaml_ensure_api_server + ensure_api_server_enabled. Called before
|
||||
// every `hermes gateway run` so that an upgrade / manual edit that drops
|
||||
// `platforms.api_server.enabled: true` is auto-healed.
|
||||
// -----------------------------------------------------------------------
|
||||
_hermesConfigHasApiServerEnabled(raw) {
|
||||
let inPlatforms = false
|
||||
let inApiServer = false
|
||||
for (const origLine of raw.split('\n')) {
|
||||
const hash = origLine.indexOf('#')
|
||||
const line = hash >= 0 ? origLine.slice(0, hash) : origLine
|
||||
const trimmed = line.replace(/\s+$/, '')
|
||||
if (!trimmed) continue
|
||||
const indent = trimmed.length - trimmed.trimStart().length
|
||||
if (indent === 0) {
|
||||
inPlatforms = trimmed.trimStart().startsWith('platforms:')
|
||||
inApiServer = false
|
||||
continue
|
||||
}
|
||||
if (!inPlatforms) continue
|
||||
if (indent <= 2) {
|
||||
inApiServer = trimmed.trimStart().startsWith('api_server:')
|
||||
continue
|
||||
}
|
||||
if (!inApiServer) continue
|
||||
const t = trimmed.trimStart()
|
||||
if (t.startsWith('enabled:')) {
|
||||
const v = t.slice(8).trim().replace(/^['"]|['"]$/g, '').toLowerCase()
|
||||
return ['true', 'yes', 'on', '1'].includes(v)
|
||||
}
|
||||
}
|
||||
return false
|
||||
},
|
||||
|
||||
_hermesPatchYamlEnsureApiServer(raw) {
|
||||
if (this._hermesConfigHasApiServerEnabled(raw)) return raw
|
||||
const lines = raw.split('\n')
|
||||
const out = []
|
||||
let platformsFound = false
|
||||
let i = 0
|
||||
while (i < lines.length) {
|
||||
const line = lines[i]
|
||||
const trimmed = line.replace(/\s+$/, '')
|
||||
const indent = trimmed.length - trimmed.trimStart().length
|
||||
if (indent === 0 && trimmed.trimStart().startsWith('platforms:')) {
|
||||
out.push(line)
|
||||
platformsFound = true
|
||||
i++
|
||||
const accumulated = []
|
||||
let skipping = false
|
||||
while (i < lines.length) {
|
||||
const l = lines[i]
|
||||
const t = l.replace(/\s+$/, '')
|
||||
const ind = t.length - t.trimStart().length
|
||||
if (ind === 0 && t !== '') break
|
||||
if (ind <= 2) skipping = t.trimStart().startsWith('api_server:')
|
||||
if (!skipping) accumulated.push(l)
|
||||
i++
|
||||
}
|
||||
out.push(' api_server:')
|
||||
out.push(' enabled: true')
|
||||
out.push(...accumulated)
|
||||
continue
|
||||
}
|
||||
out.push(line)
|
||||
i++
|
||||
}
|
||||
if (!platformsFound) {
|
||||
if (out.length && out[out.length - 1] !== '') out.push('')
|
||||
out.push('platforms:')
|
||||
out.push(' api_server:')
|
||||
out.push(' enabled: true')
|
||||
}
|
||||
let content = out.join('\n')
|
||||
if (!content.endsWith('\n')) content += '\n'
|
||||
return content
|
||||
},
|
||||
|
||||
_hermesEnsureApiServerEnabled() {
|
||||
const configPath = path.join(hermesHome(), 'config.yaml')
|
||||
if (!fs.existsSync(configPath)) return
|
||||
const raw = fs.readFileSync(configPath, 'utf8')
|
||||
if (this._hermesConfigHasApiServerEnabled(raw)) return
|
||||
const ts = Math.floor(Date.now() / 1000)
|
||||
const backupPath = configPath + `.bak-${ts}`
|
||||
try { fs.writeFileSync(backupPath, raw) } catch {}
|
||||
const patched = this._hermesPatchYamlEnsureApiServer(raw)
|
||||
fs.writeFileSync(configPath, patched)
|
||||
console.warn(`[hermes guardian] patched config.yaml (api_server.enabled). Backup: ${backupPath}`)
|
||||
},
|
||||
|
||||
// =========================================================================
|
||||
// .env editor (Step 4) — Web-mode implementations mirroring Rust behavior.
|
||||
// The managed-key list is duplicated here since Rust's hermes_providers is
|
||||
|
||||
Reference in New Issue
Block a user