feat: Filter agent session molecule noise from activity feed
Agent session molecules (gt-gastown-crew-joe, gt-gastown-witness, etc.) update frequently for status tracking, creating noisy entries in the activity feed. This change: - Adds IsAgentSessionBead() to identify agent session beads - Filters out "update" events for agent sessions from the event feed - Still updates the agent tree so status is visible there - Still shows create/complete/fail/delete events for agents The filtering happens in addEvent() in the TUI feed model. Agent session updates are identified by parsing the bead ID pattern and checking for known agent roles (mayor, deacon, witness, refinery, crew, polecat). Resolves: gt-sb6m4 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -822,3 +822,20 @@ func ParseAgentBeadID(id string) (rig, role, name string, ok bool) {
|
||||
return "", "", "", false
|
||||
}
|
||||
}
|
||||
|
||||
// IsAgentSessionBead returns true if the bead ID represents an agent session molecule.
|
||||
// Agent session beads follow patterns like gt-mayor, gt-gastown-witness, gt-gastown-crew-joe.
|
||||
// These are used to track agent state and update frequently, which can create noise.
|
||||
func IsAgentSessionBead(beadID string) bool {
|
||||
_, role, _, ok := ParseAgentBeadID(beadID)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
// Known agent roles
|
||||
switch role {
|
||||
case "mayor", "deacon", "witness", "refinery", "crew", "polecat":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -967,3 +967,81 @@ func TestResolveBeadsDir(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestParseAgentBeadID(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
wantRig string
|
||||
wantRole string
|
||||
wantName string
|
||||
wantOK bool
|
||||
}{
|
||||
// Town-level agents
|
||||
{"gt-mayor", "", "mayor", "", true},
|
||||
{"gt-deacon", "", "deacon", "", true},
|
||||
// Rig-level singletons
|
||||
{"gt-gastown-witness", "gastown", "witness", "", true},
|
||||
{"gt-gastown-refinery", "gastown", "refinery", "", true},
|
||||
// Rig-level named agents
|
||||
{"gt-gastown-crew-joe", "gastown", "crew", "joe", true},
|
||||
{"gt-gastown-crew-max", "gastown", "crew", "max", true},
|
||||
{"gt-gastown-polecat-capable", "gastown", "polecat", "capable", true},
|
||||
// Names with hyphens
|
||||
{"gt-gastown-polecat-my-agent", "gastown", "polecat", "my-agent", true},
|
||||
// Parseable but not valid agent roles (IsAgentSessionBead will reject)
|
||||
{"gt-abc123", "", "abc123", "", true}, // Parses as town-level but not valid role
|
||||
// Truly invalid patterns
|
||||
{"bd-gastown-crew-joe", "", "", "", false}, // Wrong prefix
|
||||
{"", "", "", "", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.input, func(t *testing.T) {
|
||||
rig, role, name, ok := ParseAgentBeadID(tt.input)
|
||||
if ok != tt.wantOK {
|
||||
t.Errorf("ParseAgentBeadID(%q) ok = %v, want %v", tt.input, ok, tt.wantOK)
|
||||
return
|
||||
}
|
||||
if rig != tt.wantRig {
|
||||
t.Errorf("ParseAgentBeadID(%q) rig = %q, want %q", tt.input, rig, tt.wantRig)
|
||||
}
|
||||
if role != tt.wantRole {
|
||||
t.Errorf("ParseAgentBeadID(%q) role = %q, want %q", tt.input, role, tt.wantRole)
|
||||
}
|
||||
if name != tt.wantName {
|
||||
t.Errorf("ParseAgentBeadID(%q) name = %q, want %q", tt.input, name, tt.wantName)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsAgentSessionBead(t *testing.T) {
|
||||
tests := []struct {
|
||||
beadID string
|
||||
want bool
|
||||
}{
|
||||
// Agent session beads (should return true)
|
||||
{"gt-mayor", true},
|
||||
{"gt-deacon", true},
|
||||
{"gt-gastown-witness", true},
|
||||
{"gt-gastown-refinery", true},
|
||||
{"gt-gastown-crew-joe", true},
|
||||
{"gt-gastown-polecat-capable", true},
|
||||
// Regular work beads (should return false)
|
||||
{"gt-abc123", false},
|
||||
{"gt-sb6m4", false},
|
||||
{"gt-u7dxq", false},
|
||||
// Invalid beads
|
||||
{"bd-abc123", false},
|
||||
{"", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.beadID, func(t *testing.T) {
|
||||
got := IsAgentSessionBead(tt.beadID)
|
||||
if got != tt.want {
|
||||
t.Errorf("IsAgentSessionBead(%q) = %v, want %v", tt.beadID, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/charmbracelet/bubbles/key"
|
||||
"github.com/charmbracelet/bubbles/viewport"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/steveyegge/gastown/internal/beads"
|
||||
)
|
||||
|
||||
// Panel represents which panel has focus
|
||||
@@ -252,14 +253,7 @@ func (m *Model) updateViewContent() {
|
||||
|
||||
// addEvent adds an event and updates the agent tree
|
||||
func (m *Model) addEvent(e Event) {
|
||||
m.events = append(m.events, e)
|
||||
|
||||
// Keep max 1000 events
|
||||
if len(m.events) > 1000 {
|
||||
m.events = m.events[len(m.events)-1000:]
|
||||
}
|
||||
|
||||
// Update agent tree
|
||||
// Update agent tree first (always do this for status tracking)
|
||||
if e.Rig != "" {
|
||||
rig, ok := m.rigs[e.Rig]
|
||||
if !ok {
|
||||
@@ -287,6 +281,26 @@ func (m *Model) addEvent(e Event) {
|
||||
}
|
||||
}
|
||||
|
||||
// Filter out noisy agent session updates from the event feed.
|
||||
// Agent session molecules (like gt-gastown-crew-joe) update frequently
|
||||
// for status tracking. These updates are visible in the agent tree,
|
||||
// so we don't need to clutter the event feed with them.
|
||||
// We still show create/complete/fail/delete events for agent sessions.
|
||||
if e.Type == "update" && beads.IsAgentSessionBead(e.Target) {
|
||||
// Skip adding to event feed, but still refresh the view
|
||||
// (agent tree was updated above)
|
||||
m.updateViewContent()
|
||||
return
|
||||
}
|
||||
|
||||
// Add to event feed
|
||||
m.events = append(m.events, e)
|
||||
|
||||
// Keep max 1000 events
|
||||
if len(m.events) > 1000 {
|
||||
m.events = m.events[len(m.events)-1000:]
|
||||
}
|
||||
|
||||
m.updateViewContent()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user