@bopen-io/tortuga-plugin 0.0.3 → 0.0.4

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.
package/dist/ui/index.js CHANGED
@@ -1,28 +1,903 @@
1
1
  // src/ui/index.tsx
2
- import { usePluginAction, usePluginData } from "@paperclipai/plugin-sdk/ui";
2
+ import { useMemo, useState } from "react";
3
+ import {
4
+ usePluginAction,
5
+ usePluginData,
6
+ usePluginStream,
7
+ usePluginToast
8
+ } from "@paperclipai/plugin-sdk/ui";
3
9
  import { jsx, jsxs } from "react/jsx-runtime";
4
- function DashboardWidget(_props) {
5
- const { data, loading, error } = usePluginData("health");
6
- const ping = usePluginAction("ping");
7
- if (loading) return /* @__PURE__ */ jsx("div", { children: "Loading plugin health..." });
8
- if (error) return /* @__PURE__ */ jsxs("div", { children: [
9
- "Plugin error: ",
10
- error.message
10
+ var PAGE_ROUTE = "tortuga";
11
+ var layoutStack = {
12
+ display: "grid",
13
+ gap: "12px"
14
+ };
15
+ var cardStyle = {
16
+ border: "1px solid var(--border)",
17
+ borderRadius: "12px",
18
+ padding: "14px",
19
+ background: "var(--card, transparent)"
20
+ };
21
+ var subtleCardStyle = {
22
+ border: "1px solid color-mix(in srgb, var(--border) 75%, transparent)",
23
+ borderRadius: "10px",
24
+ padding: "12px"
25
+ };
26
+ var rowStyle = {
27
+ display: "flex",
28
+ flexWrap: "wrap",
29
+ alignItems: "center",
30
+ gap: "8px"
31
+ };
32
+ var buttonStyle = {
33
+ appearance: "none",
34
+ border: "1px solid var(--border)",
35
+ borderRadius: "999px",
36
+ background: "transparent",
37
+ color: "inherit",
38
+ padding: "6px 12px",
39
+ fontSize: "12px",
40
+ cursor: "pointer"
41
+ };
42
+ var primaryButtonStyle = {
43
+ ...buttonStyle,
44
+ background: "var(--foreground)",
45
+ color: "var(--background)",
46
+ borderColor: "var(--foreground)"
47
+ };
48
+ var dangerButtonStyle = {
49
+ ...buttonStyle,
50
+ color: "var(--destructive, #dc2626)",
51
+ borderColor: "color-mix(in srgb, var(--destructive, #dc2626) 50%, var(--border))"
52
+ };
53
+ var inputStyle = {
54
+ width: "100%",
55
+ border: "1px solid var(--border)",
56
+ borderRadius: "8px",
57
+ padding: "8px 10px",
58
+ background: "transparent",
59
+ color: "inherit",
60
+ fontSize: "12px"
61
+ };
62
+ var mutedTextStyle = {
63
+ fontSize: "12px",
64
+ opacity: 0.72,
65
+ lineHeight: 1.45
66
+ };
67
+ var eyebrowStyle = {
68
+ fontSize: "11px",
69
+ opacity: 0.65,
70
+ textTransform: "uppercase",
71
+ letterSpacing: "0.06em"
72
+ };
73
+ var sectionHeaderStyle = {
74
+ display: "flex",
75
+ alignItems: "center",
76
+ justifyContent: "space-between",
77
+ gap: "8px",
78
+ marginBottom: "10px"
79
+ };
80
+ var statValueStyle = {
81
+ fontSize: "24px",
82
+ fontWeight: 700,
83
+ lineHeight: 1
84
+ };
85
+ var statLabelStyle = {
86
+ fontSize: "11px",
87
+ opacity: 0.6,
88
+ textTransform: "uppercase",
89
+ letterSpacing: "0.06em",
90
+ marginTop: "4px"
91
+ };
92
+ function hostPath(companyPrefix, suffix) {
93
+ return companyPrefix ? `/${companyPrefix}${suffix}` : suffix;
94
+ }
95
+ function pluginPagePath(companyPrefix) {
96
+ return hostPath(companyPrefix, `/${PAGE_ROUTE}`);
97
+ }
98
+ function relativeTime(isoString) {
99
+ if (!isoString) return "never";
100
+ const then = new Date(isoString).getTime();
101
+ if (Number.isNaN(then)) return "unknown";
102
+ const diffMs = Date.now() - then;
103
+ if (diffMs < 0) return "just now";
104
+ const seconds = Math.floor(diffMs / 1e3);
105
+ if (seconds < 60) return `${seconds}s ago`;
106
+ const minutes = Math.floor(seconds / 60);
107
+ if (minutes < 60) return `${minutes}m ago`;
108
+ const hours = Math.floor(minutes / 60);
109
+ if (hours < 24) return `${hours}h ago`;
110
+ const days = Math.floor(hours / 24);
111
+ return `${days}d ago`;
112
+ }
113
+ function formatDuration(ms) {
114
+ if (ms === null) return "--";
115
+ if (ms < 1e3) return `${ms}ms`;
116
+ const seconds = Math.round(ms / 1e3);
117
+ if (seconds < 60) return `${seconds}s`;
118
+ const minutes = Math.floor(seconds / 60);
119
+ const remainder = seconds % 60;
120
+ return `${minutes}m ${remainder}s`;
121
+ }
122
+ function Pill({ label, color }) {
123
+ return /* @__PURE__ */ jsx(
124
+ "span",
125
+ {
126
+ style: {
127
+ display: "inline-flex",
128
+ alignItems: "center",
129
+ gap: "4px",
130
+ borderRadius: "999px",
131
+ border: "1px solid var(--border)",
132
+ padding: "2px 8px",
133
+ fontSize: "11px",
134
+ background: color ? `color-mix(in srgb, ${color} 14%, transparent)` : void 0,
135
+ borderColor: color ? `color-mix(in srgb, ${color} 40%, var(--border))` : void 0
136
+ },
137
+ children: label
138
+ }
139
+ );
140
+ }
141
+ var STATUS_COLORS = {
142
+ running: "#2563eb",
143
+ idle: "#d97706",
144
+ error: "#dc2626",
145
+ paused: "#6b7280",
146
+ offline: "#6b7280",
147
+ online: "#16a34a"
148
+ };
149
+ function StatusDot({ status }) {
150
+ const dotColor = STATUS_COLORS[status.toLowerCase()] ?? "#6b7280";
151
+ return /* @__PURE__ */ jsx(
152
+ "span",
153
+ {
154
+ style: {
155
+ display: "inline-block",
156
+ width: "7px",
157
+ height: "7px",
158
+ borderRadius: "50%",
159
+ background: dotColor,
160
+ flexShrink: 0
161
+ },
162
+ "aria-label": status
163
+ }
164
+ );
165
+ }
166
+ function EmptyState({ message }) {
167
+ return /* @__PURE__ */ jsx(
168
+ "div",
169
+ {
170
+ style: {
171
+ padding: "32px 16px",
172
+ textAlign: "center",
173
+ fontSize: "13px",
174
+ opacity: 0.55
175
+ },
176
+ children: message
177
+ }
178
+ );
179
+ }
180
+ function LoadingIndicator({ message }) {
181
+ return /* @__PURE__ */ jsx(
182
+ "div",
183
+ {
184
+ style: {
185
+ padding: "24px 16px",
186
+ textAlign: "center",
187
+ fontSize: "12px",
188
+ opacity: 0.6
189
+ },
190
+ children: message ?? "Loading..."
191
+ }
192
+ );
193
+ }
194
+ function ErrorBanner({ message }) {
195
+ return /* @__PURE__ */ jsx(
196
+ "div",
197
+ {
198
+ style: {
199
+ ...subtleCardStyle,
200
+ borderColor: "color-mix(in srgb, #dc2626 45%, var(--border))",
201
+ fontSize: "12px",
202
+ color: "var(--destructive, #dc2626)"
203
+ },
204
+ children: message
205
+ }
206
+ );
207
+ }
208
+ function Section({
209
+ title,
210
+ action,
211
+ children
212
+ }) {
213
+ return /* @__PURE__ */ jsxs("section", { style: cardStyle, children: [
214
+ /* @__PURE__ */ jsxs("div", { style: sectionHeaderStyle, children: [
215
+ /* @__PURE__ */ jsx("strong", { children: title }),
216
+ action
217
+ ] }),
218
+ /* @__PURE__ */ jsx("div", { style: layoutStack, children })
219
+ ] });
220
+ }
221
+ function statusCounts(agents) {
222
+ const counts = {
223
+ running: 0,
224
+ idle: 0,
225
+ error: 0,
226
+ paused: 0,
227
+ offline: 0
228
+ };
229
+ for (const agent of agents) {
230
+ const s = agent.status;
231
+ if (s in counts) {
232
+ counts[s]++;
233
+ }
234
+ }
235
+ return counts;
236
+ }
237
+ function StatusBreakdown({ agents }) {
238
+ const counts = statusCounts(agents);
239
+ const entries = [
240
+ { status: "running", label: "Running" },
241
+ { status: "idle", label: "Idle" },
242
+ { status: "error", label: "Error" },
243
+ { status: "paused", label: "Paused" }
244
+ ];
245
+ return /* @__PURE__ */ jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: "10px" }, children: entries.map(({ status, label }) => /* @__PURE__ */ jsxs(
246
+ "div",
247
+ {
248
+ style: {
249
+ display: "flex",
250
+ alignItems: "center",
251
+ gap: "5px",
252
+ fontSize: "12px"
253
+ },
254
+ children: [
255
+ /* @__PURE__ */ jsx(StatusDot, { status }),
256
+ /* @__PURE__ */ jsxs("span", { style: { opacity: 0.8 }, children: [
257
+ counts[status],
258
+ " ",
259
+ label
260
+ ] })
261
+ ]
262
+ },
263
+ status
264
+ )) });
265
+ }
266
+ function FleetIcon({ size = 16 }) {
267
+ return /* @__PURE__ */ jsxs(
268
+ "svg",
269
+ {
270
+ width: size,
271
+ height: size,
272
+ viewBox: "0 0 24 24",
273
+ fill: "none",
274
+ stroke: "currentColor",
275
+ strokeWidth: "1.9",
276
+ strokeLinecap: "round",
277
+ strokeLinejoin: "round",
278
+ "aria-hidden": "true",
279
+ style: { flexShrink: 0 },
280
+ children: [
281
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "6" }),
282
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "2" }),
283
+ /* @__PURE__ */ jsx("path", { d: "M12 6v-3" }),
284
+ /* @__PURE__ */ jsx("path", { d: "M12 21v-3" }),
285
+ /* @__PURE__ */ jsx("path", { d: "M6 12H3" }),
286
+ /* @__PURE__ */ jsx("path", { d: "M21 12h-3" }),
287
+ /* @__PURE__ */ jsx("path", { d: "M7.76 7.76L5.64 5.64" }),
288
+ /* @__PURE__ */ jsx("path", { d: "M18.36 18.36l-2.12-2.12" }),
289
+ /* @__PURE__ */ jsx("path", { d: "M16.24 7.76l2.12-2.12" }),
290
+ /* @__PURE__ */ jsx("path", { d: "M5.64 18.36l2.12-2.12" })
291
+ ]
292
+ }
293
+ );
294
+ }
295
+ function FleetStatusWidget({ context }) {
296
+ const companyId = context.companyId;
297
+ const overviewParams = useMemo(
298
+ () => companyId ? { companyId } : {},
299
+ [companyId]
300
+ );
301
+ const { data: overview, loading, error } = usePluginData(
302
+ "fleet-overview",
303
+ overviewParams
304
+ );
305
+ const fleetStream = usePluginStream("fleet-status", {
306
+ companyId: companyId ?? void 0
307
+ });
308
+ const agents = useMemo(() => {
309
+ const base = overview?.agents ?? [];
310
+ if (fleetStream.events.length === 0) return base;
311
+ const latestStatus = /* @__PURE__ */ new Map();
312
+ for (const event of fleetStream.events) {
313
+ latestStatus.set(event.agentId, event.status);
314
+ }
315
+ return base.map((agent) => {
316
+ const liveStatus = latestStatus.get(agent.id);
317
+ return liveStatus ? { ...agent, status: liveStatus } : agent;
318
+ });
319
+ }, [overview?.agents, fleetStream.events]);
320
+ if (loading) return /* @__PURE__ */ jsx(LoadingIndicator, { message: "Loading fleet status..." });
321
+ if (error) return /* @__PURE__ */ jsx(ErrorBanner, { message: error.message });
322
+ const totalAgents = agents.length;
323
+ return /* @__PURE__ */ jsxs("div", { style: layoutStack, children: [
324
+ /* @__PURE__ */ jsxs("div", { style: rowStyle, children: [
325
+ /* @__PURE__ */ jsx("strong", { children: "Fleet Status" }),
326
+ fleetStream.connected ? /* @__PURE__ */ jsx(StatusDot, { status: "online" }) : null
327
+ ] }),
328
+ /* @__PURE__ */ jsxs(
329
+ "div",
330
+ {
331
+ style: {
332
+ display: "grid",
333
+ gridTemplateColumns: "1fr 1fr",
334
+ gap: "8px"
335
+ },
336
+ children: [
337
+ /* @__PURE__ */ jsxs("div", { children: [
338
+ /* @__PURE__ */ jsx("div", { style: statValueStyle, children: totalAgents }),
339
+ /* @__PURE__ */ jsx("div", { style: statLabelStyle, children: "Agents" })
340
+ ] }),
341
+ /* @__PURE__ */ jsxs("div", { children: [
342
+ /* @__PURE__ */ jsx("div", { style: statValueStyle, children: statusCounts(agents).running }),
343
+ /* @__PURE__ */ jsx("div", { style: statLabelStyle, children: "Running" })
344
+ ] })
345
+ ]
346
+ }
347
+ ),
348
+ /* @__PURE__ */ jsx(StatusBreakdown, { agents }),
349
+ /* @__PURE__ */ jsxs("div", { style: { ...mutedTextStyle, fontSize: "11px" }, children: [
350
+ "Last health check: ",
351
+ relativeTime(overview?.lastHealthCheck ?? null)
352
+ ] }),
353
+ /* @__PURE__ */ jsx(
354
+ "a",
355
+ {
356
+ href: pluginPagePath(context.companyPrefix),
357
+ style: { fontSize: "12px", color: "inherit" },
358
+ children: "Open fleet monitor"
359
+ }
360
+ )
11
361
  ] });
12
- return /* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: "0.5rem" }, children: [
13
- /* @__PURE__ */ jsx("strong", { children: "Tortuga" }),
14
- /* @__PURE__ */ jsxs("div", { children: [
15
- "Health: ",
16
- data?.status ?? "unknown"
362
+ }
363
+ function StatusFilterBar({
364
+ active,
365
+ onChange,
366
+ counts
367
+ }) {
368
+ const filters = [
369
+ { key: "all", label: "All" },
370
+ { key: "running", label: "Running" },
371
+ { key: "idle", label: "Idle" },
372
+ { key: "error", label: "Error" },
373
+ { key: "paused", label: "Paused" }
374
+ ];
375
+ return /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: "0", borderBottom: "1px solid var(--border)" }, children: filters.map((filter) => {
376
+ const count = filter.key === "all" ? Object.values(counts).reduce((a, b) => a + b, 0) : counts[filter.key];
377
+ return /* @__PURE__ */ jsxs(
378
+ "button",
379
+ {
380
+ type: "button",
381
+ onClick: () => onChange(filter.key),
382
+ style: {
383
+ appearance: "none",
384
+ background: "transparent",
385
+ border: "none",
386
+ borderBottom: active === filter.key ? "2px solid var(--foreground)" : "2px solid transparent",
387
+ color: active === filter.key ? "var(--foreground)" : "var(--muted-foreground, inherit)",
388
+ padding: "10px 16px",
389
+ fontSize: "13px",
390
+ fontWeight: active === filter.key ? 600 : 400,
391
+ cursor: "pointer",
392
+ transition: "color 0.15s, border-color 0.15s"
393
+ },
394
+ children: [
395
+ filter.label,
396
+ /* @__PURE__ */ jsx("span", { style: { marginLeft: "6px", fontSize: "11px", opacity: 0.6 }, children: count })
397
+ ]
398
+ },
399
+ filter.key
400
+ );
401
+ }) });
402
+ }
403
+ function AgentRow({
404
+ agent,
405
+ onSelect,
406
+ onPause,
407
+ onResume,
408
+ onInvoke,
409
+ actionLoading
410
+ }) {
411
+ return /* @__PURE__ */ jsxs(
412
+ "div",
413
+ {
414
+ style: {
415
+ ...subtleCardStyle,
416
+ display: "grid",
417
+ gridTemplateColumns: "1fr auto",
418
+ gap: "10px",
419
+ alignItems: "center",
420
+ cursor: "pointer",
421
+ transition: "border-color 0.15s"
422
+ },
423
+ onClick: onSelect,
424
+ onKeyDown: (e) => {
425
+ if (e.key === "Enter" || e.key === " ") {
426
+ e.preventDefault();
427
+ onSelect();
428
+ }
429
+ },
430
+ role: "button",
431
+ tabIndex: 0,
432
+ children: [
433
+ /* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: "6px", minWidth: 0 }, children: [
434
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
435
+ /* @__PURE__ */ jsx(StatusDot, { status: agent.status }),
436
+ /* @__PURE__ */ jsx(
437
+ "span",
438
+ {
439
+ style: {
440
+ fontSize: "13px",
441
+ fontWeight: 600,
442
+ overflow: "hidden",
443
+ textOverflow: "ellipsis",
444
+ whiteSpace: "nowrap"
445
+ },
446
+ children: agent.name
447
+ }
448
+ ),
449
+ agent.role ? /* @__PURE__ */ jsx(Pill, { label: agent.role }) : null,
450
+ agent.adapterType ? /* @__PURE__ */ jsx(Pill, { label: agent.adapterType, color: "#6366f1" }) : null
451
+ ] }),
452
+ /* @__PURE__ */ jsxs("div", { style: mutedTextStyle, children: [
453
+ "Last heartbeat: ",
454
+ relativeTime(agent.lastHeartbeat),
455
+ agent.lastRunResult ? ` \xB7 Last run: ${agent.lastRunResult}` : ""
456
+ ] })
457
+ ] }),
458
+ /* @__PURE__ */ jsxs(
459
+ "div",
460
+ {
461
+ style: { display: "flex", gap: "6px", flexShrink: 0 },
462
+ onClick: (e) => e.stopPropagation(),
463
+ onKeyDown: (e) => e.stopPropagation(),
464
+ children: [
465
+ agent.status === "running" || agent.status === "idle" ? /* @__PURE__ */ jsx(
466
+ "button",
467
+ {
468
+ type: "button",
469
+ style: buttonStyle,
470
+ onClick: onPause,
471
+ disabled: actionLoading,
472
+ title: "Pause agent",
473
+ children: "Pause"
474
+ }
475
+ ) : null,
476
+ agent.status === "paused" ? /* @__PURE__ */ jsx(
477
+ "button",
478
+ {
479
+ type: "button",
480
+ style: buttonStyle,
481
+ onClick: onResume,
482
+ disabled: actionLoading,
483
+ title: "Resume agent",
484
+ children: "Resume"
485
+ }
486
+ ) : null,
487
+ /* @__PURE__ */ jsx(
488
+ "button",
489
+ {
490
+ type: "button",
491
+ style: primaryButtonStyle,
492
+ onClick: onInvoke,
493
+ disabled: actionLoading || agent.status === "paused",
494
+ title: "Invoke agent",
495
+ children: "Invoke"
496
+ }
497
+ )
498
+ ]
499
+ }
500
+ )
501
+ ]
502
+ }
503
+ );
504
+ }
505
+ function AgentDetailPanel({
506
+ agentId,
507
+ companyId,
508
+ onBack,
509
+ onPause,
510
+ onResume,
511
+ onInvoke
512
+ }) {
513
+ const detailParams = useMemo(
514
+ () => ({ companyId, agentId }),
515
+ [companyId, agentId]
516
+ );
517
+ const { data: detail, loading, error } = usePluginData(
518
+ "agent-detail",
519
+ detailParams
520
+ );
521
+ const [invokePrompt, setInvokePrompt] = useState("");
522
+ if (loading) return /* @__PURE__ */ jsx(LoadingIndicator, { message: "Loading agent details..." });
523
+ if (error) return /* @__PURE__ */ jsx(ErrorBanner, { message: error.message });
524
+ if (!detail) return /* @__PURE__ */ jsx(EmptyState, { message: "Agent not found." });
525
+ const { agent, recentRuns } = detail;
526
+ return /* @__PURE__ */ jsxs("div", { style: layoutStack, children: [
527
+ /* @__PURE__ */ jsxs("div", { style: rowStyle, children: [
528
+ /* @__PURE__ */ jsx("button", { type: "button", style: buttonStyle, onClick: onBack, children: "Back" }),
529
+ /* @__PURE__ */ jsx(StatusDot, { status: agent.status }),
530
+ /* @__PURE__ */ jsx("strong", { style: { fontSize: "16px" }, children: agent.name }),
531
+ /* @__PURE__ */ jsx(Pill, { label: agent.status, color: STATUS_COLORS[agent.status] })
17
532
  ] }),
18
- /* @__PURE__ */ jsxs("div", { children: [
19
- "Checked: ",
20
- data?.checkedAt ?? "never"
533
+ /* @__PURE__ */ jsxs(
534
+ "div",
535
+ {
536
+ style: {
537
+ display: "grid",
538
+ gridTemplateColumns: "repeat(auto-fit, minmax(160px, 1fr))",
539
+ gap: "12px"
540
+ },
541
+ children: [
542
+ /* @__PURE__ */ jsxs("div", { style: subtleCardStyle, children: [
543
+ /* @__PURE__ */ jsx("div", { style: eyebrowStyle, children: "Role" }),
544
+ /* @__PURE__ */ jsx("div", { style: { fontSize: "13px", marginTop: "4px" }, children: agent.role ?? "Not assigned" })
545
+ ] }),
546
+ /* @__PURE__ */ jsxs("div", { style: subtleCardStyle, children: [
547
+ /* @__PURE__ */ jsx("div", { style: eyebrowStyle, children: "Adapter" }),
548
+ /* @__PURE__ */ jsx("div", { style: { fontSize: "13px", marginTop: "4px" }, children: agent.adapterType ?? "Default" })
549
+ ] }),
550
+ /* @__PURE__ */ jsxs("div", { style: subtleCardStyle, children: [
551
+ /* @__PURE__ */ jsx("div", { style: eyebrowStyle, children: "Last Heartbeat" }),
552
+ /* @__PURE__ */ jsx("div", { style: { fontSize: "13px", marginTop: "4px" }, children: relativeTime(agent.lastHeartbeat) })
553
+ ] }),
554
+ /* @__PURE__ */ jsxs("div", { style: subtleCardStyle, children: [
555
+ /* @__PURE__ */ jsx("div", { style: eyebrowStyle, children: "Last Run Duration" }),
556
+ /* @__PURE__ */ jsx("div", { style: { fontSize: "13px", marginTop: "4px" }, children: formatDuration(agent.lastRunDurationMs) })
557
+ ] })
558
+ ]
559
+ }
560
+ ),
561
+ /* @__PURE__ */ jsxs("div", { style: rowStyle, children: [
562
+ agent.status === "running" || agent.status === "idle" ? /* @__PURE__ */ jsx(
563
+ "button",
564
+ {
565
+ type: "button",
566
+ style: dangerButtonStyle,
567
+ onClick: () => onPause(agent.id),
568
+ children: "Pause Agent"
569
+ }
570
+ ) : null,
571
+ agent.status === "paused" ? /* @__PURE__ */ jsx(
572
+ "button",
573
+ {
574
+ type: "button",
575
+ style: buttonStyle,
576
+ onClick: () => onResume(agent.id),
577
+ children: "Resume Agent"
578
+ }
579
+ ) : null
21
580
  ] }),
22
- /* @__PURE__ */ jsx("button", { onClick: () => void ping(), children: "Ping Worker" })
581
+ /* @__PURE__ */ jsx(Section, { title: "Invoke Agent", children: /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "8px" }, children: [
582
+ /* @__PURE__ */ jsx(
583
+ "input",
584
+ {
585
+ type: "text",
586
+ value: invokePrompt,
587
+ onChange: (e) => setInvokePrompt(e.target.value),
588
+ placeholder: "Enter prompt to invoke agent...",
589
+ style: { ...inputStyle, flex: 1 },
590
+ onKeyDown: (e) => {
591
+ if (e.key === "Enter" && invokePrompt.trim()) {
592
+ onInvoke(agent.id, invokePrompt.trim());
593
+ setInvokePrompt("");
594
+ }
595
+ }
596
+ }
597
+ ),
598
+ /* @__PURE__ */ jsx(
599
+ "button",
600
+ {
601
+ type: "button",
602
+ style: primaryButtonStyle,
603
+ onClick: () => {
604
+ if (invokePrompt.trim()) {
605
+ onInvoke(agent.id, invokePrompt.trim());
606
+ setInvokePrompt("");
607
+ }
608
+ },
609
+ disabled: !invokePrompt.trim() || agent.status === "paused",
610
+ children: "Send"
611
+ }
612
+ )
613
+ ] }) }),
614
+ /* @__PURE__ */ jsx(Section, { title: "Recent Runs", children: recentRuns.length === 0 ? /* @__PURE__ */ jsx(EmptyState, { message: "No recent runs recorded." }) : /* @__PURE__ */ jsx("div", { style: { display: "grid", gap: "8px" }, children: recentRuns.map((run) => /* @__PURE__ */ jsxs(
615
+ "div",
616
+ {
617
+ style: {
618
+ ...subtleCardStyle,
619
+ display: "grid",
620
+ gridTemplateColumns: "1fr auto",
621
+ gap: "6px",
622
+ alignItems: "start"
623
+ },
624
+ children: [
625
+ /* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: "4px", minWidth: 0 }, children: [
626
+ /* @__PURE__ */ jsx("div", { style: { fontSize: "12px", fontWeight: 600 }, children: run.result }),
627
+ run.prompt ? /* @__PURE__ */ jsx("div", { style: { ...mutedTextStyle, fontSize: "11px" }, children: run.prompt }) : null
628
+ ] }),
629
+ /* @__PURE__ */ jsxs(
630
+ "div",
631
+ {
632
+ style: {
633
+ display: "grid",
634
+ gap: "2px",
635
+ textAlign: "right",
636
+ flexShrink: 0
637
+ },
638
+ children: [
639
+ /* @__PURE__ */ jsx("div", { style: { fontSize: "11px", opacity: 0.6 }, children: relativeTime(run.startedAt) }),
640
+ /* @__PURE__ */ jsx("div", { style: { fontSize: "11px", opacity: 0.5 }, children: formatDuration(run.durationMs) })
641
+ ]
642
+ }
643
+ )
644
+ ]
645
+ },
646
+ run.id
647
+ )) }) })
648
+ ] });
649
+ }
650
+ function FleetMonitorPage({ context }) {
651
+ const companyId = context.companyId;
652
+ const toast = usePluginToast();
653
+ const overviewParams = useMemo(
654
+ () => companyId ? { companyId } : {},
655
+ [companyId]
656
+ );
657
+ const {
658
+ data: overview,
659
+ loading,
660
+ error,
661
+ refresh
662
+ } = usePluginData("fleet-overview", overviewParams);
663
+ const fleetStream = usePluginStream("fleet-status", {
664
+ companyId: companyId ?? void 0
665
+ });
666
+ const pauseAgent = usePluginAction("pause-agent");
667
+ const resumeAgent = usePluginAction("resume-agent");
668
+ const invokeAgent = usePluginAction("invoke-agent");
669
+ const [statusFilter, setStatusFilter] = useState("all");
670
+ const [detailView, setDetailView] = useState(null);
671
+ const [actionLoading, setActionLoading] = useState(false);
672
+ const agents = useMemo(() => {
673
+ const base = overview?.agents ?? [];
674
+ if (fleetStream.events.length === 0) return base;
675
+ const latestStatus = /* @__PURE__ */ new Map();
676
+ for (const event of fleetStream.events) {
677
+ latestStatus.set(event.agentId, event.status);
678
+ }
679
+ return base.map((agent) => {
680
+ const liveStatus = latestStatus.get(agent.id);
681
+ return liveStatus ? { ...agent, status: liveStatus } : agent;
682
+ });
683
+ }, [overview?.agents, fleetStream.events]);
684
+ const filteredAgents = useMemo(() => {
685
+ if (statusFilter === "all") return agents;
686
+ return agents.filter((a) => a.status === statusFilter);
687
+ }, [agents, statusFilter]);
688
+ const counts = useMemo(() => statusCounts(agents), [agents]);
689
+ async function handlePause(agentId) {
690
+ if (!companyId || actionLoading) return;
691
+ setActionLoading(true);
692
+ try {
693
+ await pauseAgent({ companyId, agentId });
694
+ refresh();
695
+ toast({
696
+ title: "Agent paused",
697
+ body: "The agent has been paused and will not execute until resumed.",
698
+ tone: "success"
699
+ });
700
+ } catch (err) {
701
+ toast({
702
+ title: "Failed to pause agent",
703
+ body: err instanceof Error ? err.message : String(err),
704
+ tone: "error"
705
+ });
706
+ } finally {
707
+ setActionLoading(false);
708
+ }
709
+ }
710
+ async function handleResume(agentId) {
711
+ if (!companyId || actionLoading) return;
712
+ setActionLoading(true);
713
+ try {
714
+ await resumeAgent({ companyId, agentId });
715
+ refresh();
716
+ toast({
717
+ title: "Agent resumed",
718
+ body: "The agent is now active and ready to process work.",
719
+ tone: "success"
720
+ });
721
+ } catch (err) {
722
+ toast({
723
+ title: "Failed to resume agent",
724
+ body: err instanceof Error ? err.message : String(err),
725
+ tone: "error"
726
+ });
727
+ } finally {
728
+ setActionLoading(false);
729
+ }
730
+ }
731
+ async function handleInvoke(agentId, prompt) {
732
+ if (!companyId || actionLoading) return;
733
+ setActionLoading(true);
734
+ try {
735
+ await invokeAgent({ companyId, agentId, prompt: prompt ?? void 0 });
736
+ refresh();
737
+ toast({
738
+ title: "Agent invoked",
739
+ body: prompt ? `Agent invoked with prompt: "${prompt}"` : "Agent has been invoked.",
740
+ tone: "success"
741
+ });
742
+ } catch (err) {
743
+ toast({
744
+ title: "Failed to invoke agent",
745
+ body: err instanceof Error ? err.message : String(err),
746
+ tone: "error"
747
+ });
748
+ } finally {
749
+ setActionLoading(false);
750
+ }
751
+ }
752
+ if (detailView && companyId) {
753
+ return /* @__PURE__ */ jsx("div", { style: { ...layoutStack, maxWidth: "800px" }, children: /* @__PURE__ */ jsx(
754
+ AgentDetailPanel,
755
+ {
756
+ agentId: detailView.agentId,
757
+ companyId,
758
+ onBack: () => setDetailView(null),
759
+ onPause: (id) => void handlePause(id),
760
+ onResume: (id) => void handleResume(id),
761
+ onInvoke: (id, prompt) => void handleInvoke(id, prompt)
762
+ }
763
+ ) });
764
+ }
765
+ if (!companyId) {
766
+ return /* @__PURE__ */ jsx("div", { style: layoutStack, children: /* @__PURE__ */ jsx(Section, { title: "Fleet Monitor", children: /* @__PURE__ */ jsx(EmptyState, { message: "Select a company to view your agent fleet." }) }) });
767
+ }
768
+ return /* @__PURE__ */ jsxs("div", { style: layoutStack, children: [
769
+ /* @__PURE__ */ jsxs(
770
+ "div",
771
+ {
772
+ style: {
773
+ display: "flex",
774
+ alignItems: "center",
775
+ justifyContent: "space-between",
776
+ gap: "12px",
777
+ flexWrap: "wrap"
778
+ },
779
+ children: [
780
+ /* @__PURE__ */ jsxs("div", { children: [
781
+ /* @__PURE__ */ jsx("h1", { style: { fontSize: "18px", fontWeight: 700, margin: 0 }, children: "Fleet Monitor" }),
782
+ /* @__PURE__ */ jsx("div", { style: mutedTextStyle, children: overview ? `${overview.total} agent${overview.total === 1 ? "" : "s"} registered. Last health check: ${relativeTime(overview.lastHealthCheck)}` : "Loading fleet data..." })
783
+ ] }),
784
+ /* @__PURE__ */ jsxs("div", { style: rowStyle, children: [
785
+ fleetStream.connected ? /* @__PURE__ */ jsxs(
786
+ "div",
787
+ {
788
+ style: {
789
+ display: "flex",
790
+ alignItems: "center",
791
+ gap: "5px",
792
+ fontSize: "11px",
793
+ opacity: 0.7
794
+ },
795
+ children: [
796
+ /* @__PURE__ */ jsx(StatusDot, { status: "online" }),
797
+ "Live"
798
+ ]
799
+ }
800
+ ) : null,
801
+ /* @__PURE__ */ jsx(
802
+ "button",
803
+ {
804
+ type: "button",
805
+ style: buttonStyle,
806
+ onClick: () => refresh(),
807
+ children: "Refresh"
808
+ }
809
+ )
810
+ ] })
811
+ ]
812
+ }
813
+ ),
814
+ /* @__PURE__ */ jsx(
815
+ StatusFilterBar,
816
+ {
817
+ active: statusFilter,
818
+ onChange: setStatusFilter,
819
+ counts
820
+ }
821
+ ),
822
+ loading && agents.length === 0 ? /* @__PURE__ */ jsx(LoadingIndicator, { message: "Loading fleet agents..." }) : error ? /* @__PURE__ */ jsx(ErrorBanner, { message: error.message }) : filteredAgents.length === 0 ? /* @__PURE__ */ jsx(
823
+ EmptyState,
824
+ {
825
+ message: statusFilter !== "all" ? `No agents with status "${statusFilter}".` : "No agents registered in this fleet."
826
+ }
827
+ ) : /* @__PURE__ */ jsx("div", { style: { display: "grid", gap: "8px" }, children: filteredAgents.map((agent) => /* @__PURE__ */ jsx(
828
+ AgentRow,
829
+ {
830
+ agent,
831
+ onSelect: () => setDetailView({ agentId: agent.id }),
832
+ onPause: () => void handlePause(agent.id),
833
+ onResume: () => void handleResume(agent.id),
834
+ onInvoke: () => void handleInvoke(agent.id),
835
+ actionLoading
836
+ },
837
+ agent.id
838
+ )) })
23
839
  ] });
24
840
  }
841
+ function TortugaSidebarLink({ context }) {
842
+ const overviewParams = useMemo(
843
+ () => context.companyId ? { companyId: context.companyId } : {},
844
+ [context.companyId]
845
+ );
846
+ const { data: overview } = usePluginData(
847
+ "fleet-overview",
848
+ overviewParams
849
+ );
850
+ const href = pluginPagePath(context.companyPrefix);
851
+ const isActive = typeof window !== "undefined" && window.location.pathname === href;
852
+ const agentCount = overview?.total ?? 0;
853
+ return /* @__PURE__ */ jsxs(
854
+ "a",
855
+ {
856
+ href,
857
+ "aria-current": isActive ? "page" : void 0,
858
+ style: {
859
+ display: "flex",
860
+ alignItems: "center",
861
+ gap: "10px",
862
+ padding: "8px 12px",
863
+ fontSize: "13px",
864
+ fontWeight: isActive ? 600 : 400,
865
+ textDecoration: "none",
866
+ color: isActive ? "var(--foreground)" : "color-mix(in srgb, var(--foreground) 80%, transparent)",
867
+ background: isActive ? "color-mix(in srgb, var(--accent, var(--muted)) 60%, transparent)" : "transparent",
868
+ borderRadius: "6px",
869
+ transition: "background 0.15s, color 0.15s",
870
+ cursor: "pointer"
871
+ },
872
+ children: [
873
+ /* @__PURE__ */ jsx(FleetIcon, { size: 16 }),
874
+ /* @__PURE__ */ jsx("span", { style: { flex: 1 }, children: "Tortuga" }),
875
+ agentCount > 0 ? /* @__PURE__ */ jsx(
876
+ "span",
877
+ {
878
+ style: {
879
+ display: "inline-flex",
880
+ alignItems: "center",
881
+ justifyContent: "center",
882
+ minWidth: "20px",
883
+ height: "18px",
884
+ borderRadius: "999px",
885
+ background: "color-mix(in srgb, var(--foreground) 12%, transparent)",
886
+ fontSize: "10px",
887
+ fontWeight: 600,
888
+ padding: "0 5px",
889
+ flexShrink: 0
890
+ },
891
+ children: agentCount
892
+ }
893
+ ) : null
894
+ ]
895
+ }
896
+ );
897
+ }
25
898
  export {
26
- DashboardWidget
899
+ FleetMonitorPage,
900
+ FleetStatusWidget,
901
+ TortugaSidebarLink
27
902
  };
28
903
  //# sourceMappingURL=index.js.map