fix(dashboard): surface launch/crash failures (#100)
test / unit (pull_request) Successful in 29s
test / integration (pull_request) Successful in 43s

The dashboard runs under curses.wrapper and cmd_dashboard only caught
KeyboardInterrupt, so failures vanished:
- die() prints to stderr, but under curses that lands on the alternate
  screen and is wiped on exit, so config errors gave no reason.
- Die is a SystemExit, so the new-agent flow's `except Exception` never
  caught config errors; they crashed the TUI.
- the startup manifest probe was unguarded.

Now: Die carries its message (+ log.error()); cmd_dashboard re-surfaces
a Die's reason once the terminal is restored and writes any other
crash's traceback to ~/.bot-bottle/logs/dashboard-crash.log; the startup
probe and the new-agent flow degrade a bad config to a status-line
warning instead of crashing.

Closes #100

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-05-28 23:49:21 -04:00
parent 848515e5d4
commit 99ec267c74
3 changed files with 223 additions and 6 deletions
+15 -3
View File
@@ -14,11 +14,23 @@ def warn(msg: str) -> None:
print(f"bot-bottle: warning: {msg}", file=sys.stderr)
def error(msg: str) -> None:
print(f"bot-bottle: error: {msg}", file=sys.stderr)
class Die(SystemExit):
"""Raised by die() so callers (and tests) can distinguish a deliberate
fatal exit from an unrelated SystemExit."""
fatal exit from an unrelated SystemExit.
Carries the human-facing message so a caller that suppressed stderr
— e.g. the curses dashboard, whose alternate screen is wiped when the
terminal is restored — can re-surface the reason after the fact."""
def __init__(self, code: int = 1, message: str = "") -> None:
super().__init__(code)
self.message = message
def die(msg: str) -> NoReturn:
print(f"bot-bottle: error: {msg}", file=sys.stderr)
raise Die(1)
error(msg)
raise Die(1, msg)