Skip to content

Comments

feat: add TUI dashboard extension for at-a-glance system status#163

Open
baudbot-agent wants to merge 2 commits intomainfrom
feat/tui-dashboard
Open

feat: add TUI dashboard extension for at-a-glance system status#163
baudbot-agent wants to merge 2 commits intomainfrom
feat/tui-dashboard

Conversation

@baudbot-agent
Copy link
Collaborator

What

Adds a new pi extension (pi/extensions/dashboard.ts) that renders a persistent status widget above the editor. When an admin attaches to the running baudbot tmux session, they can see system health at a glance without querying the agent.

Dashboard layout

──────────────────────────────────── baudbot ────────────────────────────────────
  baudbot v0.1.0@e004988  │  pi v0.52.12*  │  ● bridge up broker        up 4h 23m
  ● control-agent  ● sentry-agent
  todos 0 active / 31 done / 33 total  │  ○ 0 worktrees              ⟳ 08:45:12
  heartbeat ♥ 3m ago  │  [slack] <@U0123ABC> 12m ago
────────────────────────────────────────────────────────────────────────────────

What's shown

Indicator Details
baudbot version package.json version + deployed git SHA from /opt/baudbot/current
pi version Running version, yellow with * if behind latest npm
bridge Green/red dot, up/DOWN, broker vs socket mode
sessions Green/red dots for control-agent & sentry-agent, dev-agent count
todos Active (in-progress) / done / total
worktrees Count of active git worktrees
heartbeat ♥ with last check time + healthy/unhealthy color, or paused
last event Source tag (slack/chat/sentry/heartbeat/rpc) + summary + time ago
uptime Session uptime (live-updated on every render)

Design

  • Refreshes every 30 seconds with zero LLM token cost — all data collected via filesystem reads and HTTP probes
  • /dashboard command for immediate manual refresh
  • Pi version read from package.json via process.execPath (no subprocess)
  • Bridge probed via HTTP POST to localhost:7890/send (expects 400)
  • Bridge type detected from running process (ps)
  • Heartbeat state read from session entries (cross-extension)
  • Last event tracked via message_start listener

Baudbot added 2 commits February 24, 2026 08:42
Renders a persistent widget above the editor showing:
- Pi version (with update indicator if behind latest npm)
- Slack bridge status (live HTTP probe)
- Session health (control-agent, sentry-agent, dev-agents)
- Todo stats (active/done/total)
- Worktree count
- Current model and uptime

Refreshes every 30s with zero LLM token cost. Admin can attach
to the running baudbot tmux session and see health without
sending any messages.

Also adds /dashboard command for immediate refresh.
message_start only fires for user/assistant/toolResult messages, not
custom messages from pi.sendMessage(). Slack messages arrive as
session-message custom type and were being missed.

before_agent_start fires for ALL inbound messages that trigger an
agent turn, including custom messages from the bridge/heartbeat.

Also improved the event summary to show the actual message body
excerpt alongside the sender.
@greptile-apps
Copy link

greptile-apps bot commented Feb 24, 2026

Greptile Summary

Added a new TUI dashboard extension (pi/extensions/dashboard.ts) that displays real-time system status above the editor, enabling admins to monitor baudbot health at a glance without consuming LLM tokens.

The dashboard shows:

  • Baudbot and pi versions with update indicators
  • Bridge status (up/down, broker/socket mode) via HTTP probe
  • Session health (control-agent, sentry-agent, dev-agents)
  • Todo statistics (active/done/total counts)
  • Active worktree count
  • Heartbeat status from session state
  • Last event tracking from inbound messages
  • Session uptime

Implementation details:

  • Refreshes every 30 seconds using filesystem reads and HTTP probes
  • /dashboard command for manual refresh
  • Bridge detection via ps command parsing
  • Event tracking via message_start listener
  • Zero-token monitoring approach consistent with the heartbeat extension

Minor style suggestions:

  • Shell command in detectBridgeType could be simplified to avoid nested grep
  • Error handling in the refresh interval could be more verbose for debugging

Confidence Score: 4/5

  • This PR is safe to merge with minor style improvements recommended
  • The implementation follows established patterns from the heartbeat extension, uses defensive error handling throughout, and adds monitoring functionality without modifying security-critical code. The shell command in detectBridgeType has low risk but could be simplified. No tests are included, but this aligns with the pattern of other extensions (heartbeat and tool-guard are the only tested extensions).
  • No files require special attention - the single new file follows project conventions

Important Files Changed

Filename Overview
pi/extensions/dashboard.ts New TUI dashboard extension with system status monitoring. Minor issue with shell injection risk in detectBridgeType function.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    Start[Extension Load] --> Init[Initialize State]
    Init --> SessionStart[session_start Event]
    
    SessionStart --> SaveCtx[Save Context]
    SaveCtx --> InitRefresh[Initial Refresh]
    InitRefresh --> InstallWidget[Install Dashboard Widget]
    InstallWidget --> StartTimer[Start 30s Timer]
    
    StartTimer --> Timer{Every 30s}
    Timer --> Refresh[refresh Function]
    
    Refresh --> ParallelChecks{Parallel Checks}
    ParallelChecks --> CheckBridge[HTTP POST to Bridge]
    ParallelChecks --> CheckVersion[Fetch npm Registry]
    
    ParallelChecks --> SyncChecks[Synchronous Checks]
    SyncChecks --> GetSessions[Read Session Sockets]
    SyncChecks --> GetDevAgents[Count dev-agents]
    SyncChecks --> GetTodos[Parse Todo Files]
    SyncChecks --> GetWorktrees[Count Worktrees]
    SyncChecks --> DetectBridge[execSync ps command]
    SyncChecks --> GetBaudbot[Read Symlink & package.json]
    SyncChecks --> ReadHeartbeat[Read Session Entries]
    
    CheckBridge --> UpdateData[Update DashboardData]
    CheckVersion --> UpdateData
    SyncChecks --> UpdateData
    
    UpdateData --> Timer
    
    MessageStart[message_start Event] --> ParseMessage[Parse Message Content]
    ParseMessage --> ExtractEvent[Extract Event Info]
    ExtractEvent --> UpdateEvent[Update lastEvent]
    UpdateEvent --> UpdateDataImmediate[Update data.lastEvent]
    
    Widget[Widget render Call] --> CheckData{data exists?}
    CheckData -->|No| Loading[Show Loading State]
    CheckData -->|Yes| CalcUptime[Update uptimeMs]
    CalcUptime --> RenderDashboard[renderDashboard Function]
    
    RenderDashboard --> FormatRows[Format 4 Rows + Borders]
    FormatRows --> ApplyTheme[Apply Theme Colors]
    ApplyTheme --> ReturnLines[Return String Array]
    
    Command[Manual Refresh] --> ManualRefresh[Trigger Refresh]
    ManualRefresh --> Notify[Show Notification]
    
    Shutdown[session_shutdown] --> ClearTimer[clearInterval]
Loading

Last reviewed commit: 2182b3f

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 file reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +132 to +133
const out = execSync("ps -eo args 2>/dev/null | grep -E 'broker-bridge|bridge\\.mjs' | grep -v grep", {
encoding: "utf-8", timeout: 3000,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The shell command uses grep -E with a pattern that could be vulnerable if process arguments contain shell metacharacters, though in this specific case the risk is low since you're just reading process output. However, the command itself could fail if the environment contains unexpected characters.

Consider using a safer approach like reading /proc or using a Node.js process listing library:

Suggested change
const out = execSync("ps -eo args 2>/dev/null | grep -E 'broker-bridge|bridge\\.mjs' | grep -v grep", {
encoding: "utf-8", timeout: 3000,
const out = execSync("ps -eo args", {
encoding: "utf-8", timeout: 3000,
}).trim();
if (out.includes("broker-bridge")) return "broker";
if (out.includes("bridge.mjs")) return "socket";

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: pi/extensions/dashboard.ts
Line: 132-133

Comment:
The shell command uses `grep -E` with a pattern that could be vulnerable if process arguments contain shell metacharacters, though in this specific case the risk is low since you're just reading process output. However, the command itself could fail if the environment contains unexpected characters.

Consider using a safer approach like reading `/proc` or using a Node.js process listing library:

```suggestion
    const out = execSync("ps -eo args", {
      encoding: "utf-8", timeout: 3000,
    }).trim();
    if (out.includes("broker-bridge")) return "broker";
    if (out.includes("bridge.mjs")) return "socket";
```

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +490 to +492
try { await refresh(); }
catch {}
}, REFRESH_INTERVAL_MS);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Silently catching errors in the refresh interval could hide issues. Consider logging errors or tracking failure counts to detect persistent problems.

Suggested change
try { await refresh(); }
catch {}
}, REFRESH_INTERVAL_MS);
timer = setInterval(async () => {
try { await refresh(); }
catch (err) { console.error("Dashboard refresh failed:", err); }
}, REFRESH_INTERVAL_MS);

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: pi/extensions/dashboard.ts
Line: 490-492

Comment:
Silently catching errors in the refresh interval could hide issues. Consider logging errors or tracking failure counts to detect persistent problems.

```suggestion
    timer = setInterval(async () => {
      try { await refresh(); }
      catch (err) { console.error("Dashboard refresh failed:", err); }
    }, REFRESH_INTERVAL_MS);
```

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

description: "Refresh the baudbot status dashboard",
handler: async (_args, ctx) => {
await refresh();
ctx.ui.notify("Dashboard refreshed", "info");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The /dashboard command handler calls ctx.ui.notify() without checking ctx.hasUI, which will cause a crash if run in a headless environment without a UI.
Severity: MEDIUM

Suggested Fix

Add a guard clause at the beginning of the handler for the /dashboard command. Check if ctx.hasUI is false, and if so, return early to prevent the call to ctx.ui.notify(). For example: if (!ctx.hasUI) { return; }.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: pi/extensions/dashboard.ts#L479

Potential issue: The `/dashboard` command handler calls `ctx.ui.notify()` without first
verifying if a UI context is available via the `ctx.hasUI` flag. If this command is
invoked in an environment without a UI, such as through an RPC call or an automated
script, `ctx.ui` will be undefined. This will lead to a `TypeError` and cause the
command handler to crash. Other extensions in the codebase correctly check for
`ctx.hasUI` before calling UI functions, establishing a safety pattern that is not
followed here.

Did we get this right? 👍 / 👎 to inform future reviews.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant