Add connection resilience and reconnect commands for Kosmi and Jackbox

Kosmi WebSocket would silently die after hours/days with no reconnection.
Jackbox WebSocket failed to reconnect after API server restarts (stale JWT)
and leaked heartbeat goroutines on each reconnect cycle.

Kosmi changes:
- Add WebSocket ping/pong keepalive (30s ping, 90s read deadline)
- Send EventFailure on unexpected disconnect to trigger gateway reconnectBridge()
- Add intentionalDisconnect flag to prevent false failure events on clean shutdown
- Fix Disconnect() to be safe for reconnect cycles

Jackbox changes:
- Add read deadline (90s) to detect stale connections
- Fix heartbeat goroutine leak via per-connection listenDone channel
- Re-authenticate for fresh JWT before each reconnect attempt
- Add Manager.Reconnect() for on-demand teardown and rebuild

IRC commands:
- !kreconnect - reconnect Kosmi bridge
- !jreconnect - reconnect Jackbox WebSocket
- !reconnect  - reconnect all services (Kosmi + Jackbox)

Made-with: Cursor
This commit is contained in:
cottongin
2026-04-05 05:30:39 -04:00
parent bec3615d2b
commit 4fc7f08b24
6 changed files with 182 additions and 32 deletions

View File

@@ -268,8 +268,10 @@ func (b *Birc) handlePrivMsg(client *girc.Client, event girc.Event) {
}
}
// Handle !kreconnect command: trigger Kosmi bridge reconnection
if strings.TrimSpace(rmsg.Text) == "!kreconnect" {
// Handle reconnect commands
trimmedText := strings.TrimSpace(rmsg.Text)
switch trimmedText {
case "!kreconnect":
b.Log.Infof("!kreconnect command from %s on %s", event.Source.Name, rmsg.Channel)
b.Remote <- config.Message{
Username: "system",
@@ -279,6 +281,26 @@ func (b *Birc) handlePrivMsg(client *girc.Client, event girc.Event) {
Event: config.EventReconnectKosmi,
}
return
case "!jreconnect":
b.Log.Infof("!jreconnect command from %s on %s", event.Source.Name, rmsg.Channel)
b.Remote <- config.Message{
Username: "system",
Text: "jreconnect",
Channel: rmsg.Channel,
Account: b.Account,
Event: config.EventReconnectJackbox,
}
return
case "!reconnect":
b.Log.Infof("!reconnect command from %s on %s", event.Source.Name, rmsg.Channel)
b.Remote <- config.Message{
Username: "system",
Text: "reconnect",
Channel: rmsg.Channel,
Account: b.Account,
Event: config.EventReconnectAll,
}
return
}
// Handle !votes command: query current game vote tally