feat: add improved pi agent with observatory, dashboard, and pledge-now-pay-later

This commit is contained in:
Azreen Jamal
2026-03-01 23:41:24 +08:00
parent ae242436c9
commit f832b913d5
99 changed files with 20949 additions and 74 deletions

View File

@@ -0,0 +1,971 @@
/**
* Agent Dashboard — Unified observability across all agent interfaces
*
* Passively tracks agent activity from team dispatches, subagent spawns,
* and chain pipeline runs. Provides a compact always-visible widget plus
* a full-screen overlay with four switchable views.
*
* Hooks into: dispatch_agent, subagent_create, subagent_continue, run_chain
* tool calls and their completions. Completely passive — never blocks.
*
* Commands:
* /dashboard — toggle full-screen overlay
* /dashboard clear — reset all tracked state
*
* Usage: pi -e extensions/agent-dashboard.ts
*/
import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
import { DynamicBorder } from "@mariozechner/pi-coding-agent";
import { Container, matchesKey, Text, truncateToWidth } from "@mariozechner/pi-tui";
import { applyExtensionDefaults } from "./themeMap.ts";
// ── Data Types ─────────────────────────────────────────────────────────
type AgentInterface = "team" | "subagent" | "chain";
interface TrackedAgent {
id: string;
name: string;
iface: AgentInterface;
status: "running" | "done" | "error";
task: string;
startedAt: number;
endedAt?: number;
elapsed: number;
toolCount: number;
lastText: string;
turnCount: number;
chainStep?: number;
chainName?: string;
teamName?: string;
}
interface AgentRun {
id: string;
name: string;
iface: AgentInterface;
task: string;
status: "done" | "error";
startedAt: number;
endedAt: number;
duration: number;
toolCount: number;
resultPreview: string;
chainStep?: number;
chainName?: string;
teamName?: string;
}
interface DashboardStats {
totalRuns: number;
totalSuccess: number;
totalError: number;
totalDuration: number;
agentRunCounts: Record<string, number>;
ifaceCounts: Record<AgentInterface, number>;
}
// ── Helpers ────────────────────────────────────────────────────────────
function fmtDuration(ms: number): string {
if (ms < 1000) return `${ms}ms`;
const secs = Math.floor(ms / 1000);
if (secs < 60) return `${secs}s`;
const mins = Math.floor(secs / 60);
const remSecs = secs % 60;
if (mins < 60) return `${mins}m ${remSecs}s`;
const hrs = Math.floor(mins / 60);
const remMins = mins % 60;
return `${hrs}h ${remMins}m`;
}
function shortId(): string {
return Math.random().toString(36).slice(2, 6);
}
function truncate(s: string, max: number): string {
return s.length > max ? s.slice(0, max - 1) + "…" : s;
}
function emptyStats(): DashboardStats {
return {
totalRuns: 0,
totalSuccess: 0,
totalError: 0,
totalDuration: 0,
agentRunCounts: {},
ifaceCounts: { team: 0, subagent: 0, chain: 0 },
};
}
// ── Extension ──────────────────────────────────────────────────────────
export default function (pi: ExtensionAPI) {
// ── State ──────────────────────────────────────────────────────────
const activeAgents: Map<string, TrackedAgent> = new Map();
let history: AgentRun[] = [];
let stats: DashboardStats = emptyStats();
let widgetCtx: ExtensionContext | null = null;
let tickTimer: ReturnType<typeof setInterval> | null = null;
// Mapping from toolCallId → tracked agent info (with timestamp for staleness)
const pendingCalls: Map<string, { agentId: string; ts: number }> = new Map();
// Staleness threshold: 10 minutes
const STALE_TIMEOUT_MS = 10 * 60 * 1000;
// Inactivity auto-stop: stop tick after 30s with no active agents
let lastActivityTs = Date.now();
// ── Tracked tool names ─────────────────────────────────────────────
const TRACKED_TOOLS = new Set([
"dispatch_agent",
"subagent_create",
"subagent_continue",
"run_chain",
]);
// ── State Management ───────────────────────────────────────────────
function clearState() {
activeAgents.clear();
history = [];
stats = emptyStats();
pendingCalls.clear();
lastActivityTs = Date.now();
}
function addToHistory(agent: TrackedAgent) {
const run: AgentRun = {
id: agent.id,
name: agent.name,
iface: agent.iface,
task: agent.task,
status: agent.status === "error" ? "error" : "done",
startedAt: agent.startedAt,
endedAt: agent.endedAt || Date.now(),
duration: agent.elapsed,
toolCount: agent.toolCount,
resultPreview: truncate(agent.lastText, 200),
chainStep: agent.chainStep,
chainName: agent.chainName,
teamName: agent.teamName,
};
history.push(run);
// Ring buffer capped at 200
if (history.length > 200) {
history = history.slice(-200);
}
// Update stats
stats.totalRuns++;
if (run.status === "done") stats.totalSuccess++;
else stats.totalError++;
stats.totalDuration += run.duration;
stats.agentRunCounts[run.name] = (stats.agentRunCounts[run.name] || 0) + 1;
stats.ifaceCounts[run.iface] = (stats.ifaceCounts[run.iface] || 0) + 1;
}
// ── Tick Timer ─────────────────────────────────────────────────────
function startTick() {
if (tickTimer) return;
tickTimer = setInterval(() => {
const now = Date.now();
// Update elapsed on running agents
for (const agent of activeAgents.values()) {
if (agent.status === "running") {
agent.elapsed = now - agent.startedAt;
}
}
// Staleness check: expire pending calls older than 10 minutes
for (const [callId, pending] of pendingCalls) {
if (now - pending.ts > STALE_TIMEOUT_MS) {
pendingCalls.delete(callId);
const agent = activeAgents.get(pending.agentId);
if (agent && agent.status === "running") {
agent.status = "error";
agent.endedAt = now;
agent.elapsed = now - agent.startedAt;
agent.lastText = "Timed out (no completion after 10m)";
addToHistory(agent);
activeAgents.delete(pending.agentId);
}
}
}
// Auto-stop tick after 30s of inactivity (no active agents, no pending calls)
if (activeAgents.size === 0 && pendingCalls.size === 0) {
if (now - lastActivityTs > 30_000) {
stopTick();
return;
}
} else {
lastActivityTs = now;
}
updateWidget();
}, 1000);
}
function stopTick() {
if (tickTimer) {
clearInterval(tickTimer);
tickTimer = null;
}
}
// ── Widget Rendering ───────────────────────────────────────────────
function updateWidget() {
if (!widgetCtx) return;
try {
widgetCtx.ui.setWidget("agent-dashboard", (_tui, theme) => {
const container = new Container();
const borderFn = (s: string) => theme.fg("accent", s);
container.addChild(new DynamicBorder(borderFn));
const headerText = new Text("", 1, 0);
container.addChild(headerText);
const agentLines: Text[] = [];
// Pre-allocate up to 4 lines for active agents
for (let i = 0; i < 4; i++) {
const t = new Text("", 1, 0);
agentLines.push(t);
container.addChild(t);
}
const hintText = new Text("", 1, 0);
container.addChild(hintText);
container.addChild(new DynamicBorder(borderFn));
return {
render(width: number): string[] {
const activeCount = activeAgents.size;
const doneCount = stats.totalSuccess;
const errorCount = stats.totalError;
// Line 1: summary bar
const line1 =
theme.fg("accent", " 📊 Dashboard") +
theme.fg("dim", " │ Active: ") + theme.fg(activeCount > 0 ? "accent" : "muted", `${activeCount}`) +
theme.fg("dim", " │ Done: ") + theme.fg("success", `${doneCount}`) +
theme.fg("dim", " │ Errors: ") + theme.fg(errorCount > 0 ? "error" : "muted", `${errorCount}`);
headerText.setText(truncateToWidth(line1, width - 4));
// Active agent lines
const agents = Array.from(activeAgents.values());
for (let i = 0; i < agentLines.length; i++) {
if (i < agents.length) {
const a = agents[i];
const icon = a.status === "running" ? "⟳"
: a.status === "done" ? "✓" : "✗";
const statusColor = a.status === "running" ? "accent"
: a.status === "done" ? "success" : "error";
const ifaceTag = theme.fg("dim", `[${a.iface}]`);
const elapsed = theme.fg("muted", fmtDuration(a.elapsed));
const tools = theme.fg("dim", `🔧${a.toolCount}`);
const lastText = a.lastText
? theme.fg("muted", truncate(a.lastText, Math.max(20, width - 60)))
: "";
const line =
" " + theme.fg(statusColor, icon) + " " +
theme.fg("accent", truncate(a.name, 16)) + " " +
ifaceTag + " " +
elapsed + " " +
tools +
(lastText ? theme.fg("dim", " │ ") + lastText : "");
agentLines[i].setText(truncateToWidth(line, width - 4));
} else {
agentLines[i].setText("");
}
}
// Hint line
const hintLine =
theme.fg("dim", " /dashboard") + theme.fg("muted", " — full view") +
theme.fg("dim", " │ ") +
theme.fg("muted", `${stats.totalRuns} total runs`) +
(stats.totalDuration > 0
? theme.fg("dim", " │ avg ") + theme.fg("muted", fmtDuration(Math.round(stats.totalDuration / Math.max(1, stats.totalRuns))))
: "");
hintText.setText(truncateToWidth(hintLine, width - 4));
return container.render(width);
},
invalidate() {
container.invalidate();
},
};
});
} catch {}
}
// ── Overlay ────────────────────────────────────────────────────────
async function openOverlay(ctx: ExtensionContext) {
if (!ctx.hasUI) return;
let currentView = 0; // 0=Live, 1=History, 2=Interfaces, 3=Stats
let scrollOffset = 0;
const viewNames = ["1:Live", "2:History", "3:Interfaces", "4:Stats"];
await ctx.ui.custom((_tui, theme, _kb, done) => {
return {
render(width: number): string[] {
const lines: string[] = [];
// ── Header ──
lines.push("");
const tabs = viewNames.map((name, i) =>
i === currentView
? theme.fg("accent", theme.bold(`[${name}]`))
: theme.fg("dim", `[${name}]`)
).join(" ");
lines.push(truncateToWidth(
" " + theme.fg("accent", theme.bold("📊 Agent Dashboard")) +
" ".repeat(Math.max(1, width - 20 - viewNames.join(" ").length - 2)) +
tabs,
width,
));
lines.push(theme.fg("dim", "─".repeat(width)));
// ── View content ──
const contentLines = renderView(currentView, width, theme, scrollOffset);
lines.push(...contentLines);
// ── Footer controls ──
lines.push("");
lines.push(theme.fg("dim", "─".repeat(width)));
lines.push(truncateToWidth(
" " + theme.fg("dim", "1-4/Tab: views │ j/k: scroll │ c: clear │ q/Esc: close"),
width,
));
lines.push("");
return lines;
},
handleInput(data: string) {
if (matchesKey(data, "escape") || data === "q") {
done(undefined);
return;
}
if (data === "1") { currentView = 0; scrollOffset = 0; }
else if (data === "2") { currentView = 1; scrollOffset = 0; }
else if (data === "3") { currentView = 2; scrollOffset = 0; }
else if (data === "4") { currentView = 3; scrollOffset = 0; }
else if (data === "\t") { currentView = (currentView + 1) % 4; scrollOffset = 0; }
else if (matchesKey(data, "up") || data === "k") { scrollOffset = Math.max(0, scrollOffset - 1); }
else if (matchesKey(data, "down") || data === "j") { scrollOffset++; }
else if (matchesKey(data, "pageUp")) { scrollOffset = Math.max(0, scrollOffset - 20); }
else if (matchesKey(data, "pageDown")) { scrollOffset += 20; }
else if (data === "c") {
clearState();
scrollOffset = 0;
}
_tui.requestRender();
},
invalidate() {},
};
}, {
overlay: true,
overlayOptions: { width: "90%", anchor: "center" },
});
}
// ── View Renderers ─────────────────────────────────────────────────
function renderView(view: number, width: number, theme: any, offset: number): string[] {
switch (view) {
case 0: return renderLiveView(width, theme, offset);
case 1: return renderHistoryView(width, theme, offset);
case 2: return renderInterfacesView(width, theme, offset);
case 3: return renderStatsView(width, theme, offset);
default: return [];
}
}
// ── View 1: Live ───────────────────────────────────────────────────
function renderLiveView(width: number, theme: any, offset: number): string[] {
const lines: string[] = [];
const agents = Array.from(activeAgents.values());
lines.push(truncateToWidth(
" " + theme.fg("accent", theme.bold("Active Agents")) +
theme.fg("dim", ` (${agents.length} running)`),
width,
));
lines.push("");
if (agents.length === 0) {
lines.push(truncateToWidth(
" " + theme.fg("dim", "No agents currently running. Activity will appear here when"),
width,
));
lines.push(truncateToWidth(
" " + theme.fg("dim", "dispatch_agent, subagent_create, subagent_continue, or run_chain is called."),
width,
));
lines.push("");
// Show recent completions as context
if (history.length > 0) {
lines.push(truncateToWidth(
" " + theme.fg("muted", `Last completed: ${history.length} agents`),
width,
));
const recent = history.slice(-3).reverse();
for (const run of recent) {
const icon = run.status === "done" ? "✓" : "✗";
const color = run.status === "done" ? "success" : "error";
lines.push(truncateToWidth(
" " + theme.fg(color, `${icon} ${run.name}`) +
theme.fg("dim", ` [${run.iface}] `) +
theme.fg("muted", fmtDuration(run.duration)) +
theme.fg("dim", " — ") +
theme.fg("muted", truncate(run.task, 50)),
width,
));
}
}
return lines;
}
const allLines: string[] = [];
for (const agent of agents) {
const icon = agent.status === "running" ? "●"
: agent.status === "done" ? "✓" : "✗";
const statusColor = agent.status === "running" ? "accent"
: agent.status === "done" ? "success" : "error";
// Card top
allLines.push(truncateToWidth(
" " + theme.fg("dim", "┌─ ") +
theme.fg(statusColor, `${icon} ${agent.name}`) +
theme.fg("dim", ` [${agent.iface}]`) +
(agent.chainName ? theme.fg("dim", ` chain:${agent.chainName}`) : "") +
(agent.teamName ? theme.fg("dim", ` team:${agent.teamName}`) : "") +
(agent.chainStep !== undefined ? theme.fg("dim", ` step:${agent.chainStep}`) : "") +
theme.fg("dim", " ─".repeat(Math.max(0, Math.floor((width - 50) / 2)))),
width,
));
// Task
allLines.push(truncateToWidth(
" " + theme.fg("dim", "│ ") +
theme.fg("muted", "Task: ") +
theme.fg("accent", truncate(agent.task, width - 20)),
width,
));
// Metrics
allLines.push(truncateToWidth(
" " + theme.fg("dim", "│ ") +
theme.fg("muted", "Elapsed: ") + theme.fg("success", fmtDuration(agent.elapsed)) +
theme.fg("dim", " │ ") +
theme.fg("muted", "Tools: ") + theme.fg("accent", `${agent.toolCount}`) +
theme.fg("dim", " │ ") +
theme.fg("muted", "Turns: ") + theme.fg("accent", `${agent.turnCount}`),
width,
));
// Streaming text
if (agent.lastText) {
allLines.push(truncateToWidth(
" " + theme.fg("dim", "│ ") +
theme.fg("muted", truncate(agent.lastText, width - 10)),
width,
));
}
// Card bottom
allLines.push(truncateToWidth(
" " + theme.fg("dim", "└" + "─".repeat(Math.max(0, width - 5))),
width,
));
allLines.push("");
}
const visible = allLines.slice(offset);
lines.push(...visible);
return lines;
}
// ── View 2: History ────────────────────────────────────────────────
function renderHistoryView(width: number, theme: any, offset: number): string[] {
const lines: string[] = [];
lines.push(truncateToWidth(
" " + theme.fg("accent", theme.bold("Completed Runs")) +
theme.fg("dim", ` (${history.length} total)`),
width,
));
lines.push("");
if (history.length === 0) {
lines.push(truncateToWidth(" " + theme.fg("dim", "No completed runs yet."), width));
return lines;
}
// Table header
const hdr =
theme.fg("accent", " Status") +
theme.fg("accent", " │ Name ") +
theme.fg("accent", " │ Interface ") +
theme.fg("accent", " │ Duration ") +
theme.fg("accent", " │ Tools ") +
theme.fg("accent", " │ Task");
lines.push(truncateToWidth(hdr, width));
lines.push(truncateToWidth(" " + theme.fg("dim", "─".repeat(Math.min(80, width - 4))), width));
// Show newest first
const rows: string[] = [];
const reversed = [...history].reverse();
for (const run of reversed) {
const icon = run.status === "done" ? "✓" : "✗";
const color = run.status === "done" ? "success" : "error";
const ifaceLabel = run.iface.padEnd(9);
const nameLabel = truncate(run.name, 14).padEnd(14);
const durLabel = fmtDuration(run.duration).padEnd(8);
const toolLabel = String(run.toolCount).padStart(5);
const taskPreview = truncate(run.task, Math.max(10, width - 70));
const row =
" " + theme.fg(color, ` ${icon} `) +
theme.fg("dim", " │ ") + theme.fg("accent", nameLabel) +
theme.fg("dim", " │ ") + theme.fg("muted", ifaceLabel) +
theme.fg("dim", " │ ") + theme.fg("success", durLabel) +
theme.fg("dim", " │ ") + theme.fg("accent", toolLabel) +
theme.fg("dim", " │ ") + theme.fg("muted", taskPreview);
rows.push(row);
}
const visible = rows.slice(offset);
for (const row of visible) {
lines.push(truncateToWidth(row, width));
}
return lines;
}
// ── View 3: Interfaces ─────────────────────────────────────────────
function renderInterfacesView(width: number, theme: any, offset: number): string[] {
const lines: string[] = [];
lines.push(truncateToWidth(
" " + theme.fg("accent", theme.bold("Agents by Interface")),
width,
));
lines.push("");
const ifaceLabels: Record<AgentInterface, string> = {
team: "🏢 Team (dispatch_agent)",
subagent: "🤖 Subagent (subagent_create/continue)",
chain: "🔗 Chain (run_chain)",
};
const allLines: string[] = [];
for (const iface of ["team", "subagent", "chain"] as AgentInterface[]) {
const activeForIface = Array.from(activeAgents.values()).filter(a => a.iface === iface);
const historyForIface = history.filter(r => r.iface === iface);
const totalCount = stats.ifaceCounts[iface] || 0;
allLines.push(truncateToWidth(
" " + theme.fg("accent", theme.bold(ifaceLabels[iface])) +
theme.fg("dim", `${activeForIface.length} active, ${totalCount} completed`),
width,
));
allLines.push(truncateToWidth(" " + theme.fg("dim", "─".repeat(Math.min(60, width - 6))), width));
// Active
if (activeForIface.length > 0) {
for (const agent of activeForIface) {
allLines.push(truncateToWidth(
" " + theme.fg("accent", "● ") +
theme.fg("accent", agent.name) +
theme.fg("dim", " — ") +
theme.fg("success", fmtDuration(agent.elapsed)) +
theme.fg("dim", " │ 🔧") + theme.fg("muted", `${agent.toolCount}`) +
theme.fg("dim", " │ ") +
theme.fg("muted", truncate(agent.task, 40)),
width,
));
}
}
// Recent completed (last 5)
const recent = historyForIface.slice(-5).reverse();
if (recent.length > 0) {
for (const run of recent) {
const icon = run.status === "done" ? "✓" : "✗";
const color = run.status === "done" ? "success" : "error";
allLines.push(truncateToWidth(
" " + theme.fg(color, `${icon} `) +
theme.fg("muted", run.name) +
theme.fg("dim", " — ") +
theme.fg("muted", fmtDuration(run.duration)) +
theme.fg("dim", " │ ") +
theme.fg("muted", truncate(run.task, 40)),
width,
));
}
}
if (activeForIface.length === 0 && recent.length === 0) {
allLines.push(truncateToWidth(" " + theme.fg("dim", "No activity recorded."), width));
}
allLines.push("");
}
const visible = allLines.slice(offset);
lines.push(...visible);
return lines;
}
// ── View 4: Stats ──────────────────────────────────────────────────
function renderStatsView(width: number, theme: any, offset: number): string[] {
const lines: string[] = [];
lines.push(truncateToWidth(
" " + theme.fg("accent", theme.bold("Aggregate Statistics")),
width,
));
lines.push("");
const avgDur = stats.totalRuns > 0
? fmtDuration(Math.round(stats.totalDuration / stats.totalRuns))
: "—";
const successRate = stats.totalRuns > 0
? `${Math.round((stats.totalSuccess / stats.totalRuns) * 100)}%`
: "—";
const allLines: string[] = [];
// Summary cards
allLines.push(truncateToWidth(
" " +
theme.fg("muted", "Total Runs: ") + theme.fg("accent", `${stats.totalRuns}`) +
theme.fg("dim", " │ ") +
theme.fg("muted", "Success: ") + theme.fg("success", `${stats.totalSuccess}`) +
theme.fg("dim", " │ ") +
theme.fg("muted", "Errors: ") + theme.fg(stats.totalError > 0 ? "error" : "muted", `${stats.totalError}`) +
theme.fg("dim", " │ ") +
theme.fg("muted", "Success Rate: ") + theme.fg("success", successRate),
width,
));
allLines.push(truncateToWidth(
" " +
theme.fg("muted", "Total Duration: ") + theme.fg("success", fmtDuration(stats.totalDuration)) +
theme.fg("dim", " │ ") +
theme.fg("muted", "Avg Duration: ") + theme.fg("accent", avgDur),
width,
));
allLines.push("");
// Interface breakdown
allLines.push(truncateToWidth(
" " + theme.fg("accent", theme.bold("Interface Breakdown")),
width,
));
allLines.push("");
const ifaceTotal = Math.max(1, stats.ifaceCounts.team + stats.ifaceCounts.subagent + stats.ifaceCounts.chain);
const barWidth = Math.min(30, Math.floor(width * 0.3));
for (const [iface, label] of [["team", "Team "], ["subagent", "Subagent "], ["chain", "Chain "]] as [AgentInterface, string][]) {
const count = stats.ifaceCounts[iface] || 0;
const ratio = count / ifaceTotal;
const filled = Math.round(ratio * barWidth);
const bar = "█".repeat(filled) + "░".repeat(barWidth - filled);
allLines.push(truncateToWidth(
" " +
theme.fg("accent", label) + " " +
theme.fg("success", bar) + " " +
theme.fg("muted", `${count}`) +
theme.fg("dim", ` (${Math.round(ratio * 100)}%)`),
width,
));
}
allLines.push("");
// Most-used agents bar chart
allLines.push(truncateToWidth(
" " + theme.fg("accent", theme.bold("Most-Used Agents")),
width,
));
allLines.push("");
const agentEntries = Object.entries(stats.agentRunCounts).sort((a, b) => b[1] - a[1]);
if (agentEntries.length === 0) {
allLines.push(truncateToWidth(" " + theme.fg("dim", "No agent runs recorded yet."), width));
} else {
const maxCount = agentEntries[0][1];
for (const [name, count] of agentEntries.slice(0, 15)) {
const ratio = maxCount > 0 ? count / maxCount : 0;
const filled = Math.round(ratio * barWidth);
const bar = "█".repeat(filled) + "░".repeat(barWidth - filled);
allLines.push(truncateToWidth(
" " +
theme.fg("accent", name.padEnd(16)) + " " +
theme.fg("success", bar) + " " +
theme.fg("muted", `${count}`),
width,
));
}
}
allLines.push("");
// Per-agent average durations
if (history.length > 0) {
allLines.push(truncateToWidth(
" " + theme.fg("accent", theme.bold("Average Duration by Agent")),
width,
));
allLines.push("");
const durByAgent: Record<string, number[]> = {};
for (const run of history) {
if (!durByAgent[run.name]) durByAgent[run.name] = [];
durByAgent[run.name].push(run.duration);
}
const durEntries = Object.entries(durByAgent).sort((a, b) => {
const avgA = a[1].reduce((s, v) => s + v, 0) / a[1].length;
const avgB = b[1].reduce((s, v) => s + v, 0) / b[1].length;
return avgB - avgA;
});
for (const [name, durations] of durEntries) {
const avg = durations.reduce((s, v) => s + v, 0) / durations.length;
const min = Math.min(...durations);
const max = Math.max(...durations);
allLines.push(truncateToWidth(
" " +
theme.fg("accent", name.padEnd(16)) +
theme.fg("dim", " avg: ") + theme.fg("success", fmtDuration(Math.round(avg)).padEnd(8)) +
theme.fg("dim", " min: ") + theme.fg("muted", fmtDuration(min).padEnd(8)) +
theme.fg("dim", " max: ") + theme.fg("muted", fmtDuration(max).padEnd(8)) +
theme.fg("dim", " runs: ") + theme.fg("muted", `${durations.length}`),
width,
));
}
}
const visible = allLines.slice(offset);
lines.push(...visible);
return lines;
}
// ── Commands ───────────────────────────────────────────────────────
pi.registerCommand("dashboard", {
description: "Open Agent Dashboard overlay. Args: clear",
handler: async (args, ctx) => {
widgetCtx = ctx;
const arg = (args || "").trim().toLowerCase();
if (arg === "clear") {
stopTick();
clearState();
startTick();
ctx.ui.notify("📊 Dashboard: All data cleared.", "info");
updateWidget();
return;
}
await openOverlay(ctx);
},
});
// ── Event Handlers ─────────────────────────────────────────────────
pi.on("session_start", async (_event, ctx) => {
applyExtensionDefaults(import.meta.url, ctx);
stopTick();
widgetCtx = ctx;
clearState();
startTick();
updateWidget();
});
pi.on("before_agent_start", async (_event, ctx) => {
widgetCtx = ctx;
return undefined;
});
pi.on("agent_end", async (_event, ctx) => {
widgetCtx = ctx;
updateWidget();
});
pi.on("tool_call", async (event, _ctx) => {
try {
const toolName = event.toolName;
if (!TRACKED_TOOLS.has(toolName)) return undefined;
const input = event.input;
const now = Date.now();
const callId = event.toolCallId;
lastActivityTs = now;
if (toolName === "dispatch_agent") {
const agentName = (input.agent as string) || "unknown";
const task = (input.task as string) || "";
const id = `team:${agentName}:${shortId()}`;
const tracked: TrackedAgent = {
id,
name: agentName,
iface: "team",
status: "running",
task,
startedAt: now,
elapsed: 0,
toolCount: 0,
lastText: "",
turnCount: 1,
teamName: agentName,
};
activeAgents.set(id, tracked);
pendingCalls.set(callId, { agentId: id, ts: now });
} else if (toolName === "subagent_create") {
const task = (input.task as string) || "";
const id = `sub:create:${shortId()}`;
const tracked: TrackedAgent = {
id,
name: "Subagent",
iface: "subagent",
status: "running",
task,
startedAt: now,
elapsed: 0,
toolCount: 0,
lastText: "",
turnCount: 1,
};
activeAgents.set(id, tracked);
pendingCalls.set(callId, { agentId: id, ts: now });
} else if (toolName === "subagent_continue") {
// Always create a new tracking entry using the widget's ID from input
const subId = input.id;
const prompt = (input.prompt as string) || "";
const id = `sub:cont:${subId}:${shortId()}`;
const tracked: TrackedAgent = {
id,
name: `Subagent #${subId}`,
iface: "subagent",
status: "running",
task: prompt,
startedAt: now,
elapsed: 0,
toolCount: 0,
lastText: "",
turnCount: 1,
};
activeAgents.set(id, tracked);
pendingCalls.set(callId, { agentId: id, ts: now });
} else if (toolName === "run_chain") {
const task = (input.task as string) || "";
const id = `chain:${shortId()}`;
const tracked: TrackedAgent = {
id,
name: "chain",
iface: "chain",
status: "running",
task,
startedAt: now,
elapsed: 0,
toolCount: 0,
lastText: "",
turnCount: 1,
chainName: "pipeline",
};
activeAgents.set(id, tracked);
pendingCalls.set(callId, { agentId: id, ts: now });
}
// Ensure tick is running when we have active agents
startTick();
updateWidget();
} catch {}
return undefined;
});
pi.on("tool_execution_end", async (event) => {
try {
const toolName = event.toolName;
if (!TRACKED_TOOLS.has(toolName)) return;
const now = Date.now();
const callId = event.toolCallId;
lastActivityTs = now;
const pending = pendingCalls.get(callId);
if (pending) {
pendingCalls.delete(callId);
const agent = activeAgents.get(pending.agentId);
if (agent) {
agent.status = event.isError ? "error" : "done";
agent.endedAt = now;
agent.elapsed = now - agent.startedAt;
// Extract result preview if available
try {
const result = event.result;
if (result?.content) {
for (const block of result.content) {
if (block.type === "text" && block.text) {
agent.lastText = block.text.slice(0, 200);
break;
}
}
}
} catch {}
// Move to history
addToHistory(agent);
activeAgents.delete(pending.agentId);
}
}
updateWidget();
} catch {}
});
}