feat(dashboard): add Debug Share to the System page (#38600)
* Port from google-gemini/gemini-cli#21541: back up corrupted config.yaml When config.yaml fails to parse, load_config() silently falls back to DEFAULT_CONFIG and leaves the broken file on disk. If the user then re-runs the setup wizard or hermes config set (both rewrite config.yaml), their broken-but-recoverable overrides are lost for good. Adapts the policy-file recovery from gemini-cli#21541: on the first parse warning for a given broken file, snapshot it to config.yaml.corrupt.<ts>.bak (best-effort, symlink-guarded, size-deduped) and tell the user where it landed. Unlike Gemini's version we deliberately do NOT reset config.yaml to a clean state — hermes never silently mutates user config, and leaving it means a hand-fixed file is re-read on the next load. Tests: 3 new cases (backup created + content preserved + original untouched; same-size backup dedup; symlink not copied). E2E verified with isolated HERMES_HOME and a real tab-indented broken config. * feat(dashboard): add Debug Share to the System page Surface `hermes debug share` in the dashboard. The System > Operations section gets a dedicated card that uploads a redacted report + full logs and returns the paste URLs as real, copyable links instead of a log tail. - debug.py: factor a pure build_debug_share() returning structured {urls, failures, redacted, auto_delete_seconds}; run_debug_share now calls it (CLI output unchanged). - web_server.py: POST /api/ops/debug-share runs the share core in a worker thread and returns the structured payload synchronously (the URLs are the whole point — not a backgrounded action). - api.ts: runDebugShare() + DebugShareResponse. - SystemPage.tsx: share card with a redaction toggle (on by default), per-link + copy-all buttons, and the 6h auto-delete countdown. - tests: build_debug_share core + endpoint (redact toggle, failure 502, token gate).
This commit is contained in:
@@ -1016,6 +1016,51 @@ async def run_config_migrate():
|
||||
return {"ok": True, "pid": proc.pid, "name": "config-migrate"}
|
||||
|
||||
|
||||
class DebugShareRequest(BaseModel):
|
||||
# Redaction is ON by default — force-mode scrubs credential-shaped tokens
|
||||
# out of log content before it leaves the machine. The toggle exists so an
|
||||
# operator who knows the logs are clean can opt out for fuller fidelity.
|
||||
redact: bool = True
|
||||
# Recent log lines included in the summary tail (full logs are separate).
|
||||
lines: int = 200
|
||||
|
||||
|
||||
@app.post("/api/ops/debug-share")
|
||||
async def run_debug_share_endpoint(body: DebugShareRequest | None = None):
|
||||
"""Upload a redacted debug report + full logs and return the paste URLs.
|
||||
|
||||
Unlike the other diagnostics actions (doctor, dump, prompt-size) this is
|
||||
*synchronous*: the whole point of ``debug share`` is the set of shareable
|
||||
URLs it produces, so we run the upload in a worker thread and return the
|
||||
structured ``{urls, failures, redacted, ...}`` payload directly. The
|
||||
dashboard renders those as real, copyable links instead of scraping a log
|
||||
tail. Pastes auto-delete after 6 hours (handled inside the share core).
|
||||
"""
|
||||
from hermes_cli.debug import build_debug_share
|
||||
|
||||
req = body or DebugShareRequest()
|
||||
try:
|
||||
result = await asyncio.to_thread(
|
||||
build_debug_share,
|
||||
log_lines=max(1, min(int(req.lines), 5000)),
|
||||
redact=bool(req.redact),
|
||||
)
|
||||
except RuntimeError as exc:
|
||||
# Required summary-report upload failed (offline / paste service down).
|
||||
raise HTTPException(status_code=502, detail=f"Upload failed: {exc}")
|
||||
except Exception as exc:
|
||||
_log.exception("debug share failed")
|
||||
raise HTTPException(status_code=500, detail=f"Failed: {exc}")
|
||||
|
||||
return {
|
||||
"ok": True,
|
||||
"urls": result.urls,
|
||||
"failures": result.failures,
|
||||
"redacted": result.redacted,
|
||||
"auto_delete_seconds": result.auto_delete_seconds,
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Gateway + update actions (invoked from the Status page).
|
||||
#
|
||||
|
||||
Reference in New Issue
Block a user