Merge pull request #39 from cvsloane/fix/polecat-dispatcher-notification

feat: notify dispatcher when polecat work completes
This commit is contained in:
Steve Yegge
2026-01-04 23:09:23 -08:00
committed by GitHub
3 changed files with 106 additions and 0 deletions

View File

@@ -454,6 +454,12 @@ func runSling(cmd *cobra.Command, args []string) error {
// Update agent bead's hook_bead field (ZFC: agents track their current work)
updateAgentHookBead(targetAgent, beadID, hookWorkDir, townBeadsDir)
// Store dispatcher in bead description (enables completion notification to dispatcher)
if err := storeDispatcherInBead(beadID, actor); err != nil {
// Warn but don't fail - polecat will still complete work
fmt.Printf("%s Could not store dispatcher in bead: %v\n", style.Dim.Render("Warning:"), err)
}
// Store args in bead description (no-tmux mode: beads as data plane)
if slingArgs != "" {
if err := storeArgsInBead(beadID, slingArgs); err != nil {
@@ -520,6 +526,52 @@ func storeArgsInBead(beadID, args string) error {
return nil
}
// storeDispatcherInBead stores the dispatcher agent ID in the bead's description.
// This enables polecats to notify the dispatcher when work is complete.
func storeDispatcherInBead(beadID, dispatcher string) error {
if dispatcher == "" {
return nil
}
// Get the bead to preserve existing description content
showCmd := exec.Command("bd", "show", beadID, "--json")
out, err := showCmd.Output()
if err != nil {
return fmt.Errorf("fetching bead: %w", err)
}
// Parse the bead
var issues []beads.Issue
if err := json.Unmarshal(out, &issues); err != nil {
return fmt.Errorf("parsing bead: %w", err)
}
if len(issues) == 0 {
return fmt.Errorf("bead not found")
}
issue := &issues[0]
// Get or create attachment fields
fields := beads.ParseAttachmentFields(issue)
if fields == nil {
fields = &beads.AttachmentFields{}
}
// Set the dispatcher
fields.DispatchedBy = dispatcher
// Update the description
newDesc := beads.SetAttachmentFields(issue, fields)
// Update the bead
updateCmd := exec.Command("bd", "update", beadID, "--description="+newDesc)
updateCmd.Stderr = os.Stderr
if err := updateCmd.Run(); err != nil {
return fmt.Errorf("updating bead description: %w", err)
}
return nil
}
// injectStartPrompt sends a prompt to the target pane to start working.
// Uses the reliable nudge pattern: literal mode + 500ms debounce + separate Enter.
func injectStartPrompt(pane, beadID, subject, args string) error {
@@ -862,6 +914,12 @@ func runSlingFormula(args []string) error {
// Note: formula slinging uses town root as workDir (no polecat-specific path)
updateAgentHookBead(targetAgent, wispResult.RootID, "", townBeadsDir)
// Store dispatcher in bead description (enables completion notification to dispatcher)
if err := storeDispatcherInBead(wispResult.RootID, actor); err != nil {
// Warn but don't fail - polecat will still complete work
fmt.Printf("%s Could not store dispatcher in bead: %v\n", style.Dim.Render("Warning:"), err)
}
// Store args in wisp bead if provided (no-tmux mode: beads as data plane)
if slingArgs != "" {
if err := storeArgsInBead(wispResult.RootID, slingArgs); err != nil {