feat: extract Gas Town types from beads core (bd-i54l)

Remove Gas Town-specific issue types (agent, role, rig, convoy, slot)
from beads core. These types are now identified by labels instead:
- gt:agent, gt:role, gt:rig, gt:convoy, gt:slot

Changes:
- internal/types/types.go: Remove TypeAgent, TypeRole, TypeRig, TypeConvoy, TypeSlot constants
- cmd/bd/agent.go: Create agents with TypeTask + gt:agent label
- cmd/bd/merge_slot.go: Create slots with TypeTask + gt:slot label
- internal/storage/sqlite/queries.go, transaction.go: Query convoys by gt:convoy label
- internal/rpc/server_issues_epics.go: Check gt:agent label for role_type/rig label auto-add
- cmd/bd/create.go: Check gt:agent label for role_type/rig label auto-add
- internal/ui/styles.go: Remove agent/role/rig type colors
- cmd/bd/export_obsidian.go: Remove agent/role/rig/convoy type tag mappings
- Update all affected tests

This enables beads to be a generic issue tracker while Gas Town
uses labels for its specific type semantics.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Executed-By: beads/crew/dave
Rig: beads
Role: crew
This commit is contained in:
dave
2026-01-06 22:18:37 -08:00
committed by Steve Yegge
parent b7358f17bf
commit a70c3a8cbe
14 changed files with 139 additions and 93 deletions

View File

@@ -30,7 +30,7 @@ var agentCmd = &cobra.Command{
Short: "Manage agent bead state",
Long: `Manage state on agent beads for ZFC-compliant state reporting.
Agent beads (type=agent) can self-report their state using these commands.
Agent beads (labeled gt:agent) can self-report their state using these commands.
This enables the Witness and other monitoring systems to track agent health.
States:
@@ -207,7 +207,7 @@ func runAgentState(cmd *cobra.Command, args []string) error {
agent = &types.Issue{
ID: agentID,
Title: fmt.Sprintf("Agent: %s", agentID),
IssueType: types.TypeAgent,
IssueType: types.TypeTask, // Use task type; gt:agent label marks it as agent
Status: types.StatusOpen,
RoleType: roleType,
Rig: rig,
@@ -218,10 +218,11 @@ func runAgentState(cmd *cobra.Command, args []string) error {
createArgs := &rpc.CreateArgs{
ID: agentID,
Title: agent.Title,
IssueType: string(types.TypeAgent),
IssueType: string(types.TypeTask), // Use task type; gt:agent label marks it as agent
RoleType: roleType,
Rig: rig,
CreatedBy: actor,
Labels: []string{"gt:agent"}, // Gas Town agent label
}
resp, err := daemonClient.Create(createArgs)
if err != nil {
@@ -234,6 +235,10 @@ func runAgentState(cmd *cobra.Command, args []string) error {
if err := activeStore.CreateIssue(ctx, agent, actor); err != nil {
return fmt.Errorf("failed to auto-create agent bead %s: %w", agentID, err)
}
// Add gt:agent label to mark as agent bead
if err := activeStore.AddLabel(ctx, agent.ID, "gt:agent", actor); err != nil {
fmt.Fprintf(os.Stderr, "warning: failed to add gt:agent label: %v\n", err)
}
// Add role_type and rig labels for filtering
if roleType != "" {
if err := activeStore.AddLabel(ctx, agent.ID, "role_type:"+roleType, actor); err != nil {
@@ -248,9 +253,12 @@ func runAgentState(cmd *cobra.Command, args []string) error {
}
} else {
// Get existing agent bead to verify it's an agent
var labels []string
if routedResult != nil && routedResult.Issue != nil {
// Already have the issue from routed resolution
agent = routedResult.Issue
// Get labels from routed store
labels, _ = routedResult.Store.GetLabels(ctx, agentID)
} else if daemonClient != nil && !needsRouting(agentArg) {
resp, err := daemonClient.Show(&rpc.ShowArgs{ID: agentID})
if err != nil {
@@ -259,17 +267,19 @@ func runAgentState(cmd *cobra.Command, args []string) error {
if err := json.Unmarshal(resp.Data, &agent); err != nil {
return fmt.Errorf("parsing response: %w", err)
}
labels = agent.Labels
} else {
var err error
agent, err = activeStore.GetIssue(ctx, agentID)
if err != nil || agent == nil {
return fmt.Errorf("agent bead not found: %s", agentID)
}
labels, _ = activeStore.GetLabels(ctx, agentID)
}
// Verify agent bead is actually an agent
if agent.IssueType != "agent" {
return fmt.Errorf("%s is not an agent bead (type=%s)", agentID, agent.IssueType)
// Verify agent bead is actually an agent (check for gt:agent label)
if !isAgentBead(labels) {
return fmt.Errorf("%s is not an agent bead (missing gt:agent label)", agentID)
}
}
@@ -362,9 +372,11 @@ func runAgentHeartbeat(cmd *cobra.Command, args []string) error {
// Get agent bead to verify it's an agent
var agent *types.Issue
var labels []string
if routedResult != nil && routedResult.Issue != nil {
// Already have the issue from routed resolution
agent = routedResult.Issue
labels, _ = routedResult.Store.GetLabels(ctx, agentID)
} else if daemonClient != nil && !needsRouting(agentArg) {
resp, err := daemonClient.Show(&rpc.ShowArgs{ID: agentID})
if err != nil {
@@ -373,17 +385,19 @@ func runAgentHeartbeat(cmd *cobra.Command, args []string) error {
if err := json.Unmarshal(resp.Data, &agent); err != nil {
return fmt.Errorf("parsing response: %w", err)
}
labels = agent.Labels
} else {
var err error
agent, err = activeStore.GetIssue(ctx, agentID)
if err != nil || agent == nil {
return fmt.Errorf("agent bead not found: %s", agentID)
}
labels, _ = activeStore.GetLabels(ctx, agentID)
}
// Verify agent bead is actually an agent
if agent.IssueType != "agent" {
return fmt.Errorf("%s is not an agent bead (type=%s)", agentID, agent.IssueType)
// Verify agent bead is actually an agent (check for gt:agent label)
if !isAgentBead(labels) {
return fmt.Errorf("%s is not an agent bead (missing gt:agent label)", agentID)
}
// Update only last_activity
@@ -464,9 +478,11 @@ func runAgentShow(cmd *cobra.Command, args []string) error {
// Get agent bead
var agent *types.Issue
var labels []string
if routedResult != nil && routedResult.Issue != nil {
// Already have the issue from routed resolution
agent = routedResult.Issue
labels, _ = routedResult.Store.GetLabels(ctx, agentID)
} else if daemonClient != nil && !needsRouting(agentArg) {
resp, err := daemonClient.Show(&rpc.ShowArgs{ID: agentID})
if err != nil {
@@ -475,17 +491,19 @@ func runAgentShow(cmd *cobra.Command, args []string) error {
if err := json.Unmarshal(resp.Data, &agent); err != nil {
return fmt.Errorf("parsing response: %w", err)
}
labels = agent.Labels
} else {
var err error
agent, err = store.GetIssue(ctx, agentID)
if err != nil || agent == nil {
return fmt.Errorf("agent bead not found: %s", agentID)
}
labels, _ = store.GetLabels(ctx, agentID)
}
// Verify agent bead is actually an agent
if agent.IssueType != "agent" {
return fmt.Errorf("%s is not an agent bead (type=%s)", agentID, agent.IssueType)
// Verify agent bead is actually an agent (check for gt:agent label)
if !isAgentBead(labels) {
return fmt.Errorf("%s is not an agent bead (missing gt:agent label)", agentID)
}
if jsonOutput {
@@ -565,11 +583,11 @@ func runAgentBackfillLabels(cmd *cobra.Command, args []string) error {
ctx := rootCtx
// List all agent beads
// List all agent beads (by gt:agent label)
var agents []*types.Issue
if daemonClient != nil {
resp, err := daemonClient.List(&rpc.ListArgs{
IssueType: "agent",
Labels: []string{"gt:agent"},
})
if err != nil {
return fmt.Errorf("failed to list agents: %w", err)
@@ -578,9 +596,8 @@ func runAgentBackfillLabels(cmd *cobra.Command, args []string) error {
return fmt.Errorf("parsing response: %w", err)
}
} else {
agentType := types.TypeAgent
filter := types.IssueFilter{
IssueType: &agentType,
Labels: []string{"gt:agent"},
}
var err error
agents, err = store.SearchIssues(ctx, "", filter)
@@ -756,6 +773,17 @@ func containsLabel(labels []string, label string) bool {
return false
}
// isAgentBead checks if an issue is an agent bead by looking for the gt:agent label.
// This replaces the previous type-based check (issue_type='agent') for Gas Town separation.
func isAgentBead(labels []string) bool {
for _, l := range labels {
if l == "gt:agent" {
return true
}
}
return false
}
// parseAgentIDFields extracts role_type and rig from an agent bead ID.
// Agent ID patterns:
// - Town-level: <prefix>-<role> (e.g., gt-mayor) → role="mayor", rig=""