fix(sling): check hooked status and send LIFECYCLE:Shutdown on --force (#828)
* fix(sling): check hooked status and send LIFECYCLE:Shutdown on --force - Change sling validation to check both pinned and hooked status (was only checking pinned, likely a bug) - Add --force handling that sends LIFECYCLE:Shutdown message to witness when forcibly reassigning work from an already-hooked bead - Use existing LIFECYCLE:Shutdown protocol instead of new KILL_POLECAT - witness will auto-nuke if clean, or create cleanup wisp if dirty - Use agent.Self() to identify the requester (falls back to "unknown" for CLI users without GT_ROLE env vars) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: use env vars instead of undefined agent.Self() The agent.Self() function does not exist in the agent package. Replace with direct env var lookups for GT_POLECAT (when running as a polecat) or USER as fallback. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: julianknutsen <julianknutsen@users.noreply.github> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: beads/crew/lizzy <steve.yegge@gmail.com>
This commit is contained in:
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/steveyegge/gastown/internal/beads"
|
||||
"github.com/steveyegge/gastown/internal/events"
|
||||
"github.com/steveyegge/gastown/internal/mail"
|
||||
"github.com/steveyegge/gastown/internal/style"
|
||||
"github.com/steveyegge/gastown/internal/workspace"
|
||||
)
|
||||
@@ -312,17 +313,63 @@ func runSling(cmd *cobra.Command, args []string) error {
|
||||
fmt.Printf("%s Slinging %s to %s...\n", style.Bold.Render("🎯"), beadID, targetAgent)
|
||||
}
|
||||
|
||||
// Check if bead is already pinned (guard against accidental re-sling)
|
||||
// Check if bead is already assigned (guard against accidental re-sling)
|
||||
info, err := getBeadInfo(beadID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("checking bead status: %w", err)
|
||||
}
|
||||
if info.Status == "pinned" && !slingForce {
|
||||
if (info.Status == "pinned" || info.Status == "hooked") && !slingForce {
|
||||
assignee := info.Assignee
|
||||
if assignee == "" {
|
||||
assignee = "(unknown)"
|
||||
}
|
||||
return fmt.Errorf("bead %s is already pinned to %s\nUse --force to re-sling", beadID, assignee)
|
||||
return fmt.Errorf("bead %s is already %s to %s\nUse --force to re-sling", beadID, info.Status, assignee)
|
||||
}
|
||||
|
||||
// Handle --force when bead is already hooked: send shutdown to old polecat and unhook
|
||||
if info.Status == "hooked" && slingForce && info.Assignee != "" {
|
||||
fmt.Printf("%s Bead already hooked to %s, forcing reassignment...\n", style.Warning.Render("⚠"), info.Assignee)
|
||||
|
||||
// Determine requester identity from env vars, fall back to "gt-sling"
|
||||
requester := "gt-sling"
|
||||
if polecat := os.Getenv("GT_POLECAT"); polecat != "" {
|
||||
requester = polecat
|
||||
} else if user := os.Getenv("USER"); user != "" {
|
||||
requester = user
|
||||
}
|
||||
|
||||
// Extract rig name from assignee (e.g., "gastown/polecats/Toast" -> "gastown")
|
||||
assigneeParts := strings.Split(info.Assignee, "/")
|
||||
if len(assigneeParts) >= 3 && assigneeParts[1] == "polecats" {
|
||||
oldRigName := assigneeParts[0]
|
||||
oldPolecatName := assigneeParts[2]
|
||||
|
||||
// Send LIFECYCLE:Shutdown to witness - will auto-nuke if clean,
|
||||
// otherwise create cleanup wisp for manual intervention
|
||||
if townRoot != "" {
|
||||
router := mail.NewRouter(townRoot)
|
||||
shutdownMsg := &mail.Message{
|
||||
From: "gt-sling",
|
||||
To: fmt.Sprintf("%s/witness", oldRigName),
|
||||
Subject: fmt.Sprintf("LIFECYCLE:Shutdown %s", oldPolecatName),
|
||||
Body: fmt.Sprintf("Reason: work_reassigned\nRequestedBy: %s\nBead: %s\nNewAssignee: %s", requester, beadID, targetAgent),
|
||||
Type: mail.TypeTask,
|
||||
Priority: mail.PriorityHigh,
|
||||
}
|
||||
if err := router.Send(shutdownMsg); err != nil {
|
||||
fmt.Printf("%s Could not send shutdown to witness: %v\n", style.Dim.Render("Warning:"), err)
|
||||
} else {
|
||||
fmt.Printf("%s Sent LIFECYCLE:Shutdown to %s/witness for %s\n", style.Bold.Render("→"), oldRigName, oldPolecatName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Unhook the bead from old owner (set status back to open)
|
||||
unhookCmd := exec.Command("bd", "--no-daemon", "update", beadID, "--status=open", "--assignee=")
|
||||
unhookCmd.Dir = beads.ResolveHookDir(townRoot, beadID, "")
|
||||
if err := unhookCmd.Run(); err != nil {
|
||||
fmt.Printf("%s Could not unhook bead from old owner: %v\n", style.Dim.Render("Warning:"), err)
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-convoy: check if issue is already tracked by a convoy
|
||||
|
||||
Reference in New Issue
Block a user