feat: add TUI dashboard extension for at-a-glance system status#163
feat: add TUI dashboard extension for at-a-glance system status#163baudbot-agent wants to merge 2 commits intomainfrom
Conversation
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 SummaryAdded a new TUI dashboard extension ( The dashboard shows:
Implementation details:
Minor style suggestions:
Confidence Score: 4/5
Important Files Changed
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]
Last reviewed commit: 2182b3f |
| const out = execSync("ps -eo args 2>/dev/null | grep -E 'broker-bridge|bridge\\.mjs' | grep -v grep", { | ||
| encoding: "utf-8", timeout: 3000, |
There was a problem hiding this 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:
| 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.| try { await refresh(); } | ||
| catch {} | ||
| }, REFRESH_INTERVAL_MS); |
There was a problem hiding this comment.
Silently catching errors in the refresh interval could hide issues. Consider logging errors or tracking failure counts to detect persistent problems.
| 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"); |
There was a problem hiding this comment.
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.
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
What's shown
/opt/baudbot/current*if behind latest npmDesign
/dashboardcommand for immediate manual refreshpackage.jsonviaprocess.execPath(no subprocess)localhost:7890/send(expects 400)ps)message_startlistener