fix(sling): auto-attach work molecule and handle dead polecats

Combines three related sling improvements:

1. Auto-attach mol-polecat-work (Issue #288)
   - Automatically attach work molecule when slinging to polecats
   - Ensures polecats have standard guidance molecule attached

2. Fix polecat hook with molecule (Issue #197)
   - Use beads.ResolveHookDir() for correct directory resolution
   - Prevents bd cook from failing in polecat worktree

3. Spawn fresh polecat when target has no session
   - When slinging to a dead polecat, spawn fresh one instead of failing
   - Fixes stale convoys not progressing due to done polecats
This commit is contained in:
mayor
2026-01-13 14:01:49 +01:00
committed by Daniel Sauer
parent 278b2f2d4d
commit 7924921d17
3 changed files with 112 additions and 1 deletions

View File

@@ -9,7 +9,9 @@ import (
"time"
"github.com/steveyegge/gastown/internal/beads"
"github.com/steveyegge/gastown/internal/config"
"github.com/steveyegge/gastown/internal/constants"
"github.com/steveyegge/gastown/internal/style"
"github.com/steveyegge/gastown/internal/tmux"
"github.com/steveyegge/gastown/internal/workspace"
)
@@ -373,3 +375,67 @@ func wakeRigAgents(rigName string) {
_ = t.NudgeSession(witnessSession, "Polecat dispatched - check for work")
_ = t.NudgeSession(refinerySession, "Polecat dispatched - check for merge requests")
}
// isPolecatTarget checks if the target string refers to a polecat.
// Returns true if the target format is "rig/polecats/name".
// This is used to determine if we should respawn a dead polecat
// instead of failing when slinging work.
func isPolecatTarget(target string) bool {
parts := strings.Split(target, "/")
return len(parts) >= 3 && parts[1] == "polecats"
}
// attachPolecatWorkMolecule attaches the mol-polecat-work molecule to a polecat's agent bead.
// This ensures all polecats have the standard work molecule attached for guidance.
// The molecule is attached by storing it in the agent bead's description using attachment fields.
//
// Per issue #288: gt sling should auto-attach mol-polecat-work when slinging to polecats.
func attachPolecatWorkMolecule(targetAgent, hookWorkDir, townRoot string) error {
// Parse the polecat name from targetAgent (format: "rig/polecats/name")
parts := strings.Split(targetAgent, "/")
if len(parts) != 3 || parts[1] != "polecats" {
return fmt.Errorf("invalid polecat agent format: %s", targetAgent)
}
rigName := parts[0]
polecatName := parts[2]
// Get the polecat's agent bead ID
// Format: "<prefix>-<rig>-polecat-<name>" (e.g., "gt-gastown-polecat-Toast")
prefix := config.GetRigPrefix(townRoot, rigName)
agentBeadID := beads.PolecatBeadIDWithPrefix(prefix, rigName, polecatName)
// Resolve the rig directory for running bd commands.
// Use ResolveHookDir to ensure we run bd from the correct rig directory
// (not from the polecat's worktree, which doesn't have a .beads directory).
// This fixes issue #197: polecat fails to hook when slinging with molecule.
rigDir := beads.ResolveHookDir(townRoot, prefix+"-"+polecatName, hookWorkDir)
b := beads.New(rigDir)
// Check if molecule is already attached (avoid duplicate attach)
attachment, err := b.GetAttachment(agentBeadID)
if err == nil && attachment != nil && attachment.AttachedMolecule != "" {
// Already has a molecule attached - skip
return nil
}
// Cook the mol-polecat-work formula to ensure the proto exists
// This is safe to run multiple times - cooking is idempotent
cookCmd := exec.Command("bd", "--no-daemon", "cook", "mol-polecat-work")
cookCmd.Dir = rigDir
cookCmd.Stderr = os.Stderr
if err := cookCmd.Run(); err != nil {
return fmt.Errorf("cooking mol-polecat-work formula: %w", err)
}
// Attach the molecule to the polecat's agent bead
// The molecule ID is the formula name "mol-polecat-work"
moleculeID := "mol-polecat-work"
_, err = b.AttachMolecule(agentBeadID, moleculeID)
if err != nil {
return fmt.Errorf("attaching molecule %s to %s: %w", moleculeID, agentBeadID, err)
}
fmt.Printf("%s Attached %s to %s\n", style.Bold.Render("✓"), moleculeID, agentBeadID)
return nil
}