Resilience: LaunchAgent restarts BroV on crash; crashed Claude Code/Codex sessions get a resume button (optional auto); login and waiting status in the Claude/ChatGPT pills

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
maksarsanjeev
2026-10-06 22:56:39 +03:00
parent 37c01f991a
commit 0d40b78b26
9 changed files with 453 additions and 7 deletions
+38
View File
@@ -414,6 +414,20 @@ final class HookServer: @unchecked Sendable {
// Approval dismissed — fall through so the resolving event updates state normally.
}
// BroV: crash watch + login status.
if let pid = (payload["brov_agent_pid"] as? NSNumber)?.intValue {
AgentWatch.shared.register(sessionId: sessionId, pid: pid,
kind: payload["brov_agent_kind"] as? String ?? "",
cwd: cwd, pillId: agentId)
}
if name == "SessionEnd" { AgentWatch.shared.ended(sessionId: sessionId) }
if name == "Notification" || name == "StopFailure" {
let text = (payload["message"] as? String ?? "") + " " + (payload["error"] as? String ?? "")
if AgentAuth.looksLikeAuthError(text) {
AgentAuth.shared.reportAuthError(isCodexEvent ? .codex : .claude)
}
}
switch name {
case "SessionStart":
@@ -2051,6 +2065,30 @@ def main():
if agent:
payload.setdefault('coucou_agent', agent)
# BroV crash watch: on session start / prompt, report the agent process (claude/codex)
# found by walking up the parent chain, so BroV can notice when it dies mid-turn.
try:
ev0 = payload.get('hook_event_name', '') or arg_event
if ev0 in ('SessionStart', 'UserPromptSubmit', 'session_start', 'user_prompt_submit'):
import subprocess
pid = os.getppid()
for _ in range(8):
out = subprocess.run(['ps', '-o', 'ppid=,comm=', '-p', str(pid)],
capture_output=True, text=True, timeout=1).stdout.strip()
if not out:
break
ppid_s, _, comm = out.partition(' ')
name = os.path.basename(comm.strip())
if name in ('claude', 'codex'):
payload['brov_agent_pid'] = pid
payload['brov_agent_kind'] = name
break
pid = int(ppid_s)
if pid <= 1:
break
except Exception:
pass
# Enrich with terminal context
env = os.environ
payload.setdefault('term_program', env.get('TERM_PROGRAM', ''))