@harbinger-ai/harbinger 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,490 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useState, useEffect, useCallback } from "react";
4
+ import { motion, AnimatePresence } from "framer-motion";
5
+ import { SpinnerIcon, RefreshIcon, ChevronDownIcon, BellIcon } from "./icons.js";
6
+ import { getSwarmStatus, getNotifications } from "../actions.js";
7
+ function formatDuration(seconds) {
8
+ if (!seconds || seconds < 0) return "0s";
9
+ if (seconds < 60) return `${seconds}s`;
10
+ const minutes = Math.floor(seconds / 60);
11
+ const secs = seconds % 60;
12
+ if (minutes < 60) return `${minutes}m ${secs}s`;
13
+ const hours = Math.floor(minutes / 60);
14
+ const mins = minutes % 60;
15
+ return `${hours}h ${mins}m`;
16
+ }
17
+ function timeAgo(timestamp) {
18
+ if (!timestamp) return "";
19
+ const seconds = Math.floor((Date.now() - new Date(timestamp).getTime()) / 1e3);
20
+ if (seconds < 60) return "just now";
21
+ const minutes = Math.floor(seconds / 60);
22
+ if (minutes < 60) return `${minutes}m ago`;
23
+ const hours = Math.floor(minutes / 60);
24
+ if (hours < 24) return `${hours}h ago`;
25
+ return `${Math.floor(hours / 24)}d ago`;
26
+ }
27
+ function getJobId(branch) {
28
+ if (!branch) return "";
29
+ return branch.replace("job/", "").slice(0, 8);
30
+ }
31
+ function AgentRoster({ agents, runs, selectedAgent, onSelect, collapsed, onToggleCollapse }) {
32
+ const agentJobCounts = {};
33
+ for (const run of runs) {
34
+ const branch = run.branch || "";
35
+ for (const agent of agents) {
36
+ const codename = (agent.codename || agent.name || agent.id || "").toLowerCase();
37
+ if (branch.toLowerCase().includes(codename)) {
38
+ agentJobCounts[codename] = (agentJobCounts[codename] || 0) + 1;
39
+ }
40
+ }
41
+ }
42
+ const activeRuns = runs.filter((r) => r.status === "in_progress" || r.status === "queued");
43
+ const agentActivity = {};
44
+ for (const run of activeRuns) {
45
+ const branch = (run.branch || "").toLowerCase();
46
+ for (const agent of agents) {
47
+ const codename = (agent.codename || agent.name || agent.id || "").toLowerCase();
48
+ if (branch.includes(codename)) {
49
+ agentActivity[codename] = run.status === "in_progress" ? "active" : "queued";
50
+ }
51
+ }
52
+ }
53
+ return /* @__PURE__ */ jsxs("div", { className: `shrink-0 border-r border-white/[0.06] transition-all ${collapsed ? "w-14" : "w-60"}`, children: [
54
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between p-3 border-b border-white/[0.06]", children: [
55
+ !collapsed && /* @__PURE__ */ jsx("h2", { className: "text-[10px] font-mono font-medium uppercase tracking-wider text-muted-foreground", children: "Agents" }),
56
+ /* @__PURE__ */ jsx(
57
+ "button",
58
+ {
59
+ onClick: onToggleCollapse,
60
+ className: "p-1 text-muted-foreground hover:text-foreground transition-colors",
61
+ children: /* @__PURE__ */ jsx(ChevronDownIcon, { size: 14, className: `transition-transform ${collapsed ? "-rotate-90" : "rotate-90"}` })
62
+ }
63
+ )
64
+ ] }),
65
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1 p-2 overflow-y-auto max-h-[calc(100vh-16rem)] scrollbar-thin", children: [
66
+ /* @__PURE__ */ jsxs(
67
+ "button",
68
+ {
69
+ onClick: () => onSelect(null),
70
+ className: `flex items-center gap-2 rounded-md px-2 py-1.5 text-left transition-colors ${selectedAgent === null ? "bg-[--cyan]/10 text-[--cyan]" : "hover:bg-white/[0.04] text-muted-foreground hover:text-foreground"}`,
71
+ children: [
72
+ /* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-[--cyan] shrink-0" }),
73
+ !collapsed && /* @__PURE__ */ jsx("span", { className: "text-xs font-mono truncate", children: "All Agents" })
74
+ ]
75
+ }
76
+ ),
77
+ agents.map((agent, i) => {
78
+ const codename = agent.codename || agent.name || agent.id;
79
+ const lcCodename = codename.toLowerCase();
80
+ const activity = agentActivity[lcCodename];
81
+ const isSelected = selectedAgent === agent.id;
82
+ return /* @__PURE__ */ jsxs(
83
+ motion.button,
84
+ {
85
+ initial: { opacity: 0, x: -8 },
86
+ animate: { opacity: 1, x: 0 },
87
+ transition: { duration: 0.2, delay: i * 0.03 },
88
+ onClick: () => onSelect(agent.id),
89
+ className: `flex items-center gap-2 rounded-md px-2 py-1.5 text-left transition-colors ${isSelected ? "bg-[--cyan]/10 text-[--cyan]" : "hover:bg-white/[0.04] text-foreground/80 hover:text-foreground"}`,
90
+ children: [
91
+ /* @__PURE__ */ jsx("div", { className: `w-2 h-2 rounded-full shrink-0 ${activity === "active" ? "bg-green-500 animate-pulse" : activity === "queued" ? "bg-yellow-500" : "bg-muted-foreground/40"}` }),
92
+ !collapsed && /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
93
+ /* @__PURE__ */ jsxs("span", { className: "text-xs font-mono font-medium truncate block", children: [
94
+ "@",
95
+ codename.toUpperCase()
96
+ ] }),
97
+ agent.role && /* @__PURE__ */ jsx("span", { className: "text-[10px] font-mono text-muted-foreground truncate block", children: agent.role })
98
+ ] })
99
+ ]
100
+ },
101
+ agent.id
102
+ );
103
+ }),
104
+ agents.length === 0 && !collapsed && /* @__PURE__ */ jsx("div", { className: "text-center py-4", children: /* @__PURE__ */ jsx("p", { className: "text-[10px] font-mono text-muted-foreground", children: "No agents configured" }) })
105
+ ] })
106
+ ] });
107
+ }
108
+ function MissionCard({ run, agents, index }) {
109
+ const [expanded, setExpanded] = useState(false);
110
+ const isActive = run.status === "in_progress";
111
+ const isQueued = run.status === "queued";
112
+ const isFailed = run.conclusion === "failure";
113
+ const isSuccess = run.conclusion === "success";
114
+ const jobId = getJobId(run.branch);
115
+ const branch = (run.branch || "").toLowerCase();
116
+ const matchedAgent = agents.find((a) => {
117
+ const codename = (a.codename || a.name || a.id || "").toLowerCase();
118
+ return branch.includes(codename);
119
+ });
120
+ return /* @__PURE__ */ jsxs(
121
+ motion.div,
122
+ {
123
+ initial: { opacity: 0, y: 6 },
124
+ animate: { opacity: 1, y: 0 },
125
+ transition: { duration: 0.2, delay: index * 0.03 },
126
+ layout: true,
127
+ className: `rounded-lg border bg-[--card] transition-all ${isActive ? "border-green-500/30 shadow-[0_0_15px_oklch(0.7_0.17_145/10%)]" : isFailed ? "border-red-500/20" : "border-white/[0.06] hover:border-[--cyan]/20"}`,
128
+ children: [
129
+ /* @__PURE__ */ jsxs(
130
+ "button",
131
+ {
132
+ onClick: () => setExpanded(!expanded),
133
+ className: "flex items-center gap-3 w-full text-left p-3 hover:bg-white/[0.02] rounded-lg transition-colors",
134
+ children: [
135
+ /* @__PURE__ */ jsx("div", { className: `shrink-0 w-2.5 h-2.5 rounded-full ${isActive ? "bg-green-500 animate-pulse" : isQueued ? "bg-yellow-500" : isFailed ? "bg-red-500" : isSuccess ? "bg-green-500" : "bg-muted-foreground"}` }),
136
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
137
+ /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsx("span", { className: "text-xs font-mono font-medium text-foreground/90 truncate", children: run.workflow_name || run.branch || "Unknown" }) }),
138
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mt-0.5 flex-wrap", children: [
139
+ jobId && /* @__PURE__ */ jsx("span", { className: "text-[10px] font-mono text-muted-foreground", children: jobId }),
140
+ matchedAgent && /* @__PURE__ */ jsxs("span", { className: "inline-flex rounded-full bg-[--cyan]/10 border border-[--cyan]/20 px-1.5 py-0.5 text-[9px] font-mono text-[--cyan]", children: [
141
+ "@",
142
+ (matchedAgent.codename || matchedAgent.name || matchedAgent.id).toUpperCase()
143
+ ] })
144
+ ] })
145
+ ] }),
146
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] font-mono text-muted-foreground shrink-0", children: isActive || isQueued ? formatDuration(run.duration_seconds) : timeAgo(run.updated_at || run.started_at) }),
147
+ !isActive && !isQueued && /* @__PURE__ */ jsx("span", { className: `inline-flex rounded-full px-2 py-0.5 text-[9px] font-mono font-medium border ${isFailed ? "bg-red-500/10 text-red-500 border-red-500/20" : isSuccess ? "bg-green-500/10 text-green-500 border-green-500/20" : "bg-white/5 text-muted-foreground border-white/10"}`, children: run.conclusion || "unknown" }),
148
+ /* @__PURE__ */ jsx(ChevronDownIcon, { size: 14, className: `shrink-0 text-muted-foreground transition-transform ${expanded ? "rotate-180" : ""}` })
149
+ ]
150
+ }
151
+ ),
152
+ /* @__PURE__ */ jsx(AnimatePresence, { children: expanded && /* @__PURE__ */ jsx(
153
+ motion.div,
154
+ {
155
+ initial: { height: 0, opacity: 0 },
156
+ animate: { height: "auto", opacity: 1 },
157
+ exit: { height: 0, opacity: 0 },
158
+ transition: { duration: 0.2 },
159
+ className: "overflow-hidden",
160
+ children: /* @__PURE__ */ jsxs("div", { className: "border-t border-white/[0.06] px-4 py-3 flex flex-col gap-2", children: [
161
+ run.branch && /* @__PURE__ */ jsxs("div", { className: "flex gap-2 text-xs items-baseline", children: [
162
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground font-mono text-[10px] uppercase tracking-wider w-16 shrink-0", children: "Branch" }),
163
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-foreground/80", children: run.branch })
164
+ ] }),
165
+ run.started_at && /* @__PURE__ */ jsxs("div", { className: "flex gap-2 text-xs items-baseline", children: [
166
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground font-mono text-[10px] uppercase tracking-wider w-16 shrink-0", children: "Started" }),
167
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-foreground/80", children: new Date(run.started_at).toLocaleString() })
168
+ ] }),
169
+ run.duration_seconds > 0 && /* @__PURE__ */ jsxs("div", { className: "flex gap-2 text-xs items-baseline", children: [
170
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground font-mono text-[10px] uppercase tracking-wider w-16 shrink-0", children: "Duration" }),
171
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-foreground/80", children: formatDuration(run.duration_seconds) })
172
+ ] }),
173
+ run.html_url && /* @__PURE__ */ jsx("div", { className: "mt-1", children: /* @__PURE__ */ jsx("a", { href: run.html_url, target: "_blank", rel: "noopener noreferrer", className: "text-[10px] font-mono text-[--cyan] hover:underline", children: "View on GitHub \u2192" }) })
174
+ ] })
175
+ }
176
+ ) })
177
+ ]
178
+ }
179
+ );
180
+ }
181
+ function MissionQueue({ runs, agents, selectedAgent }) {
182
+ const filteredRuns = selectedAgent ? runs.filter((r) => {
183
+ const branch = (r.branch || "").toLowerCase();
184
+ const agent = agents.find((a) => a.id === selectedAgent);
185
+ if (!agent) return false;
186
+ const codename = (agent.codename || agent.name || agent.id || "").toLowerCase();
187
+ return branch.includes(codename);
188
+ }) : runs;
189
+ const queued = filteredRuns.filter((r) => r.status === "queued");
190
+ const active = filteredRuns.filter((r) => r.status === "in_progress");
191
+ const completed = filteredRuns.filter((r) => r.status !== "queued" && r.status !== "in_progress");
192
+ const columns = [
193
+ { id: "queued", label: "QUEUED", runs: queued, color: "text-yellow-500", dotColor: "bg-yellow-500" },
194
+ { id: "active", label: "IN PROGRESS", runs: active, color: "text-green-500", dotColor: "bg-green-500 animate-pulse" },
195
+ { id: "completed", label: "COMPLETED", runs: completed, color: "text-muted-foreground", dotColor: "bg-muted-foreground" }
196
+ ];
197
+ return /* @__PURE__ */ jsx("div", { className: "flex-1 min-w-0 p-4 overflow-y-auto scrollbar-thin", children: /* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 md:grid-cols-3 gap-4 h-full", children: columns.map((col) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col min-h-0", children: [
198
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-3 pb-2 border-b border-white/[0.06]", children: [
199
+ /* @__PURE__ */ jsx("div", { className: `w-2 h-2 rounded-full ${col.dotColor}` }),
200
+ /* @__PURE__ */ jsx("h3", { className: `text-[10px] font-mono font-medium uppercase tracking-wider ${col.color}`, children: col.label }),
201
+ /* @__PURE__ */ jsxs("span", { className: "text-[10px] font-mono text-muted-foreground", children: [
202
+ "(",
203
+ col.runs.length,
204
+ ")"
205
+ ] })
206
+ ] }),
207
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 overflow-y-auto scrollbar-thin flex-1", children: [
208
+ /* @__PURE__ */ jsx(AnimatePresence, { children: col.runs.map((run, i) => /* @__PURE__ */ jsx(MissionCard, { run, agents, index: i }, run.run_id)) }),
209
+ col.runs.length === 0 && /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-8 text-[10px] font-mono text-muted-foreground/50", children: "No missions" })
210
+ ] })
211
+ ] }, col.id)) }) });
212
+ }
213
+ function LiveFeed({ runs, notifications, collapsed, onToggleCollapse }) {
214
+ const events = [];
215
+ for (const run of runs.slice(0, 20)) {
216
+ const isActive = run.status === "in_progress";
217
+ const isQueued = run.status === "queued";
218
+ const isFailed = run.conclusion === "failure";
219
+ const isSuccess = run.conclusion === "success";
220
+ events.push({
221
+ id: `run-${run.run_id}`,
222
+ time: run.updated_at || run.started_at || run.created_at,
223
+ type: isActive ? "job_started" : isQueued ? "job_created" : isFailed ? "job_failed" : isSuccess ? "job_completed" : "job_completed",
224
+ label: isActive ? "Running" : isQueued ? "Queued" : isFailed ? "Failed" : "Completed",
225
+ description: run.workflow_name || getJobId(run.branch) || "job",
226
+ color: isActive ? "text-green-500" : isQueued ? "text-yellow-500" : isFailed ? "text-[--destructive]" : "text-green-500",
227
+ dotColor: isActive ? "bg-green-500 animate-pulse" : isQueued ? "bg-yellow-500" : isFailed ? "bg-red-500" : "bg-green-500",
228
+ url: run.html_url
229
+ });
230
+ }
231
+ for (const notif of (notifications || []).slice(0, 10)) {
232
+ const payload = typeof notif.payload === "string" ? JSON.parse(notif.payload) : notif.payload;
233
+ events.push({
234
+ id: `notif-${notif.id}`,
235
+ time: notif.createdAt,
236
+ type: "notification",
237
+ label: payload?.conclusion === "failure" ? "Failed" : "PR Merged",
238
+ description: payload?.title || payload?.pr_title || "Notification",
239
+ color: payload?.conclusion === "failure" ? "text-[--destructive]" : "text-[--cyan]",
240
+ dotColor: payload?.conclusion === "failure" ? "bg-red-500" : "bg-[--cyan]",
241
+ url: payload?.pr_url || payload?.html_url
242
+ });
243
+ }
244
+ events.sort((a, b) => {
245
+ const ta = a.time ? new Date(a.time).getTime() : 0;
246
+ const tb = b.time ? new Date(b.time).getTime() : 0;
247
+ return tb - ta;
248
+ });
249
+ const unique = [];
250
+ const seen = /* @__PURE__ */ new Set();
251
+ for (const e of events) {
252
+ if (!seen.has(e.id)) {
253
+ seen.add(e.id);
254
+ unique.push(e);
255
+ }
256
+ }
257
+ return /* @__PURE__ */ jsxs("div", { className: `shrink-0 border-l border-white/[0.06] transition-all ${collapsed ? "w-14" : "w-72"}`, children: [
258
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between p-3 border-b border-white/[0.06]", children: [
259
+ /* @__PURE__ */ jsx(
260
+ "button",
261
+ {
262
+ onClick: onToggleCollapse,
263
+ className: "p-1 text-muted-foreground hover:text-foreground transition-colors",
264
+ children: /* @__PURE__ */ jsx(ChevronDownIcon, { size: 14, className: `transition-transform ${collapsed ? "rotate-90" : "-rotate-90"}` })
265
+ }
266
+ ),
267
+ !collapsed && /* @__PURE__ */ jsx("h2", { className: "text-[10px] font-mono font-medium uppercase tracking-wider text-muted-foreground", children: "Live Feed" })
268
+ ] }),
269
+ !collapsed && /* @__PURE__ */ jsxs("div", { className: "flex flex-col overflow-y-auto max-h-[calc(100vh-16rem)] scrollbar-thin", children: [
270
+ unique.slice(0, 30).map((event, i) => /* @__PURE__ */ jsxs(
271
+ motion.div,
272
+ {
273
+ initial: { opacity: 0, x: 8 },
274
+ animate: { opacity: 1, x: 0 },
275
+ transition: { duration: 0.2, delay: i * 0.02 },
276
+ className: "flex items-start gap-2 px-3 py-2 border-b border-white/[0.03] hover:bg-white/[0.02] transition-colors",
277
+ children: [
278
+ /* @__PURE__ */ jsx("div", { className: `shrink-0 w-1.5 h-1.5 rounded-full mt-1.5 ${event.dotColor}` }),
279
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
280
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
281
+ /* @__PURE__ */ jsx("span", { className: `text-[9px] font-mono font-medium ${event.color}`, children: event.label }),
282
+ /* @__PURE__ */ jsx("span", { className: "text-[9px] font-mono text-muted-foreground", children: timeAgo(event.time) })
283
+ ] }),
284
+ event.url ? /* @__PURE__ */ jsx("a", { href: event.url, target: "_blank", rel: "noopener noreferrer", className: "text-[10px] font-mono text-foreground/70 hover:text-[--cyan] truncate block transition-colors", children: event.description }) : /* @__PURE__ */ jsx("p", { className: "text-[10px] font-mono text-foreground/70 truncate", children: event.description })
285
+ ] })
286
+ ]
287
+ },
288
+ event.id
289
+ )),
290
+ unique.length === 0 && /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-8 text-center", children: [
291
+ /* @__PURE__ */ jsx(BellIcon, { size: 16 }),
292
+ /* @__PURE__ */ jsx("p", { className: "text-[10px] font-mono text-muted-foreground mt-2", children: "No events yet" })
293
+ ] })
294
+ ] })
295
+ ] });
296
+ }
297
+ function MissionControlPage() {
298
+ const [agents, setAgents] = useState([]);
299
+ const [runs, setRuns] = useState([]);
300
+ const [notifications_, setNotifications] = useState([]);
301
+ const [loading, setLoading] = useState(true);
302
+ const [refreshing, setRefreshing] = useState(false);
303
+ const [selectedAgent, setSelectedAgent] = useState(null);
304
+ const [leftCollapsed, setLeftCollapsed] = useState(false);
305
+ const [rightCollapsed, setRightCollapsed] = useState(false);
306
+ const fetchData = useCallback(async () => {
307
+ try {
308
+ const [swarm, notifs, agentProfiles] = await Promise.all([
309
+ getSwarmStatus(1),
310
+ getNotifications(),
311
+ import("../actions.js").then((m) => m.getAgentProfiles ? m.getAgentProfiles() : []).catch(() => [])
312
+ ]);
313
+ setRuns(swarm.runs || []);
314
+ setNotifications(notifs || []);
315
+ setAgents(agentProfiles || []);
316
+ } catch (err) {
317
+ console.error("Failed to fetch mission control data:", err);
318
+ } finally {
319
+ setLoading(false);
320
+ setRefreshing(false);
321
+ }
322
+ }, []);
323
+ useEffect(() => {
324
+ fetchData();
325
+ }, [fetchData]);
326
+ useEffect(() => {
327
+ const interval = setInterval(() => fetchData(), 1e4);
328
+ return () => clearInterval(interval);
329
+ }, [fetchData]);
330
+ const activeCount = runs.filter((r) => r.status === "in_progress").length;
331
+ const queuedCount = runs.filter((r) => r.status === "queued").length;
332
+ if (loading) {
333
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4 p-6", children: [
334
+ /* @__PURE__ */ jsx("div", { className: "h-8 w-48 animate-pulse rounded-lg bg-white/[0.04]" }),
335
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-3 gap-4 flex-1", children: [...Array(3)].map((_, i) => /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-3", children: [...Array(4)].map((_2, j) => /* @__PURE__ */ jsx("div", { className: "h-16 animate-pulse rounded-lg bg-white/[0.04] border border-white/[0.06]" }, j)) }, i)) })
336
+ ] });
337
+ }
338
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col h-full -m-4", children: [
339
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-4 py-3 border-b border-white/[0.06]", children: [
340
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
341
+ /* @__PURE__ */ jsx("h1", { className: "text-lg font-mono font-semibold text-[--cyan] text-glow-cyan", children: "Mission Control" }),
342
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
343
+ activeCount > 0 && /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1.5 rounded-md px-2 py-0.5 text-[10px] font-mono font-medium bg-green-500/10 text-green-500 border border-green-500/20", children: [
344
+ /* @__PURE__ */ jsx("span", { className: "w-1.5 h-1.5 rounded-full bg-green-500 animate-pulse" }),
345
+ activeCount,
346
+ " active"
347
+ ] }),
348
+ queuedCount > 0 && /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1.5 rounded-md px-2 py-0.5 text-[10px] font-mono font-medium bg-yellow-500/10 text-yellow-500 border border-yellow-500/20", children: [
349
+ queuedCount,
350
+ " queued"
351
+ ] }),
352
+ /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1.5 rounded-md px-2 py-0.5 text-[10px] font-mono font-medium bg-[--cyan]/10 text-[--cyan] border border-[--cyan]/20", children: [
353
+ agents.length,
354
+ " agents"
355
+ ] })
356
+ ] })
357
+ ] }),
358
+ /* @__PURE__ */ jsxs(
359
+ "button",
360
+ {
361
+ onClick: () => {
362
+ setRefreshing(true);
363
+ fetchData();
364
+ },
365
+ disabled: refreshing,
366
+ className: "inline-flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-mono font-medium border border-white/[0.06] text-muted-foreground hover:text-foreground hover:bg-white/[0.04] disabled:opacity-50 transition-colors",
367
+ children: [
368
+ refreshing ? /* @__PURE__ */ jsx(SpinnerIcon, { size: 14 }) : /* @__PURE__ */ jsx(RefreshIcon, { size: 14 }),
369
+ refreshing ? "Refreshing" : "Refresh"
370
+ ]
371
+ }
372
+ )
373
+ ] }),
374
+ /* @__PURE__ */ jsxs("div", { className: "hidden md:flex flex-1 min-h-0", children: [
375
+ /* @__PURE__ */ jsx(
376
+ AgentRoster,
377
+ {
378
+ agents,
379
+ runs,
380
+ selectedAgent,
381
+ onSelect: setSelectedAgent,
382
+ collapsed: leftCollapsed,
383
+ onToggleCollapse: () => setLeftCollapsed(!leftCollapsed)
384
+ }
385
+ ),
386
+ /* @__PURE__ */ jsx(
387
+ MissionQueue,
388
+ {
389
+ runs,
390
+ agents,
391
+ selectedAgent
392
+ }
393
+ ),
394
+ /* @__PURE__ */ jsx(
395
+ LiveFeed,
396
+ {
397
+ runs,
398
+ notifications: notifications_,
399
+ collapsed: rightCollapsed,
400
+ onToggleCollapse: () => setRightCollapsed(!rightCollapsed)
401
+ }
402
+ )
403
+ ] }),
404
+ /* @__PURE__ */ jsx(
405
+ MobileMissionControl,
406
+ {
407
+ agents,
408
+ runs,
409
+ notifications: notifications_,
410
+ selectedAgent,
411
+ onSelectAgent: setSelectedAgent
412
+ }
413
+ )
414
+ ] });
415
+ }
416
+ function MobileMissionControl({ agents, runs, notifications, selectedAgent, onSelectAgent }) {
417
+ const [tab, setTab] = useState("missions");
418
+ const tabs = [
419
+ { id: "agents", label: "AGENTS", count: agents.length },
420
+ { id: "missions", label: "MISSIONS", count: runs.length },
421
+ { id: "feed", label: "FEED", count: null }
422
+ ];
423
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col flex-1 md:hidden", children: [
424
+ /* @__PURE__ */ jsx("div", { className: "flex gap-1 border-b border-white/[0.06] px-2", children: tabs.map((t) => /* @__PURE__ */ jsxs(
425
+ "button",
426
+ {
427
+ onClick: () => setTab(t.id),
428
+ className: `px-3 py-2 text-[10px] font-mono font-medium uppercase tracking-wider border-b-2 transition-colors ${tab === t.id ? "border-[--cyan] text-[--cyan]" : "border-transparent text-muted-foreground hover:text-foreground"}`,
429
+ children: [
430
+ t.label,
431
+ " ",
432
+ t.count !== null && /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
433
+ "(",
434
+ t.count,
435
+ ")"
436
+ ] })
437
+ ]
438
+ },
439
+ t.id
440
+ )) }),
441
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto p-3", children: [
442
+ tab === "agents" && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
443
+ /* @__PURE__ */ jsxs(
444
+ "button",
445
+ {
446
+ onClick: () => {
447
+ onSelectAgent(null);
448
+ setTab("missions");
449
+ },
450
+ className: `flex items-center gap-2 rounded-md px-3 py-2 text-left transition-colors ${selectedAgent === null ? "bg-[--cyan]/10 text-[--cyan]" : "hover:bg-white/[0.04]"}`,
451
+ children: [
452
+ /* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-[--cyan]" }),
453
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-mono", children: "All Agents" })
454
+ ]
455
+ }
456
+ ),
457
+ agents.map((agent) => {
458
+ const codename = agent.codename || agent.name || agent.id;
459
+ return /* @__PURE__ */ jsxs(
460
+ "button",
461
+ {
462
+ onClick: () => {
463
+ onSelectAgent(agent.id);
464
+ setTab("missions");
465
+ },
466
+ className: `flex items-center gap-2 rounded-md px-3 py-2 text-left transition-colors ${selectedAgent === agent.id ? "bg-[--cyan]/10 text-[--cyan]" : "hover:bg-white/[0.04]"}`,
467
+ children: [
468
+ /* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-muted-foreground/40" }),
469
+ /* @__PURE__ */ jsxs("div", { children: [
470
+ /* @__PURE__ */ jsxs("span", { className: "text-xs font-mono font-medium block", children: [
471
+ "@",
472
+ codename.toUpperCase()
473
+ ] }),
474
+ agent.role && /* @__PURE__ */ jsx("span", { className: "text-[10px] font-mono text-muted-foreground block", children: agent.role })
475
+ ] })
476
+ ]
477
+ },
478
+ agent.id
479
+ );
480
+ })
481
+ ] }),
482
+ tab === "missions" && /* @__PURE__ */ jsx(MissionQueue, { runs, agents, selectedAgent }),
483
+ tab === "feed" && /* @__PURE__ */ jsx(LiveFeed, { runs, notifications, collapsed: false, onToggleCollapse: () => {
484
+ } })
485
+ ] })
486
+ ] });
487
+ }
488
+ export {
489
+ MissionControlPage
490
+ };