refactor: Move pending spawn tracking to gt spawn pending (hq-466n)
- Moved pending.go from deacon/ to polecat/ package - Changed storage location from deacon/pending.json to spawn/pending.json - Added 'gt spawn pending' subcommand for listing/clearing pending spawns - Deprecated 'gt deacon pending' (still works, shows deprecation notice) This decouples pending spawn observation from the Deacon role - anyone can now observe pending polecats (Mayor, humans, debugging, etc.). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/steveyegge/gastown/internal/config"
|
||||
@@ -35,6 +36,9 @@ var (
|
||||
spawnMolecule string
|
||||
spawnForce bool
|
||||
spawnAccount string
|
||||
|
||||
// spawn pending flags
|
||||
spawnPendingLines int
|
||||
)
|
||||
|
||||
var spawnCmd = &cobra.Command{
|
||||
@@ -44,6 +48,8 @@ var spawnCmd = &cobra.Command{
|
||||
Short: "Spawn a polecat with work assignment",
|
||||
Long: `Spawn a polecat with a work assignment.
|
||||
|
||||
Use 'gt spawn pending' to view spawns waiting to be triggered.
|
||||
|
||||
Assigns an issue or task to a polecat and starts a session. If no polecat
|
||||
is specified, auto-selects an idle polecat in the rig.
|
||||
|
||||
@@ -67,6 +73,27 @@ Examples:
|
||||
RunE: runSpawn,
|
||||
}
|
||||
|
||||
var spawnPendingCmd = &cobra.Command{
|
||||
Use: "pending [session-to-clear]",
|
||||
Short: "List pending spawns with captured output (for AI observation)",
|
||||
Long: `List pending polecat spawns with their terminal output for AI analysis.
|
||||
|
||||
This shows spawns waiting to be triggered (Claude is still initializing).
|
||||
The terminal output helps determine if Claude is ready.
|
||||
|
||||
Workflow:
|
||||
1. Run 'gt spawn pending' to see pending spawns and their output
|
||||
2. Analyze the output to determine if Claude is ready (look for "> " prompt)
|
||||
3. Run 'gt nudge <session> "Begin."' to trigger ready polecats
|
||||
4. Run 'gt spawn pending <session>' to clear from pending list
|
||||
|
||||
Examples:
|
||||
gt spawn pending # List all pending with output
|
||||
gt spawn pending gastown/p-abc123 # Clear specific session from pending`,
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
RunE: runSpawnPending,
|
||||
}
|
||||
|
||||
func init() {
|
||||
spawnCmd.Flags().StringVar(&spawnIssue, "issue", "", "Beads issue ID to assign")
|
||||
spawnCmd.Flags().StringVarP(&spawnMessage, "message", "m", "", "Free-form task description")
|
||||
@@ -78,6 +105,11 @@ func init() {
|
||||
spawnCmd.Flags().BoolVar(&spawnForce, "force", false, "Force spawn even if polecat has unread mail")
|
||||
spawnCmd.Flags().StringVar(&spawnAccount, "account", "", "Claude Code account handle to use (overrides default)")
|
||||
|
||||
// spawn pending flags
|
||||
spawnPendingCmd.Flags().IntVarP(&spawnPendingLines, "lines", "n", 15,
|
||||
"Number of terminal lines to capture per session")
|
||||
|
||||
spawnCmd.AddCommand(spawnPendingCmd)
|
||||
rootCmd.AddCommand(spawnCmd)
|
||||
}
|
||||
|
||||
@@ -741,3 +773,108 @@ func buildWorkAssignmentMail(issue *BeadsIssue, message, polecatAddress string,
|
||||
}
|
||||
}
|
||||
|
||||
// runSpawnPending shows pending spawns with captured output for AI observation.
|
||||
// This is the ZFC-compliant way to observe polecats waiting to be triggered.
|
||||
func runSpawnPending(cmd *cobra.Command, args []string) error {
|
||||
townRoot, err := workspace.FindFromCwdOrError()
|
||||
if err != nil {
|
||||
return fmt.Errorf("not in a Gas Town workspace: %w", err)
|
||||
}
|
||||
|
||||
// If session argument provided, clear it from pending
|
||||
if len(args) == 1 {
|
||||
return clearSpawnPending(townRoot, args[0])
|
||||
}
|
||||
|
||||
// Step 1: Check inbox for new POLECAT_STARTED messages
|
||||
pending, err := polecat.CheckInboxForSpawns(townRoot)
|
||||
if err != nil {
|
||||
return fmt.Errorf("checking inbox: %w", err)
|
||||
}
|
||||
|
||||
if len(pending) == 0 {
|
||||
fmt.Printf("%s No pending spawns\n", style.Dim.Render("○"))
|
||||
return nil
|
||||
}
|
||||
|
||||
t := tmux.NewTmux()
|
||||
|
||||
fmt.Printf("%s Pending spawns (%d):\n\n", style.Bold.Render("●"), len(pending))
|
||||
|
||||
for i, ps := range pending {
|
||||
// Check if session still exists
|
||||
running, err := t.HasSession(ps.Session)
|
||||
if err != nil {
|
||||
fmt.Printf("Session: %s\n", ps.Session)
|
||||
fmt.Printf(" Status: error checking session: %v\n\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !running {
|
||||
fmt.Printf("Session: %s\n", ps.Session)
|
||||
fmt.Printf(" Status: session no longer exists\n\n")
|
||||
continue
|
||||
}
|
||||
|
||||
// Capture terminal output for AI analysis
|
||||
output, err := t.CapturePane(ps.Session, spawnPendingLines)
|
||||
if err != nil {
|
||||
fmt.Printf("Session: %s\n", ps.Session)
|
||||
fmt.Printf(" Status: error capturing output: %v\n\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Print session info
|
||||
fmt.Printf("Session: %s\n", ps.Session)
|
||||
fmt.Printf(" Rig: %s\n", ps.Rig)
|
||||
fmt.Printf(" Polecat: %s\n", ps.Polecat)
|
||||
if ps.Issue != "" {
|
||||
fmt.Printf(" Issue: %s\n", ps.Issue)
|
||||
}
|
||||
fmt.Printf(" Spawned: %s ago\n", time.Since(ps.SpawnedAt).Round(time.Second))
|
||||
fmt.Printf(" Terminal output (last %d lines):\n", spawnPendingLines)
|
||||
fmt.Println(strings.Repeat("─", 50))
|
||||
fmt.Println(output)
|
||||
fmt.Println(strings.Repeat("─", 50))
|
||||
|
||||
if i < len(pending)-1 {
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
fmt.Printf("%s To trigger a ready polecat:\n", style.Dim.Render("→"))
|
||||
fmt.Printf(" gt nudge <session> \"Begin.\"\n")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// clearSpawnPending removes a session from the pending list.
|
||||
func clearSpawnPending(townRoot, session string) error {
|
||||
pending, err := polecat.LoadPending(townRoot)
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading pending: %w", err)
|
||||
}
|
||||
|
||||
var remaining []*polecat.PendingSpawn
|
||||
found := false
|
||||
for _, ps := range pending {
|
||||
if ps.Session == session {
|
||||
found = true
|
||||
continue
|
||||
}
|
||||
remaining = append(remaining, ps)
|
||||
}
|
||||
|
||||
if !found {
|
||||
return fmt.Errorf("session %s not found in pending list", session)
|
||||
}
|
||||
|
||||
if err := polecat.SavePending(townRoot, remaining); err != nil {
|
||||
return fmt.Errorf("saving pending: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("%s Cleared %s from pending list\n", style.Bold.Render("✓"), session)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user