Add session beacon for predecessor discovery via /resume

Inject an identity beacon as the first message when Gas Town starts Claude
sessions. This beacon becomes the session title in Claude Code '/resume
picker, enabling workers to find their predecessor sessions for debugging.

Beacon format: [GAS TOWN] <address> • <mol-id or "ready"> • <timestamp>

Examples:
- [GAS TOWN] gastown/crew/max • gt-abc12 • 2025-12-30T14:32
- [GAS TOWN] gastown/polecats/Toast • ready • 2025-12-30T09:15
- [GAS TOWN] deacon • patrol • 2025-12-30T08:00

Workers can now press / in /resume picker and search for their address
(e.g., "gastown/crew/max") to see all predecessor sessions.

Note: Respawn-loop agents (deacon/refinery via up.go) skip beacon injection
since Claude restarts multiple times - would need post-restart injection.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-12-30 18:01:47 -08:00
parent 5c61c8c334
commit 66042da18b
8 changed files with 166 additions and 1 deletions

View File

@@ -11,9 +11,11 @@ import (
"github.com/spf13/cobra"
"github.com/steveyegge/gastown/internal/beads"
"github.com/steveyegge/gastown/internal/config"
"github.com/steveyegge/gastown/internal/constants"
"github.com/steveyegge/gastown/internal/daemon"
"github.com/steveyegge/gastown/internal/events"
"github.com/steveyegge/gastown/internal/refinery"
"github.com/steveyegge/gastown/internal/session"
"github.com/steveyegge/gastown/internal/style"
"github.com/steveyegge/gastown/internal/tmux"
"github.com/steveyegge/gastown/internal/workspace"
@@ -271,6 +273,20 @@ func ensureSession(t *tmux.Tmux, sessionName, workDir, role string) error {
return err
}
// Wait for Claude to start (non-fatal)
// Note: Deacon respawn loop makes beacon tricky - Claude restarts multiple times
// For non-respawn (mayor), inject beacon
if role != "deacon" {
if err := t.WaitForCommand(sessionName, constants.SupportedShells, constants.ClaudeStartTimeout); err != nil {
// Non-fatal
}
time.Sleep(constants.ShutdownNotifyDelay)
// Inject session beacon for predecessor discovery via /resume
beacon := session.SessionBeacon(role, "")
_ = t.NudgeSession(sessionName, beacon) // Non-fatal
}
return nil
}
@@ -306,6 +322,17 @@ func ensureWitness(t *tmux.Tmux, sessionName, rigPath, rigName string) error {
return err
}
// Wait for Claude to start (non-fatal)
if err := t.WaitForCommand(sessionName, constants.SupportedShells, constants.ClaudeStartTimeout); err != nil {
// Non-fatal
}
time.Sleep(constants.ShutdownNotifyDelay)
// Inject session beacon for predecessor discovery via /resume
address := fmt.Sprintf("%s/witness", rigName)
beacon := session.SessionBeacon(address, "patrol")
_ = t.NudgeSession(sessionName, beacon) // Non-fatal
return nil
}
@@ -517,6 +544,17 @@ func ensureCrewSession(t *tmux.Tmux, sessionName, crewPath, rigName, crewName st
return err
}
// Wait for Claude to start (non-fatal)
if err := t.WaitForCommand(sessionName, constants.SupportedShells, constants.ClaudeStartTimeout); err != nil {
// Non-fatal
}
time.Sleep(constants.ShutdownNotifyDelay)
// Inject session beacon for predecessor discovery via /resume
address := fmt.Sprintf("%s/crew/%s", rigName, crewName)
beacon := session.SessionBeacon(address, "")
_ = t.NudgeSession(sessionName, beacon) // Non-fatal
return nil
}
@@ -605,5 +643,16 @@ func ensurePolecatSession(t *tmux.Tmux, sessionName, polecatPath, rigName, polec
return err
}
// Wait for Claude to start (non-fatal)
if err := t.WaitForCommand(sessionName, constants.SupportedShells, constants.ClaudeStartTimeout); err != nil {
// Non-fatal
}
time.Sleep(constants.ShutdownNotifyDelay)
// Inject session beacon for predecessor discovery via /resume
address := fmt.Sprintf("%s/polecats/%s", rigName, polecatName)
beacon := session.SessionBeacon(address, "")
_ = t.NudgeSession(sessionName, beacon) // Non-fatal
return nil
}