feat(gate): add add-waiter and show commands for phase handoff
- Add `bd gate add-waiter <gate-id> <waiter>` command to register agents as waiters on a gate bead using the native Waiters field - Add `bd gate show <gate-id>` command to display gate details including waiters (used by gt gate wake) - Add Waiters field to UpdateArgs in RPC protocol - Update server to handle waiters field in updates This is part of the polecat phase handoff feature (bd-quw1). The corresponding gastown changes (gt done --phase-complete) are tracked separately. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
171
cmd/bd/gate.go
171
cmd/bd/gate.go
@@ -167,6 +167,175 @@ func displayGates(gates []*types.Issue) {
|
||||
fmt.Printf("To resolve a gate: bd close <gate-id>\n")
|
||||
}
|
||||
|
||||
// gateAddWaiterCmd adds a waiter to a gate
|
||||
var gateAddWaiterCmd = &cobra.Command{
|
||||
Use: "add-waiter <gate-id> <waiter>",
|
||||
Short: "Add a waiter to a gate",
|
||||
Long: `Register an agent as a waiter on a gate bead.
|
||||
|
||||
When the gate closes, the waiter will receive a wake notification via 'gt gate wake'.
|
||||
The waiter is typically the polecat's address (e.g., "gastown/polecats/Toast").
|
||||
|
||||
This is used by 'gt done --phase-complete' to register for gate wake notifications.`,
|
||||
Args: cobra.ExactArgs(2),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
CheckReadonly("gate add-waiter")
|
||||
|
||||
gateID := args[0]
|
||||
waiter := args[1]
|
||||
ctx := rootCtx
|
||||
|
||||
// Get the gate issue
|
||||
var issue *types.Issue
|
||||
var err error
|
||||
|
||||
if daemonClient != nil {
|
||||
resp, rerr := daemonClient.Show(&rpc.ShowArgs{ID: gateID})
|
||||
if rerr != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", rerr)
|
||||
os.Exit(1)
|
||||
}
|
||||
if !resp.Success {
|
||||
fmt.Fprintf(os.Stderr, "Error: %s\n", resp.Error)
|
||||
os.Exit(1)
|
||||
}
|
||||
var details types.IssueDetails
|
||||
if uerr := json.Unmarshal(resp.Data, &details); uerr != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error parsing response: %v\n", uerr)
|
||||
os.Exit(1)
|
||||
}
|
||||
issue = &details.Issue
|
||||
} else {
|
||||
issue, err = store.GetIssue(ctx, gateID)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: gate not found: %s\n", gateID)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
if issue.IssueType != types.TypeGate {
|
||||
fmt.Fprintf(os.Stderr, "Error: %s is not a gate issue (type=%s)\n", gateID, issue.IssueType)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Check if waiter is already registered
|
||||
for _, w := range issue.Waiters {
|
||||
if w == waiter {
|
||||
fmt.Printf("Waiter already registered on gate %s\n", gateID)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Add waiter to the waiters list
|
||||
newWaiters := append(issue.Waiters, waiter)
|
||||
|
||||
// Update the gate
|
||||
if daemonClient != nil {
|
||||
updateArgs := &rpc.UpdateArgs{
|
||||
ID: gateID,
|
||||
Waiters: newWaiters,
|
||||
}
|
||||
resp, uerr := daemonClient.Update(updateArgs)
|
||||
if uerr != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", uerr)
|
||||
os.Exit(1)
|
||||
}
|
||||
if !resp.Success {
|
||||
fmt.Fprintf(os.Stderr, "Error: %s\n", resp.Error)
|
||||
os.Exit(1)
|
||||
}
|
||||
} else {
|
||||
updates := map[string]interface{}{
|
||||
"waiters": newWaiters,
|
||||
}
|
||||
if err := store.UpdateIssue(ctx, gateID, updates, actor); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error updating gate: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
markDirtyAndScheduleFlush()
|
||||
}
|
||||
|
||||
fmt.Printf("%s Added waiter to gate %s: %s\n", ui.RenderPass("✓"), gateID, waiter)
|
||||
},
|
||||
}
|
||||
|
||||
// gateShowCmd shows a gate issue
|
||||
var gateShowCmd = &cobra.Command{
|
||||
Use: "show <gate-id>",
|
||||
Short: "Show a gate issue",
|
||||
Long: `Display details of a gate issue including its waiters.
|
||||
|
||||
This is similar to 'bd show' but validates that the issue is a gate.`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
gateID := args[0]
|
||||
ctx := rootCtx
|
||||
|
||||
// Get the gate issue
|
||||
var issue *types.Issue
|
||||
var err error
|
||||
|
||||
if daemonClient != nil {
|
||||
resp, rerr := daemonClient.Show(&rpc.ShowArgs{ID: gateID})
|
||||
if rerr != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", rerr)
|
||||
os.Exit(1)
|
||||
}
|
||||
if !resp.Success {
|
||||
fmt.Fprintf(os.Stderr, "Error: %s\n", resp.Error)
|
||||
os.Exit(1)
|
||||
}
|
||||
var details types.IssueDetails
|
||||
if uerr := json.Unmarshal(resp.Data, &details); uerr != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error parsing response: %v\n", uerr)
|
||||
os.Exit(1)
|
||||
}
|
||||
issue = &details.Issue
|
||||
} else {
|
||||
issue, err = store.GetIssue(ctx, gateID)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: gate not found: %s\n", gateID)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
if issue.IssueType != types.TypeGate {
|
||||
fmt.Fprintf(os.Stderr, "Error: %s is not a gate issue (type=%s)\n", gateID, issue.IssueType)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if jsonOutput {
|
||||
outputJSON(issue)
|
||||
return
|
||||
}
|
||||
|
||||
// Display gate details
|
||||
statusSym := "○"
|
||||
if issue.Status == types.StatusClosed {
|
||||
statusSym = "●"
|
||||
}
|
||||
|
||||
fmt.Printf("%s %s - %s\n", statusSym, ui.RenderID(issue.ID), issue.Title)
|
||||
fmt.Printf(" Status: %s\n", issue.Status)
|
||||
fmt.Printf(" Await Type: %s\n", issue.AwaitType)
|
||||
if issue.AwaitID != "" {
|
||||
fmt.Printf(" Await ID: %s\n", issue.AwaitID)
|
||||
}
|
||||
if issue.Timeout > 0 {
|
||||
fmt.Printf(" Timeout: %s\n", issue.Timeout)
|
||||
}
|
||||
if len(issue.Waiters) > 0 {
|
||||
fmt.Printf(" Waiters:\n")
|
||||
for _, w := range issue.Waiters {
|
||||
fmt.Printf(" - %s\n", w)
|
||||
}
|
||||
}
|
||||
if issue.Description != "" {
|
||||
fmt.Printf(" Description: %s\n", issue.Description)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// gateResolveCmd manually closes a gate
|
||||
var gateResolveCmd = &cobra.Command{
|
||||
Use: "resolve <gate-id>",
|
||||
@@ -703,8 +872,10 @@ func init() {
|
||||
|
||||
// Add subcommands
|
||||
gateCmd.AddCommand(gateListCmd)
|
||||
gateCmd.AddCommand(gateShowCmd)
|
||||
gateCmd.AddCommand(gateResolveCmd)
|
||||
gateCmd.AddCommand(gateCheckCmd)
|
||||
gateCmd.AddCommand(gateAddWaiterCmd)
|
||||
|
||||
rootCmd.AddCommand(gateCmd)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user