Merge origin/main
This commit is contained in:
@@ -101,12 +101,10 @@ func runCrewAt(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Apply rig-based theming (non-fatal: theming failure doesn't affect operation)
|
// Apply rig-based theming (non-fatal: theming failure doesn't affect operation)
|
||||||
|
// Note: ConfigureGasTownSession includes cycle bindings
|
||||||
theme := getThemeForRig(r.Name)
|
theme := getThemeForRig(r.Name)
|
||||||
_ = t.ConfigureGasTownSession(sessionID, theme, r.Name, name, "crew")
|
_ = t.ConfigureGasTownSession(sessionID, theme, r.Name, name, "crew")
|
||||||
|
|
||||||
// Set up C-b n/p keybindings for crew session cycling (non-fatal)
|
|
||||||
_ = t.SetCrewCycleBindings(sessionID)
|
|
||||||
|
|
||||||
// Wait for shell to be ready after session creation
|
// Wait for shell to be ready after session creation
|
||||||
if err := t.WaitForShellReady(sessionID, 5*time.Second); err != nil {
|
if err := t.WaitForShellReady(sessionID, 5*time.Second); err != nil {
|
||||||
return fmt.Errorf("waiting for shell: %w", err)
|
return fmt.Errorf("waiting for shell: %w", err)
|
||||||
|
|||||||
+88
-1
@@ -1,6 +1,9 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -28,6 +31,7 @@ var cycleCmd = &cobra.Command{
|
|||||||
Session groups:
|
Session groups:
|
||||||
- Town sessions: Mayor ↔ Deacon
|
- Town sessions: Mayor ↔ Deacon
|
||||||
- Crew sessions: All crew members in the same rig (e.g., gastown/crew/max ↔ gastown/crew/joe)
|
- Crew sessions: All crew members in the same rig (e.g., gastown/crew/max ↔ gastown/crew/joe)
|
||||||
|
- Rig infra sessions: Witness ↔ Refinery (per rig)
|
||||||
|
|
||||||
The appropriate cycling is detected automatically from the session name.`,
|
The appropriate cycling is detected automatically from the session name.`,
|
||||||
}
|
}
|
||||||
@@ -83,6 +87,89 @@ func cycleToSession(direction int, sessionOverride string) error {
|
|||||||
return cycleCrewSession(direction, session)
|
return cycleCrewSession(direction, session)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unknown session type (polecat, witness, refinery) - do nothing
|
// Check if it's a rig infra session (witness or refinery)
|
||||||
|
if rig := parseRigInfraSession(session); rig != "" {
|
||||||
|
return cycleRigInfraSession(direction, session, rig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unknown session type (polecat) - do nothing
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseRigInfraSession extracts rig name if this is a witness or refinery session.
|
||||||
|
// Returns empty string if not a rig infra session.
|
||||||
|
// Format: gt-<rig>-witness or gt-<rig>-refinery
|
||||||
|
func parseRigInfraSession(session string) string {
|
||||||
|
if !strings.HasPrefix(session, "gt-") {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
rest := session[3:] // Remove "gt-" prefix
|
||||||
|
|
||||||
|
// Check for -witness or -refinery suffix
|
||||||
|
if strings.HasSuffix(rest, "-witness") {
|
||||||
|
return strings.TrimSuffix(rest, "-witness")
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(rest, "-refinery") {
|
||||||
|
return strings.TrimSuffix(rest, "-refinery")
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// cycleRigInfraSession cycles between witness and refinery sessions for a rig.
|
||||||
|
func cycleRigInfraSession(direction int, currentSession, rig string) error {
|
||||||
|
// Find running infra sessions for this rig
|
||||||
|
witnessSession := fmt.Sprintf("gt-%s-witness", rig)
|
||||||
|
refinerySession := fmt.Sprintf("gt-%s-refinery", rig)
|
||||||
|
|
||||||
|
var sessions []string
|
||||||
|
allSessions, err := listTmuxSessions()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range allSessions {
|
||||||
|
if s == witnessSession || s == refinerySession {
|
||||||
|
sessions = append(sessions, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(sessions) == 0 {
|
||||||
|
return nil // No infra sessions running
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort for consistent ordering
|
||||||
|
sort.Strings(sessions)
|
||||||
|
|
||||||
|
// Find current position
|
||||||
|
currentIdx := -1
|
||||||
|
for i, s := range sessions {
|
||||||
|
if s == currentSession {
|
||||||
|
currentIdx = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if currentIdx == -1 {
|
||||||
|
return nil // Current session not in list
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate target index (with wrapping)
|
||||||
|
targetIdx := (currentIdx + direction + len(sessions)) % len(sessions)
|
||||||
|
|
||||||
|
if targetIdx == currentIdx {
|
||||||
|
return nil // Only one session
|
||||||
|
}
|
||||||
|
|
||||||
|
// Switch to target session
|
||||||
|
cmd := exec.Command("tmux", "switch-client", "-t", sessions[targetIdx])
|
||||||
|
return cmd.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
// listTmuxSessions returns all tmux session names.
|
||||||
|
func listTmuxSessions() ([]string, error) {
|
||||||
|
out, err := exec.Command("tmux", "list-sessions", "-F", "#{session_name}").Output()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return splitLines(string(out)), nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -185,12 +185,10 @@ func startDeaconSession(t *tmux.Tmux) error {
|
|||||||
_ = t.SetEnvironment(DeaconSessionName, "BD_ACTOR", "deacon")
|
_ = t.SetEnvironment(DeaconSessionName, "BD_ACTOR", "deacon")
|
||||||
|
|
||||||
// Apply Deacon theme (non-fatal: theming failure doesn't affect operation)
|
// Apply Deacon theme (non-fatal: theming failure doesn't affect operation)
|
||||||
|
// Note: ConfigureGasTownSession includes cycle bindings
|
||||||
theme := tmux.DeaconTheme()
|
theme := tmux.DeaconTheme()
|
||||||
_ = t.ConfigureGasTownSession(DeaconSessionName, theme, "", "Deacon", "health-check")
|
_ = t.ConfigureGasTownSession(DeaconSessionName, theme, "", "Deacon", "health-check")
|
||||||
|
|
||||||
// Set up C-b n/p keybindings for town session cycling (non-fatal)
|
|
||||||
_ = t.SetTownCycleBindings(DeaconSessionName)
|
|
||||||
|
|
||||||
// Launch Claude directly (no shell respawn loop)
|
// Launch Claude directly (no shell respawn loop)
|
||||||
// Restarts are handled by daemon via ensureDeaconRunning on each heartbeat
|
// Restarts are handled by daemon via ensureDeaconRunning on each heartbeat
|
||||||
// The startup hook handles context loading automatically
|
// The startup hook handles context loading automatically
|
||||||
|
|||||||
@@ -75,6 +75,12 @@ Examples:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func runFeed(cmd *cobra.Command, args []string) error {
|
func runFeed(cmd *cobra.Command, args []string) error {
|
||||||
|
// Must be in a Gas Town workspace
|
||||||
|
townRoot, err := workspace.FindFromCwdOrError()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("not in a Gas Town workspace (run from ~/gt or a rig directory)")
|
||||||
|
}
|
||||||
|
|
||||||
// Determine working directory
|
// Determine working directory
|
||||||
workDir, err := os.Getwd()
|
workDir, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -83,11 +89,6 @@ func runFeed(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
// If --rig specified, find that rig's beads directory
|
// If --rig specified, find that rig's beads directory
|
||||||
if feedRig != "" {
|
if feedRig != "" {
|
||||||
townRoot, err := workspace.FindFromCwdOrError()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("not in a Gas Town workspace: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try common beads locations for the rig
|
// Try common beads locations for the rig
|
||||||
candidates := []string{
|
candidates := []string{
|
||||||
fmt.Sprintf("%s/%s/mayor/rig", townRoot, feedRig),
|
fmt.Sprintf("%s/%s/mayor/rig", townRoot, feedRig),
|
||||||
|
|||||||
@@ -123,12 +123,10 @@ func startMayorSession(t *tmux.Tmux) error {
|
|||||||
_ = t.SetEnvironment(MayorSessionName, "BD_ACTOR", "mayor")
|
_ = t.SetEnvironment(MayorSessionName, "BD_ACTOR", "mayor")
|
||||||
|
|
||||||
// Apply Mayor theme (non-fatal: theming failure doesn't affect operation)
|
// Apply Mayor theme (non-fatal: theming failure doesn't affect operation)
|
||||||
|
// Note: ConfigureGasTownSession includes cycle bindings
|
||||||
theme := tmux.MayorTheme()
|
theme := tmux.MayorTheme()
|
||||||
_ = t.ConfigureGasTownSession(MayorSessionName, theme, "", "Mayor", "coordinator")
|
_ = t.ConfigureGasTownSession(MayorSessionName, theme, "", "Mayor", "coordinator")
|
||||||
|
|
||||||
// Set up C-b n/p keybindings for town session cycling (non-fatal)
|
|
||||||
_ = t.SetTownCycleBindings(MayorSessionName)
|
|
||||||
|
|
||||||
// Launch Claude - the startup hook handles 'gt prime' automatically
|
// Launch Claude - the startup hook handles 'gt prime' automatically
|
||||||
// Use SendKeysDelayed to allow shell initialization after NewSession
|
// Use SendKeysDelayed to allow shell initialization after NewSession
|
||||||
// Export GT_ROLE and BD_ACTOR in the command since tmux SetEnvironment only affects new panes
|
// Export GT_ROLE and BD_ACTOR in the command since tmux SetEnvironment only affects new panes
|
||||||
|
|||||||
@@ -747,7 +747,7 @@ func runStartCrew(cmd *cobra.Command, args []string) error {
|
|||||||
return fmt.Errorf("restarting claude: %w", err)
|
return fmt.Errorf("restarting claude: %w", err)
|
||||||
}
|
}
|
||||||
// Wait for Claude to start, then prime
|
// Wait for Claude to start, then prime
|
||||||
shells := []string{"bash", "zsh", "sh", "fish", "tcsh", "ksh"}
|
shells := constants.SupportedShells
|
||||||
if err := t.WaitForCommand(sessionID, shells, 15*time.Second); err != nil {
|
if err := t.WaitForCommand(sessionID, shells, 15*time.Second); err != nil {
|
||||||
style.PrintWarning("Timeout waiting for Claude to start: %v", err)
|
style.PrintWarning("Timeout waiting for Claude to start: %v", err)
|
||||||
}
|
}
|
||||||
@@ -774,12 +774,10 @@ func runStartCrew(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Apply rig-based theming (non-fatal: theming failure doesn't affect operation)
|
// Apply rig-based theming (non-fatal: theming failure doesn't affect operation)
|
||||||
|
// Note: ConfigureGasTownSession includes cycle bindings
|
||||||
theme := getThemeForRig(rigName)
|
theme := getThemeForRig(rigName)
|
||||||
_ = t.ConfigureGasTownSession(sessionID, theme, rigName, name, "crew")
|
_ = t.ConfigureGasTownSession(sessionID, theme, rigName, name, "crew")
|
||||||
|
|
||||||
// Set up C-b n/p keybindings for crew session cycling (non-fatal)
|
|
||||||
_ = t.SetCrewCycleBindings(sessionID)
|
|
||||||
|
|
||||||
// Wait for shell to be ready after session creation
|
// Wait for shell to be ready after session creation
|
||||||
if err := t.WaitForShellReady(sessionID, 5*time.Second); err != nil {
|
if err := t.WaitForShellReady(sessionID, 5*time.Second); err != nil {
|
||||||
return fmt.Errorf("waiting for shell: %w", err)
|
return fmt.Errorf("waiting for shell: %w", err)
|
||||||
@@ -791,7 +789,7 @@ func runStartCrew(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Wait for Claude to start
|
// Wait for Claude to start
|
||||||
shells := []string{"bash", "zsh", "sh", "fish", "tcsh", "ksh"}
|
shells := constants.SupportedShells
|
||||||
if err := t.WaitForCommand(sessionID, shells, 15*time.Second); err != nil {
|
if err := t.WaitForCommand(sessionID, shells, 15*time.Second); err != nil {
|
||||||
style.PrintWarning("Timeout waiting for Claude to start: %v", err)
|
style.PrintWarning("Timeout waiting for Claude to start: %v", err)
|
||||||
}
|
}
|
||||||
@@ -925,7 +923,7 @@ func startCrewMember(rigName, crewName, townRoot string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Wait for Claude to start
|
// Wait for Claude to start
|
||||||
shells := []string{"bash", "zsh", "sh", "fish", "tcsh", "ksh"}
|
shells := constants.SupportedShells
|
||||||
if err := t.WaitForCommand(sessionID, shells, 15*time.Second); err != nil {
|
if err := t.WaitForCommand(sessionID, shells, 15*time.Second); err != nil {
|
||||||
// Non-fatal: Claude might still be starting
|
// Non-fatal: Claude might still be starting
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -611,6 +611,9 @@ func (t *Tmux) ConfigureGasTownSession(session string, theme Theme, rig, worker,
|
|||||||
if err := t.SetFeedBinding(session); err != nil {
|
if err := t.SetFeedBinding(session); err != nil {
|
||||||
return fmt.Errorf("setting feed binding: %w", err)
|
return fmt.Errorf("setting feed binding: %w", err)
|
||||||
}
|
}
|
||||||
|
if err := t.SetCycleBindings(session); err != nil {
|
||||||
|
return fmt.Errorf("setting cycle bindings: %w", err)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+15
-12
@@ -1,6 +1,7 @@
|
|||||||
package feed
|
package feed
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/charmbracelet/bubbles/help"
|
"github.com/charmbracelet/bubbles/help"
|
||||||
@@ -68,20 +69,19 @@ type Model struct {
|
|||||||
help help.Model
|
help help.Model
|
||||||
showHelp bool
|
showHelp bool
|
||||||
filter string
|
filter string
|
||||||
filterActive bool
|
|
||||||
err error
|
|
||||||
|
|
||||||
// Event source
|
// Event source
|
||||||
eventChan <-chan Event
|
eventChan <-chan Event
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
|
closeOnce sync.Once
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewModel creates a new feed TUI model
|
// NewModel creates a new feed TUI model
|
||||||
func NewModel() Model {
|
func NewModel() *Model {
|
||||||
h := help.New()
|
h := help.New()
|
||||||
h.ShowAll = false
|
h.ShowAll = false
|
||||||
|
|
||||||
return Model{
|
return &Model{
|
||||||
focusedPanel: PanelTree,
|
focusedPanel: PanelTree,
|
||||||
treeViewport: viewport.New(0, 0),
|
treeViewport: viewport.New(0, 0),
|
||||||
feedViewport: viewport.New(0, 0),
|
feedViewport: viewport.New(0, 0),
|
||||||
@@ -94,7 +94,7 @@ func NewModel() Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Init initializes the model
|
// Init initializes the model
|
||||||
func (m Model) Init() tea.Cmd {
|
func (m *Model) Init() tea.Cmd {
|
||||||
return tea.Batch(
|
return tea.Batch(
|
||||||
m.listenForEvents(),
|
m.listenForEvents(),
|
||||||
tea.SetWindowTitle("GT Feed"),
|
tea.SetWindowTitle("GT Feed"),
|
||||||
@@ -108,18 +108,21 @@ type eventMsg Event
|
|||||||
type tickMsg time.Time
|
type tickMsg time.Time
|
||||||
|
|
||||||
// listenForEvents returns a command that listens for events
|
// listenForEvents returns a command that listens for events
|
||||||
func (m Model) listenForEvents() tea.Cmd {
|
func (m *Model) listenForEvents() tea.Cmd {
|
||||||
if m.eventChan == nil {
|
if m.eventChan == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
// Capture channels to avoid race with Model mutations
|
||||||
|
eventChan := m.eventChan
|
||||||
|
done := m.done
|
||||||
return func() tea.Msg {
|
return func() tea.Msg {
|
||||||
select {
|
select {
|
||||||
case event, ok := <-m.eventChan:
|
case event, ok := <-eventChan:
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return eventMsg(event)
|
return eventMsg(event)
|
||||||
case <-m.done:
|
case <-done:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -133,7 +136,7 @@ func tick() tea.Cmd {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update handles messages
|
// Update handles messages
|
||||||
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
var cmds []tea.Cmd
|
var cmds []tea.Cmd
|
||||||
|
|
||||||
switch msg := msg.(type) {
|
switch msg := msg.(type) {
|
||||||
@@ -166,10 +169,10 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// handleKey processes key presses
|
// handleKey processes key presses
|
||||||
func (m Model) handleKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
func (m *Model) handleKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||||
switch {
|
switch {
|
||||||
case key.Matches(msg, m.keys.Quit):
|
case key.Matches(msg, m.keys.Quit):
|
||||||
close(m.done)
|
m.closeOnce.Do(func() { close(m.done) })
|
||||||
return m, tea.Quit
|
return m, tea.Quit
|
||||||
|
|
||||||
case key.Matches(msg, m.keys.Help):
|
case key.Matches(msg, m.keys.Help):
|
||||||
@@ -293,6 +296,6 @@ func (m *Model) SetEventChannel(ch <-chan Event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// View renders the TUI
|
// View renders the TUI
|
||||||
func (m Model) View() string {
|
func (m *Model) View() string {
|
||||||
return m.render()
|
return m.render()
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-13
@@ -10,7 +10,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// render produces the full TUI output
|
// render produces the full TUI output
|
||||||
func (m Model) render() string {
|
func (m *Model) render() string {
|
||||||
if m.width == 0 || m.height == 0 {
|
if m.width == 0 || m.height == 0 {
|
||||||
return "Loading..."
|
return "Loading..."
|
||||||
}
|
}
|
||||||
@@ -40,7 +40,7 @@ func (m Model) render() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// renderHeader renders the top header bar
|
// renderHeader renders the top header bar
|
||||||
func (m Model) renderHeader() string {
|
func (m *Model) renderHeader() string {
|
||||||
title := TitleStyle.Render("GT Feed")
|
title := TitleStyle.Render("GT Feed")
|
||||||
|
|
||||||
filter := ""
|
filter := ""
|
||||||
@@ -60,7 +60,7 @@ func (m Model) renderHeader() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// renderTreePanel renders the agent tree panel with border
|
// renderTreePanel renders the agent tree panel with border
|
||||||
func (m Model) renderTreePanel() string {
|
func (m *Model) renderTreePanel() string {
|
||||||
style := TreePanelStyle
|
style := TreePanelStyle
|
||||||
if m.focusedPanel == PanelTree {
|
if m.focusedPanel == PanelTree {
|
||||||
style = FocusedBorderStyle
|
style = FocusedBorderStyle
|
||||||
@@ -69,7 +69,7 @@ func (m Model) renderTreePanel() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// renderFeedPanel renders the event feed panel with border
|
// renderFeedPanel renders the event feed panel with border
|
||||||
func (m Model) renderFeedPanel() string {
|
func (m *Model) renderFeedPanel() string {
|
||||||
style := StreamPanelStyle
|
style := StreamPanelStyle
|
||||||
if m.focusedPanel == PanelFeed {
|
if m.focusedPanel == PanelFeed {
|
||||||
style = FocusedBorderStyle
|
style = FocusedBorderStyle
|
||||||
@@ -78,7 +78,7 @@ func (m Model) renderFeedPanel() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// renderTree renders the agent tree content
|
// renderTree renders the agent tree content
|
||||||
func (m Model) renderTree() string {
|
func (m *Model) renderTree() string {
|
||||||
if len(m.rigs) == 0 {
|
if len(m.rigs) == 0 {
|
||||||
return AgentIdleStyle.Render("No agents active")
|
return AgentIdleStyle.Render("No agents active")
|
||||||
}
|
}
|
||||||
@@ -131,7 +131,7 @@ func (m Model) renderTree() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// groupAgentsByRole groups agents by their role
|
// groupAgentsByRole groups agents by their role
|
||||||
func (m Model) groupAgentsByRole(agents map[string]*Agent) map[string][]*Agent {
|
func (m *Model) groupAgentsByRole(agents map[string]*Agent) map[string][]*Agent {
|
||||||
result := make(map[string][]*Agent)
|
result := make(map[string][]*Agent)
|
||||||
for _, agent := range agents {
|
for _, agent := range agents {
|
||||||
role := agent.Role
|
role := agent.Role
|
||||||
@@ -152,7 +152,7 @@ func (m Model) groupAgentsByRole(agents map[string]*Agent) map[string][]*Agent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// renderAgentGroup renders a group of agents (crew or polecats)
|
// renderAgentGroup renders a group of agents (crew or polecats)
|
||||||
func (m Model) renderAgentGroup(icon, role string, agents []*Agent) string {
|
func (m *Model) renderAgentGroup(icon, role string, agents []*Agent) string {
|
||||||
var lines []string
|
var lines []string
|
||||||
|
|
||||||
// Group header
|
// Group header
|
||||||
@@ -172,10 +172,12 @@ func (m Model) renderAgentGroup(icon, role string, agents []*Agent) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// renderAgent renders a single agent line
|
// renderAgent renders a single agent line
|
||||||
func (m Model) renderAgent(icon string, agent *Agent, indent int) string {
|
func (m *Model) renderAgent(icon string, agent *Agent, indent int) string {
|
||||||
prefix := strings.Repeat(" ", indent)
|
prefix := strings.Repeat(" ", indent)
|
||||||
if icon != "" {
|
if icon != "" && indent >= 2 {
|
||||||
prefix = strings.Repeat(" ", indent-2) + icon + " "
|
prefix = strings.Repeat(" ", indent-2) + icon + " "
|
||||||
|
} else if icon != "" {
|
||||||
|
prefix = icon + " "
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name with status indicator
|
// Name with status indicator
|
||||||
@@ -208,7 +210,7 @@ func (m Model) renderAgent(icon string, agent *Agent, indent int) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// renderFeed renders the event feed content
|
// renderFeed renders the event feed content
|
||||||
func (m Model) renderFeed() string {
|
func (m *Model) renderFeed() string {
|
||||||
if len(m.events) == 0 {
|
if len(m.events) == 0 {
|
||||||
return AgentIdleStyle.Render("No events yet")
|
return AgentIdleStyle.Render("No events yet")
|
||||||
}
|
}
|
||||||
@@ -230,7 +232,7 @@ func (m Model) renderFeed() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// renderEvent renders a single event line
|
// renderEvent renders a single event line
|
||||||
func (m Model) renderEvent(e Event) string {
|
func (m *Model) renderEvent(e Event) string {
|
||||||
// Timestamp
|
// Timestamp
|
||||||
ts := TimestampStyle.Render(fmt.Sprintf("[%s]", e.Time.Format("15:04:05")))
|
ts := TimestampStyle.Render(fmt.Sprintf("[%s]", e.Time.Format("15:04:05")))
|
||||||
|
|
||||||
@@ -282,7 +284,7 @@ func (m Model) renderEvent(e Event) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// renderStatusBar renders the bottom status bar
|
// renderStatusBar renders the bottom status bar
|
||||||
func (m Model) renderStatusBar() string {
|
func (m *Model) renderStatusBar() string {
|
||||||
// Panel indicator
|
// Panel indicator
|
||||||
panelName := "tree"
|
panelName := "tree"
|
||||||
if m.focusedPanel == PanelFeed {
|
if m.focusedPanel == PanelFeed {
|
||||||
@@ -307,7 +309,7 @@ func (m Model) renderStatusBar() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// renderShortHelp renders abbreviated key hints
|
// renderShortHelp renders abbreviated key hints
|
||||||
func (m Model) renderShortHelp() string {
|
func (m *Model) renderShortHelp() string {
|
||||||
hints := []string{
|
hints := []string{
|
||||||
HelpKeyStyle.Render("j/k") + HelpDescStyle.Render(":scroll"),
|
HelpKeyStyle.Render("j/k") + HelpDescStyle.Render(":scroll"),
|
||||||
HelpKeyStyle.Render("tab") + HelpDescStyle.Render(":switch"),
|
HelpKeyStyle.Render("tab") + HelpDescStyle.Render(":switch"),
|
||||||
|
|||||||
Reference in New Issue
Block a user