@burdenoff/microfe-vibecontrols 2026.527.5 → 2026.527.7

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.
@@ -1,32 +1,32 @@
1
1
  import { AgentStatusBadge as e } from "./AgentStatusBadge.js";
2
2
  import { useState as t } from "react";
3
3
  import { useI18n as n } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
4
- import { Clock as r, Cpu as i, Globe as a, Link as o, MoreVertical as s, Play as c, Server as l, Settings as u, Square as d } from "lucide-react";
5
- import { AnimatePresence as f, motion as p } from "framer-motion";
6
- import { jsx as m, jsxs as h } from "react/jsx-runtime";
4
+ import { Clock as r, Cpu as i, Globe as a, Link as o, MoreVertical as s, Play as c, RefreshCw as l, Server as u, Settings as d, Square as f } from "lucide-react";
5
+ import { AnimatePresence as p, motion as m } from "framer-motion";
6
+ import { jsx as h, jsxs as g } from "react/jsx-runtime";
7
7
  //#region src/components/agents/AgentCard.tsx
8
- var g = ({ agent: g, onSelect: _, onConfigure: v, onSettings: y, onStart: b, onStop: x, onDisconnect: S, onEditTunnelUrl: C, selected: w = !1, onToggleSelect: T, className: E = "", healthErrorMessage: D }) => {
9
- let { t: O } = n(), k = (e, t) => {
10
- let n = O(e);
8
+ var _ = ({ agent: _, onSelect: v, onConfigure: y, onSettings: b, onStart: x, onStop: S, onDisconnect: C, onEditTunnelUrl: w, selected: T = !1, onToggleSelect: E, className: D = "", healthErrorMessage: O }) => {
9
+ let { t: k } = n(), A = (e, t) => {
10
+ let n = k(e);
11
11
  return n === e ? t : n;
12
- }, A = v || y, [j, M] = t(!1), N = (() => {
13
- if (g.status) return g.status;
14
- if (!g.isActive) return "OFFLINE";
15
- if (g.lastHeartbeat) {
16
- let e = new Date(g.lastHeartbeat).getTime();
12
+ }, j = y || b, [M, N] = t(!1), P = (() => {
13
+ if (_.status) return _.status;
14
+ if (!_.isActive) return "OFFLINE";
15
+ if (_.lastHeartbeat) {
16
+ let e = new Date(_.lastHeartbeat).getTime();
17
17
  if (Date.now() - e > 300 * 1e3) return "DEGRADED";
18
18
  }
19
19
  return "ACTIVE";
20
- })(), P = (e) => {
21
- if (!e) return k("agentsPage.never", "Never");
20
+ })(), F = (e) => {
21
+ if (!e) return A("agentsPage.never", "Never");
22
22
  let t = Date.now() - new Date(e).getTime();
23
- return t < 6e4 ? k("agentsPage.justNow", "Just now") : t < 36e5 ? `${Math.floor(t / 6e4)}m ago` : t < 864e5 ? `${Math.floor(t / 36e5)}h ago` : `${Math.floor(t / 864e5)}d ago`;
24
- }, F = {
23
+ return t < 6e4 ? A("agentsPage.justNow", "Just now") : t < 36e5 ? `${Math.floor(t / 6e4)}m ago` : t < 864e5 ? `${Math.floor(t / 36e5)}h ago` : `${Math.floor(t / 864e5)}d ago`;
24
+ }, I = {
25
25
  darwin: "🍎",
26
26
  linux: "🐧",
27
27
  windows: "🪟"
28
- }[g.platform || ""] || "💻";
29
- return /* @__PURE__ */ h(p.div, {
28
+ }[_.platform || ""] || "💻";
29
+ return /* @__PURE__ */ g(m.div, {
30
30
  whileHover: {
31
31
  y: -2,
32
32
  transition: {
@@ -40,68 +40,68 @@ var g = ({ agent: g, onSelect: _, onConfigure: v, onSettings: y, onStart: b, onS
40
40
  }
41
41
  },
42
42
  whileTap: { scale: .98 },
43
- className: `bg-bg-surface border rounded-lg p-4 h-full hover:border-border-strong hover:shadow-md transition-all cursor-pointer ${w ? "border-action-primary-bg ring-1 ring-action-primary-bg/30" : "border-border-default"} ${E}`,
44
- onClick: () => _?.(g),
43
+ className: `bg-bg-surface border rounded-lg p-4 h-full hover:border-border-strong hover:shadow-md transition-all cursor-pointer ${T ? "border-action-primary-bg ring-1 ring-action-primary-bg/30" : "border-border-default"} ${D}`,
44
+ onClick: () => v?.(_),
45
45
  children: [
46
- /* @__PURE__ */ h("div", {
46
+ /* @__PURE__ */ g("div", {
47
47
  className: "flex items-start justify-between gap-2",
48
- children: [/* @__PURE__ */ h("div", {
48
+ children: [/* @__PURE__ */ g("div", {
49
49
  className: "flex items-start gap-2 sm:gap-3 min-w-0 flex-1",
50
50
  children: [
51
- T && /* @__PURE__ */ m("button", {
51
+ E && /* @__PURE__ */ h("button", {
52
52
  onClick: (e) => {
53
- e.stopPropagation(), T();
53
+ e.stopPropagation(), E();
54
54
  },
55
- className: `flex-shrink-0 w-5 h-5 min-w-[1.25rem] min-h-[1.25rem] rounded border transition-colors mt-0.5 ${w ? "bg-action-primary-bg border-action-primary-bg text-action-primary-text" : "border-border-default hover:border-border-strong bg-bg-surface"} flex items-center justify-center`,
56
- children: w && /* @__PURE__ */ m("svg", {
55
+ className: `flex-shrink-0 w-5 h-5 min-w-[1.25rem] min-h-[1.25rem] rounded border transition-colors mt-0.5 ${T ? "bg-action-primary-bg border-action-primary-bg text-action-primary-text" : "border-border-default hover:border-border-strong bg-bg-surface"} flex items-center justify-center`,
56
+ children: T && /* @__PURE__ */ h("svg", {
57
57
  className: "w-3 h-3",
58
58
  fill: "none",
59
59
  viewBox: "0 0 24 24",
60
60
  stroke: "currentColor",
61
61
  strokeWidth: 3,
62
- children: /* @__PURE__ */ m("path", {
62
+ children: /* @__PURE__ */ h("path", {
63
63
  strokeLinecap: "round",
64
64
  strokeLinejoin: "round",
65
65
  d: "M5 13l4 4L19 7"
66
66
  })
67
67
  })
68
68
  }),
69
- /* @__PURE__ */ m("div", {
69
+ /* @__PURE__ */ h("div", {
70
70
  className: "flex-shrink-0 w-8 h-8 sm:w-10 sm:h-10 rounded-lg bg-bg-sunken flex items-center justify-center",
71
- children: /* @__PURE__ */ m(l, { className: "w-4 h-4 sm:w-5 sm:h-5 text-text-secondary" })
71
+ children: /* @__PURE__ */ h(u, { className: "w-4 h-4 sm:w-5 sm:h-5 text-text-secondary" })
72
72
  }),
73
- /* @__PURE__ */ h("div", {
73
+ /* @__PURE__ */ g("div", {
74
74
  className: "min-w-0 flex-1",
75
- children: [/* @__PURE__ */ h("div", {
75
+ children: [/* @__PURE__ */ g("div", {
76
76
  className: "flex items-center gap-1.5 sm:gap-2 min-w-0",
77
- children: [/* @__PURE__ */ m("h3", {
77
+ children: [/* @__PURE__ */ h("h3", {
78
78
  className: "font-semibold text-text-primary truncate min-w-0 max-w-[120px] sm:max-w-none text-sm sm:text-base",
79
- children: g.name
80
- }), /* @__PURE__ */ m("span", {
79
+ children: _.name
80
+ }), /* @__PURE__ */ h("span", {
81
81
  className: "text-base sm:text-lg flex-shrink-0",
82
- children: F
82
+ children: I
83
83
  })]
84
- }), /* @__PURE__ */ m("p", {
84
+ }), /* @__PURE__ */ h("p", {
85
85
  className: "text-xs sm:text-sm text-text-secondary truncate",
86
- children: g.hostname || `ID: ${g.id}`
86
+ children: _.hostname || `ID: ${_.id}`
87
87
  })]
88
88
  })
89
89
  ]
90
- }), /* @__PURE__ */ h("div", {
90
+ }), /* @__PURE__ */ g("div", {
91
91
  className: "flex items-center gap-1 sm:gap-2 flex-shrink-0",
92
- children: [/* @__PURE__ */ m(e, {
93
- status: N,
94
- reason: D ?? null
95
- }), (A || b || x) && /* @__PURE__ */ h("div", {
92
+ children: [/* @__PURE__ */ h(e, {
93
+ status: P,
94
+ reason: O ?? null
95
+ }), (j || x || S) && /* @__PURE__ */ g("div", {
96
96
  className: "relative",
97
- children: [/* @__PURE__ */ m("button", {
97
+ children: [/* @__PURE__ */ h("button", {
98
98
  onClick: (e) => {
99
- e.stopPropagation(), M(!j);
99
+ e.stopPropagation(), N(!M);
100
100
  },
101
101
  className: "p-1 sm:p-1.5 rounded hover:bg-bg-sunken transition-colors flex items-center justify-center",
102
102
  "aria-label": "Agent actions menu",
103
- children: /* @__PURE__ */ m(s, { className: "w-4 h-4 text-text-primary opacity-70 hover:opacity-100" })
104
- }), /* @__PURE__ */ m(f, { children: j && /* @__PURE__ */ h(p.div, {
103
+ children: /* @__PURE__ */ h(s, { className: "w-4 h-4 text-text-primary opacity-70 hover:opacity-100" })
104
+ }), /* @__PURE__ */ h(p, { children: M && /* @__PURE__ */ g(m.div, {
105
105
  initial: {
106
106
  opacity: 0,
107
107
  scale: .95,
@@ -120,93 +120,102 @@ var g = ({ agent: g, onSelect: _, onConfigure: v, onSettings: y, onStart: b, onS
120
120
  transition: { duration: .15 },
121
121
  className: "absolute right-0 mt-1 w-40 bg-bg-surface border border-border-default rounded-lg shadow-lg z-10",
122
122
  children: [
123
- N === "ACTIVE" && x && /* @__PURE__ */ h(p.button, {
123
+ P === "ACTIVE" && S && /* @__PURE__ */ g(m.button, {
124
+ whileTap: { scale: .97 },
125
+ onClick: (e) => {
126
+ e.stopPropagation(), S(_), N(!1);
127
+ },
128
+ className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken rounded-t-lg",
129
+ children: [/* @__PURE__ */ h(f, { className: "w-4 h-4" }), A("agentsPage.stopAgent", "Stop Agent")]
130
+ }),
131
+ (P === "STOPPED" || P === "OFFLINE") && x && /* @__PURE__ */ g(m.button, {
124
132
  whileTap: { scale: .97 },
125
133
  onClick: (e) => {
126
- e.stopPropagation(), x(g), M(!1);
134
+ e.stopPropagation(), x(_), N(!1);
127
135
  },
128
136
  className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken rounded-t-lg",
129
- children: [/* @__PURE__ */ m(d, { className: "w-4 h-4" }), k("agentsPage.stopAgent", "Stop Agent")]
137
+ children: [/* @__PURE__ */ h(c, { className: "w-4 h-4" }), A("agentsPage.startAgent", "Start Agent")]
130
138
  }),
131
- (N === "STOPPED" || N === "OFFLINE") && b && /* @__PURE__ */ h(p.button, {
139
+ P === "DEGRADED" && x && /* @__PURE__ */ g(m.button, {
132
140
  whileTap: { scale: .97 },
133
141
  onClick: (e) => {
134
- e.stopPropagation(), b(g), M(!1);
142
+ e.stopPropagation(), x(_), N(!1);
135
143
  },
136
144
  className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken rounded-t-lg",
137
- children: [/* @__PURE__ */ m(c, { className: "w-4 h-4" }), k("agentsPage.startAgent", "Start Agent")]
145
+ title: O ? `Retry lifecycle/start last error: ${O}` : "Retry lifecycle/start",
146
+ children: [/* @__PURE__ */ h(l, { className: "w-4 h-4" }), A("agentsPage.reconnectAgent", "Reconnect")]
138
147
  }),
139
- (N === "ACTIVE" || N === "STOPPED" || N === "OFFLINE") && S && /* @__PURE__ */ h(p.button, {
148
+ (P === "ACTIVE" || P === "STOPPED" || P === "OFFLINE" || P === "DEGRADED") && C && /* @__PURE__ */ g(m.button, {
140
149
  whileTap: { scale: .97 },
141
150
  onClick: (e) => {
142
- e.stopPropagation(), S(g), M(!1);
151
+ e.stopPropagation(), C(_), N(!1);
143
152
  },
144
153
  className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-action-danger-text hover:bg-bg-sunken",
145
- children: [/* @__PURE__ */ m(d, { className: "w-4 h-4" }), N === "OFFLINE" ? "Clear Connection" : "Disconnect"]
154
+ children: [/* @__PURE__ */ h(f, { className: "w-4 h-4" }), P === "OFFLINE" ? "Clear Connection" : "Disconnect"]
146
155
  }),
147
- C && /* @__PURE__ */ h(p.button, {
156
+ w && /* @__PURE__ */ g(m.button, {
148
157
  whileTap: { scale: .97 },
149
158
  onClick: (e) => {
150
- e.stopPropagation(), C(g), M(!1);
159
+ e.stopPropagation(), w(_), N(!1);
151
160
  },
152
161
  className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken",
153
- children: [/* @__PURE__ */ m(o, { className: "w-4 h-4" }), "Edit Tunnel"]
162
+ children: [/* @__PURE__ */ h(o, { className: "w-4 h-4" }), "Edit Tunnel"]
154
163
  }),
155
- A && /* @__PURE__ */ h(p.button, {
164
+ j && /* @__PURE__ */ g(m.button, {
156
165
  whileTap: { scale: .97 },
157
166
  onClick: (e) => {
158
- e.stopPropagation(), A(g), M(!1);
167
+ e.stopPropagation(), j(_), N(!1);
159
168
  },
160
169
  className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken rounded-b-lg",
161
- children: [/* @__PURE__ */ m(u, { className: "w-4 h-4" }), "Settings"]
170
+ children: [/* @__PURE__ */ h(d, { className: "w-4 h-4" }), "Settings"]
162
171
  })
163
172
  ]
164
173
  }) })]
165
174
  })]
166
175
  })]
167
176
  }),
168
- /* @__PURE__ */ h("div", {
177
+ /* @__PURE__ */ g("div", {
169
178
  className: "mt-4 grid grid-cols-2 gap-3 text-sm",
170
- children: [/* @__PURE__ */ h("div", {
179
+ children: [/* @__PURE__ */ g("div", {
171
180
  className: "flex items-center gap-2 text-text-secondary",
172
- children: [/* @__PURE__ */ m(r, { className: "w-4 h-4" }), /* @__PURE__ */ m("span", { children: P(g.lastHeartbeat) })]
173
- }), g.architecture && /* @__PURE__ */ h("div", {
181
+ children: [/* @__PURE__ */ h(r, { className: "w-4 h-4" }), /* @__PURE__ */ h("span", { children: F(_.lastHeartbeat) })]
182
+ }), _.architecture && /* @__PURE__ */ g("div", {
174
183
  className: "flex items-center gap-2 text-text-secondary",
175
- children: [/* @__PURE__ */ m(i, { className: "w-4 h-4" }), /* @__PURE__ */ m("span", { children: g.architecture })]
184
+ children: [/* @__PURE__ */ h(i, { className: "w-4 h-4" }), /* @__PURE__ */ h("span", { children: _.architecture })]
176
185
  })]
177
186
  }),
178
- g.tunnelUrl && /* @__PURE__ */ h("div", {
187
+ _.tunnelUrl && /* @__PURE__ */ g("div", {
179
188
  className: "mt-3 flex items-center gap-2 text-sm",
180
189
  children: [
181
- /* @__PURE__ */ m(a, { className: "w-4 h-4 flex-shrink-0 text-text-secondary" }),
182
- /* @__PURE__ */ m("span", {
190
+ /* @__PURE__ */ h(a, { className: "w-4 h-4 flex-shrink-0 text-text-secondary" }),
191
+ /* @__PURE__ */ h("span", {
183
192
  className: "text-text-secondary flex-shrink-0",
184
193
  children: "Tunnel:"
185
194
  }),
186
- /* @__PURE__ */ m("a", {
187
- href: g.tunnelUrl,
195
+ /* @__PURE__ */ h("a", {
196
+ href: _.tunnelUrl,
188
197
  target: "_blank",
189
198
  rel: "noopener noreferrer",
190
199
  onClick: (e) => e.stopPropagation(),
191
200
  className: "text-action-primary-text hover:underline truncate",
192
- children: g.tunnelUrl
201
+ children: _.tunnelUrl
193
202
  })
194
203
  ]
195
204
  }),
196
- g.capabilities && g.capabilities.length > 0 && /* @__PURE__ */ h("div", {
205
+ _.capabilities && _.capabilities.length > 0 && /* @__PURE__ */ g("div", {
197
206
  className: "mt-3 flex flex-wrap gap-1",
198
- children: [g.capabilities.slice(0, 4).map((e) => /* @__PURE__ */ m("span", {
207
+ children: [_.capabilities.slice(0, 4).map((e) => /* @__PURE__ */ h("span", {
199
208
  className: "px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded",
200
209
  children: e
201
- }, e)), g.capabilities.length > 4 && /* @__PURE__ */ h("span", {
210
+ }, e)), _.capabilities.length > 4 && /* @__PURE__ */ g("span", {
202
211
  className: "px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded",
203
- children: ["+", g.capabilities.length - 4]
212
+ children: ["+", _.capabilities.length - 4]
204
213
  })]
205
214
  })
206
215
  ]
207
216
  });
208
217
  };
209
218
  //#endregion
210
- export { g as AgentCard };
219
+ export { _ as AgentCard };
211
220
 
212
221
  //# sourceMappingURL=AgentCard.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"AgentCard.js","names":[],"sources":["../../../src/components/agents/AgentCard.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport {\n Server,\n Clock,\n Cpu,\n MoreVertical,\n Play,\n Square,\n Settings,\n Globe,\n Link,\n} from 'lucide-react';\nimport { AgentStatusBadge } from './AgentStatusBadge';\nimport type { Agent, AgentStatus } from './types';\n\nexport interface AgentCardProps {\n agent: Agent;\n onSelect?: (agent: Agent) => void;\n onConfigure?: (agent: Agent) => void;\n onSettings?: (agent: Agent) => void;\n onStart?: (agent: Agent) => void;\n onStop?: (agent: Agent) => void;\n onDisconnect?: (agent: Agent) => void;\n onEditTunnelUrl?: (agent: Agent) => void;\n selected?: boolean;\n onToggleSelect?: () => void;\n className?: string;\n /**\n * Last error from /health/ready or the live probe — shown in the badge\n * tooltip when the agent is degraded so users know WHY without curling\n * the tunnel themselves.\n */\n healthErrorMessage?: string | null;\n}\n\n/**\n * AgentCard - Display agent information in a card format\n *\n * @example\n * ```tsx\n * <AgentCard\n * agent={myAgent}\n * onSelect={handleSelect}\n * onConfigure={handleConfigure}\n * />\n * ```\n */\nexport const AgentCard: FC<AgentCardProps> = ({\n agent,\n onSelect,\n onConfigure,\n onSettings,\n onStart,\n onStop,\n onDisconnect,\n onEditTunnelUrl,\n selected = false,\n onToggleSelect,\n className = '',\n healthErrorMessage,\n}) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string) => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n // Support both onConfigure and onSettings\n const handleConfigure = onConfigure || onSettings;\n const [showMenu, setShowMenu] = useState(false);\n\n const getStatus = (): AgentStatus => {\n if (agent.status) return agent.status;\n if (!agent.isActive) return 'OFFLINE';\n\n if (agent.lastHeartbeat) {\n const lastHeartbeatTime = new Date(agent.lastHeartbeat).getTime();\n const now = Date.now();\n const diff = now - lastHeartbeatTime;\n\n // If heartbeat is older than 5 minutes, consider it inactive\n if (diff > 5 * 60 * 1000) return 'DEGRADED';\n }\n\n return 'ACTIVE';\n };\n\n const status = getStatus();\n\n const formatLastHeartbeat = (timestamp: string | null | undefined) => {\n if (!timestamp) return tr('agentsPage.never', 'Never');\n const diff = Date.now() - new Date(timestamp).getTime();\n if (diff < 60000) return tr('agentsPage.justNow', 'Just now');\n if (diff < 3600000) return `${Math.floor(diff / 60000)}m ago`;\n if (diff < 86400000) return `${Math.floor(diff / 3600000)}h ago`;\n return `${Math.floor(diff / 86400000)}d ago`;\n };\n\n const platformIcon =\n {\n darwin: '🍎',\n linux: '🐧',\n windows: '🪟',\n }[agent.platform || ''] || '💻';\n\n return (\n <motion.div\n whileHover={{ y: -2, transition: { duration: 0.15, ease: [0.4, 0, 0.2, 1] } }}\n whileTap={{ scale: 0.98 }}\n className={`bg-bg-surface border rounded-lg p-4 h-full hover:border-border-strong hover:shadow-md transition-all cursor-pointer ${\n selected\n ? 'border-action-primary-bg ring-1 ring-action-primary-bg/30'\n : 'border-border-default'\n } ${className}`}\n onClick={() => onSelect?.(agent)}\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex items-start gap-2 sm:gap-3 min-w-0 flex-1\">\n {/* Selection Checkbox */}\n {onToggleSelect && (\n <button\n onClick={(e) => {\n e.stopPropagation();\n onToggleSelect();\n }}\n className={`flex-shrink-0 w-5 h-5 min-w-[1.25rem] min-h-[1.25rem] rounded border transition-colors mt-0.5 ${\n selected\n ? 'bg-action-primary-bg border-action-primary-bg text-action-primary-text'\n : 'border-border-default hover:border-border-strong bg-bg-surface'\n } flex items-center justify-center`}\n >\n {selected && (\n <svg\n className=\"w-3 h-3\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n strokeWidth={3}\n >\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M5 13l4 4L19 7\" />\n </svg>\n )}\n </button>\n )}\n <div className=\"flex-shrink-0 w-8 h-8 sm:w-10 sm:h-10 rounded-lg bg-bg-sunken flex items-center justify-center\">\n <Server className=\"w-4 h-4 sm:w-5 sm:h-5 text-text-secondary\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center gap-1.5 sm:gap-2 min-w-0\">\n <h3 className=\"font-semibold text-text-primary truncate min-w-0 max-w-[120px] sm:max-w-none text-sm sm:text-base\">\n {agent.name}\n </h3>\n <span className=\"text-base sm:text-lg flex-shrink-0\">{platformIcon}</span>\n </div>\n <p className=\"text-xs sm:text-sm text-text-secondary truncate\">\n {agent.hostname || `ID: ${agent.id}`}\n </p>\n </div>\n </div>\n {/* Status and Actions - always visible */}\n <div className=\"flex items-center gap-1 sm:gap-2 flex-shrink-0\">\n <AgentStatusBadge status={status} reason={healthErrorMessage ?? null} />\n {(handleConfigure || onStart || onStop) && (\n <div className=\"relative\">\n <button\n onClick={(e) => {\n e.stopPropagation();\n setShowMenu(!showMenu);\n }}\n className=\"p-1 sm:p-1.5 rounded hover:bg-bg-sunken transition-colors flex items-center justify-center\"\n aria-label=\"Agent actions menu\"\n >\n <MoreVertical className=\"w-4 h-4 text-text-primary opacity-70 hover:opacity-100\" />\n </button>\n <AnimatePresence>\n {showMenu && (\n <motion.div\n initial={{ opacity: 0, scale: 0.95, y: -4 }}\n animate={{ opacity: 1, scale: 1, y: 0 }}\n exit={{ opacity: 0, scale: 0.95, y: -4 }}\n transition={{ duration: 0.15 }}\n className=\"absolute right-0 mt-1 w-40 bg-bg-surface border border-border-default rounded-lg shadow-lg z-10\"\n >\n {status === 'ACTIVE' && onStop && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onStop(agent);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken rounded-t-lg\"\n >\n <Square className=\"w-4 h-4\" />\n {tr('agentsPage.stopAgent', 'Stop Agent')}\n </motion.button>\n )}\n {(status === 'STOPPED' || status === 'OFFLINE') && onStart && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onStart(agent);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken rounded-t-lg\"\n >\n <Play className=\"w-4 h-4\" />\n {tr('agentsPage.startAgent', 'Start Agent')}\n </motion.button>\n )}\n {(status === 'ACTIVE' || status === 'STOPPED' || status === 'OFFLINE') &&\n onDisconnect && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onDisconnect(agent);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-action-danger-text hover:bg-bg-sunken\"\n >\n <Square className=\"w-4 h-4\" />\n {status === 'OFFLINE' ? 'Clear Connection' : 'Disconnect'}\n </motion.button>\n )}\n {onEditTunnelUrl && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onEditTunnelUrl(agent);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Link className=\"w-4 h-4\" />\n Edit Tunnel\n </motion.button>\n )}\n {handleConfigure && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n handleConfigure(agent);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken rounded-b-lg\"\n >\n <Settings className=\"w-4 h-4\" />\n Settings\n </motion.button>\n )}\n </motion.div>\n )}\n </AnimatePresence>\n </div>\n )}\n </div>\n </div>\n\n <div className=\"mt-4 grid grid-cols-2 gap-3 text-sm\">\n <div className=\"flex items-center gap-2 text-text-secondary\">\n <Clock className=\"w-4 h-4\" />\n <span>{formatLastHeartbeat(agent.lastHeartbeat)}</span>\n </div>\n {agent.architecture && (\n <div className=\"flex items-center gap-2 text-text-secondary\">\n <Cpu className=\"w-4 h-4\" />\n <span>{agent.architecture}</span>\n </div>\n )}\n </div>\n\n {agent.tunnelUrl && (\n // Plain \"Tunnel: <link>\" row. The colored status chip that used\n // to sit here was a redundant second status surface — the\n // canonical state lives in the <AgentStatusBadge> top-right.\n // The Globe glyph stays as a neutral icon (no status color).\n <div className=\"mt-3 flex items-center gap-2 text-sm\">\n <Globe className=\"w-4 h-4 flex-shrink-0 text-text-secondary\" />\n <span className=\"text-text-secondary flex-shrink-0\">Tunnel:</span>\n <a\n href={agent.tunnelUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n onClick={(e) => e.stopPropagation()}\n className=\"text-action-primary-text hover:underline truncate\"\n >\n {agent.tunnelUrl}\n </a>\n </div>\n )}\n\n {agent.capabilities && agent.capabilities.length > 0 && (\n <div className=\"mt-3 flex flex-wrap gap-1\">\n {agent.capabilities.slice(0, 4).map((cap) => (\n <span\n key={cap}\n className=\"px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded\"\n >\n {cap}\n </span>\n ))}\n {agent.capabilities.length > 4 && (\n <span className=\"px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded\">\n +{agent.capabilities.length - 4}\n </span>\n )}\n </div>\n )}\n </motion.div>\n );\n};\n"],"mappings":";;;;;;;AAkDA,IAAa,KAAiC,EAC5C,UACA,aACA,gBACA,eACA,YACA,WACA,iBACA,oBACA,cAAW,IACX,mBACA,eAAY,IACZ,4BACI;CACJ,IAAM,EAAE,SAAM,GAAS,EACjB,KAAM,GAAa,MAAqB;EAC5C,IAAM,IAAI,EAAE,EAAI;AAChB,SAAO,MAAM,IAAM,IAAW;IAG1B,IAAkB,KAAe,GACjC,CAAC,GAAU,KAAe,EAAS,GAAM,EAkBzC,WAhB+B;AACnC,MAAI,EAAM,OAAQ,QAAO,EAAM;AAC/B,MAAI,CAAC,EAAM,SAAU,QAAO;AAE5B,MAAI,EAAM,eAAe;GACvB,IAAM,IAAoB,IAAI,KAAK,EAAM,cAAc,CAAC,SAAS;AAKjE,OAJY,KAAK,KAAK,GACH,IAGR,MAAS,IAAM,QAAO;;AAGnC,SAAO;KAGiB,EAEpB,KAAuB,MAAyC;AACpE,MAAI,CAAC,EAAW,QAAO,EAAG,oBAAoB,QAAQ;EACtD,IAAM,IAAO,KAAK,KAAK,GAAG,IAAI,KAAK,EAAU,CAAC,SAAS;AAIvD,SAHI,IAAO,MAAc,EAAG,sBAAsB,WAAW,GACzD,IAAO,OAAgB,GAAG,KAAK,MAAM,IAAO,IAAM,CAAC,SACnD,IAAO,QAAiB,GAAG,KAAK,MAAM,IAAO,KAAQ,CAAC,SACnD,GAAG,KAAK,MAAM,IAAO,MAAS,CAAC;IAGlC,IACJ;EACE,QAAQ;EACR,OAAO;EACP,SAAS;EACV,CAAC,EAAM,YAAY,OAAO;AAE7B,QACE,kBAAC,EAAO,KAAR;EACE,YAAY;GAAE,GAAG;GAAI,YAAY;IAAE,UAAU;IAAM,MAAM;KAAC;KAAK;KAAG;KAAK;KAAE;IAAE;GAAE;EAC7E,UAAU,EAAE,OAAO,KAAM;EACzB,WAAW,uHACT,IACI,8DACA,wBACL,GAAG;EACJ,eAAe,IAAW,EAAM;YARlC;GAUE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf;MAEG,KACC,kBAAC,UAAD;OACE,UAAU,MAAM;AAEd,QADA,EAAE,iBAAiB,EACnB,GAAgB;;OAElB,WAAW,iGACT,IACI,2EACA,iEACL;iBAEA,KACC,kBAAC,OAAD;QACE,WAAU;QACV,MAAK;QACL,SAAQ;QACR,QAAO;QACP,aAAa;kBAEb,kBAAC,QAAD;SAAM,eAAc;SAAQ,gBAAe;SAAQ,GAAE;SAAmB,CAAA;QACpE,CAAA;OAED,CAAA;MAEX,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,GAAD,EAAQ,WAAU,6CAA8C,CAAA;OAC5D,CAAA;MACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,MAAD;SAAI,WAAU;mBACX,EAAM;SACJ,CAAA,EACL,kBAAC,QAAD;SAAM,WAAU;mBAAsC;SAAoB,CAAA,CACtE;WACN,kBAAC,KAAD;QAAG,WAAU;kBACV,EAAM,YAAY,OAAO,EAAM;QAC9B,CAAA,CACA;;MACF;QAEN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD;MAA0B;MAAQ,QAAQ,KAAsB;MAAQ,CAAA,GACtE,KAAmB,KAAW,MAC9B,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,UAAD;OACE,UAAU,MAAM;AAEd,QADA,EAAE,iBAAiB,EACnB,EAAY,CAAC,EAAS;;OAExB,WAAU;OACV,cAAW;iBAEX,kBAAC,GAAD,EAAc,WAAU,0DAA2D,CAAA;OAC5E,CAAA,EACT,kBAAC,GAAD,EAAA,UACG,KACC,kBAAC,EAAO,KAAR;OACE,SAAS;QAAE,SAAS;QAAG,OAAO;QAAM,GAAG;QAAI;OAC3C,SAAS;QAAE,SAAS;QAAG,OAAO;QAAG,GAAG;QAAG;OACvC,MAAM;QAAE,SAAS;QAAG,OAAO;QAAM,GAAG;QAAI;OACxC,YAAY,EAAE,UAAU,KAAM;OAC9B,WAAU;iBALZ;QAOG,MAAW,YAAY,KACtB,kBAAC,EAAO,QAAR;SACE,UAAU,EAAE,OAAO,KAAM;SACzB,UAAU,MAAM;AAGd,UAFA,EAAE,iBAAiB,EACnB,EAAO,EAAM,EACb,EAAY,GAAM;;SAEpB,WAAU;mBAPZ,CASE,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA,EAC7B,EAAG,wBAAwB,aAAa,CAC3B;;SAEhB,MAAW,aAAa,MAAW,cAAc,KACjD,kBAAC,EAAO,QAAR;SACE,UAAU,EAAE,OAAO,KAAM;SACzB,UAAU,MAAM;AAGd,UAFA,EAAE,iBAAiB,EACnB,EAAQ,EAAM,EACd,EAAY,GAAM;;SAEpB,WAAU;mBAPZ,CASE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAC3B,EAAG,yBAAyB,cAAc,CAC7B;;SAEhB,MAAW,YAAY,MAAW,aAAa,MAAW,cAC1D,KACE,kBAAC,EAAO,QAAR;SACE,UAAU,EAAE,OAAO,KAAM;SACzB,UAAU,MAAM;AAGd,UAFA,EAAE,iBAAiB,EACnB,EAAa,EAAM,EACnB,EAAY,GAAM;;SAEpB,WAAU;mBAPZ,CASE,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA,EAC7B,MAAW,YAAY,qBAAqB,aAC/B;;QAEnB,KACC,kBAAC,EAAO,QAAR;SACE,UAAU,EAAE,OAAO,KAAM;SACzB,UAAU,MAAM;AAGd,UAFA,EAAE,iBAAiB,EACnB,EAAgB,EAAM,EACtB,EAAY,GAAM;;SAEpB,WAAU;mBAPZ,CASE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAAA,cAEd;;QAEjB,KACC,kBAAC,EAAO,QAAR;SACE,UAAU,EAAE,OAAO,KAAM;SACzB,UAAU,MAAM;AAGd,UAFA,EAAE,iBAAiB,EACnB,EAAgB,EAAM,EACtB,EAAY,GAAM;;SAEpB,WAAU;mBAPZ,CASE,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA,EAAA,WAElB;;QAEP;UAEC,CAAA,CACd;QAEJ;OACF;;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD,EAAO,WAAU,WAAY,CAAA,EAC7B,kBAAC,QAAD,EAAA,UAAO,EAAoB,EAAM,cAAc,EAAQ,CAAA,CACnD;QACL,EAAM,gBACL,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD,EAAK,WAAU,WAAY,CAAA,EAC3B,kBAAC,QAAD,EAAA,UAAO,EAAM,cAAoB,CAAA,CAC7B;OAEJ;;GAEL,EAAM,aAKL,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD,EAAO,WAAU,6CAA8C,CAAA;KAC/D,kBAAC,QAAD;MAAM,WAAU;gBAAoC;MAAc,CAAA;KAClE,kBAAC,KAAD;MACE,MAAM,EAAM;MACZ,QAAO;MACP,KAAI;MACJ,UAAU,MAAM,EAAE,iBAAiB;MACnC,WAAU;gBAET,EAAM;MACL,CAAA;KACA;;GAGP,EAAM,gBAAgB,EAAM,aAAa,SAAS,KACjD,kBAAC,OAAD;IAAK,WAAU;cAAf,CACG,EAAM,aAAa,MAAM,GAAG,EAAE,CAAC,KAAK,MACnC,kBAAC,QAAD;KAEE,WAAU;eAET;KACI,EAJA,EAIA,CACP,EACD,EAAM,aAAa,SAAS,KAC3B,kBAAC,QAAD;KAAM,WAAU;eAAhB,CAA+E,KAC3E,EAAM,aAAa,SAAS,EACzB;OAEL;;GAEG"}
1
+ {"version":3,"file":"AgentCard.js","names":[],"sources":["../../../src/components/agents/AgentCard.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport {\n Server,\n Clock,\n Cpu,\n MoreVertical,\n Play,\n Square,\n Settings,\n Globe,\n Link,\n RefreshCw,\n} from 'lucide-react';\nimport { AgentStatusBadge } from './AgentStatusBadge';\nimport type { Agent, AgentStatus } from './types';\n\nexport interface AgentCardProps {\n agent: Agent;\n onSelect?: (agent: Agent) => void;\n onConfigure?: (agent: Agent) => void;\n onSettings?: (agent: Agent) => void;\n onStart?: (agent: Agent) => void;\n onStop?: (agent: Agent) => void;\n onDisconnect?: (agent: Agent) => void;\n onEditTunnelUrl?: (agent: Agent) => void;\n selected?: boolean;\n onToggleSelect?: () => void;\n className?: string;\n /**\n * Last error from /health/ready or the live probe — shown in the badge\n * tooltip when the agent is degraded so users know WHY without curling\n * the tunnel themselves.\n */\n healthErrorMessage?: string | null;\n}\n\n/**\n * AgentCard - Display agent information in a card format\n *\n * @example\n * ```tsx\n * <AgentCard\n * agent={myAgent}\n * onSelect={handleSelect}\n * onConfigure={handleConfigure}\n * />\n * ```\n */\nexport const AgentCard: FC<AgentCardProps> = ({\n agent,\n onSelect,\n onConfigure,\n onSettings,\n onStart,\n onStop,\n onDisconnect,\n onEditTunnelUrl,\n selected = false,\n onToggleSelect,\n className = '',\n healthErrorMessage,\n}) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string) => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n // Support both onConfigure and onSettings\n const handleConfigure = onConfigure || onSettings;\n const [showMenu, setShowMenu] = useState(false);\n\n const getStatus = (): AgentStatus => {\n if (agent.status) return agent.status;\n if (!agent.isActive) return 'OFFLINE';\n\n if (agent.lastHeartbeat) {\n const lastHeartbeatTime = new Date(agent.lastHeartbeat).getTime();\n const now = Date.now();\n const diff = now - lastHeartbeatTime;\n\n // If heartbeat is older than 5 minutes, consider it inactive\n if (diff > 5 * 60 * 1000) return 'DEGRADED';\n }\n\n return 'ACTIVE';\n };\n\n const status = getStatus();\n\n const formatLastHeartbeat = (timestamp: string | null | undefined) => {\n if (!timestamp) return tr('agentsPage.never', 'Never');\n const diff = Date.now() - new Date(timestamp).getTime();\n if (diff < 60000) return tr('agentsPage.justNow', 'Just now');\n if (diff < 3600000) return `${Math.floor(diff / 60000)}m ago`;\n if (diff < 86400000) return `${Math.floor(diff / 3600000)}h ago`;\n return `${Math.floor(diff / 86400000)}d ago`;\n };\n\n const platformIcon =\n {\n darwin: '🍎',\n linux: '🐧',\n windows: '🪟',\n }[agent.platform || ''] || '💻';\n\n return (\n <motion.div\n whileHover={{ y: -2, transition: { duration: 0.15, ease: [0.4, 0, 0.2, 1] } }}\n whileTap={{ scale: 0.98 }}\n className={`bg-bg-surface border rounded-lg p-4 h-full hover:border-border-strong hover:shadow-md transition-all cursor-pointer ${\n selected\n ? 'border-action-primary-bg ring-1 ring-action-primary-bg/30'\n : 'border-border-default'\n } ${className}`}\n onClick={() => onSelect?.(agent)}\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex items-start gap-2 sm:gap-3 min-w-0 flex-1\">\n {/* Selection Checkbox */}\n {onToggleSelect && (\n <button\n onClick={(e) => {\n e.stopPropagation();\n onToggleSelect();\n }}\n className={`flex-shrink-0 w-5 h-5 min-w-[1.25rem] min-h-[1.25rem] rounded border transition-colors mt-0.5 ${\n selected\n ? 'bg-action-primary-bg border-action-primary-bg text-action-primary-text'\n : 'border-border-default hover:border-border-strong bg-bg-surface'\n } flex items-center justify-center`}\n >\n {selected && (\n <svg\n className=\"w-3 h-3\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n strokeWidth={3}\n >\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M5 13l4 4L19 7\" />\n </svg>\n )}\n </button>\n )}\n <div className=\"flex-shrink-0 w-8 h-8 sm:w-10 sm:h-10 rounded-lg bg-bg-sunken flex items-center justify-center\">\n <Server className=\"w-4 h-4 sm:w-5 sm:h-5 text-text-secondary\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center gap-1.5 sm:gap-2 min-w-0\">\n <h3 className=\"font-semibold text-text-primary truncate min-w-0 max-w-[120px] sm:max-w-none text-sm sm:text-base\">\n {agent.name}\n </h3>\n <span className=\"text-base sm:text-lg flex-shrink-0\">{platformIcon}</span>\n </div>\n <p className=\"text-xs sm:text-sm text-text-secondary truncate\">\n {agent.hostname || `ID: ${agent.id}`}\n </p>\n </div>\n </div>\n {/* Status and Actions - always visible */}\n <div className=\"flex items-center gap-1 sm:gap-2 flex-shrink-0\">\n <AgentStatusBadge status={status} reason={healthErrorMessage ?? null} />\n {(handleConfigure || onStart || onStop) && (\n <div className=\"relative\">\n <button\n onClick={(e) => {\n e.stopPropagation();\n setShowMenu(!showMenu);\n }}\n className=\"p-1 sm:p-1.5 rounded hover:bg-bg-sunken transition-colors flex items-center justify-center\"\n aria-label=\"Agent actions menu\"\n >\n <MoreVertical className=\"w-4 h-4 text-text-primary opacity-70 hover:opacity-100\" />\n </button>\n <AnimatePresence>\n {showMenu && (\n <motion.div\n initial={{ opacity: 0, scale: 0.95, y: -4 }}\n animate={{ opacity: 1, scale: 1, y: 0 }}\n exit={{ opacity: 0, scale: 0.95, y: -4 }}\n transition={{ duration: 0.15 }}\n className=\"absolute right-0 mt-1 w-40 bg-bg-surface border border-border-default rounded-lg shadow-lg z-10\"\n >\n {status === 'ACTIVE' && onStop && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onStop(agent);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken rounded-t-lg\"\n >\n <Square className=\"w-4 h-4\" />\n {tr('agentsPage.stopAgent', 'Stop Agent')}\n </motion.button>\n )}\n {(status === 'STOPPED' || status === 'OFFLINE') && onStart && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onStart(agent);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken rounded-t-lg\"\n >\n <Play className=\"w-4 h-4\" />\n {tr('agentsPage.startAgent', 'Start Agent')}\n </motion.button>\n )}\n {status === 'DEGRADED' && onStart && (\n // Reconnect on a degraded agent fires the same\n // startVibecontrolsAgent mutation as a fresh start —\n // svc-side that POSTs lifecycle/start to the agent's\n // tunnel, which usually clears transient failures\n // (tunnel hiccup, finalize retry). Labelled\n // \"Reconnect\" not \"Start\" so the user knows it's a\n // recovery action, not creating a new instance.\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onStart(agent);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken rounded-t-lg\"\n title={\n healthErrorMessage\n ? `Retry lifecycle/start — last error: ${healthErrorMessage}`\n : 'Retry lifecycle/start'\n }\n >\n <RefreshCw className=\"w-4 h-4\" />\n {tr('agentsPage.reconnectAgent', 'Reconnect')}\n </motion.button>\n )}\n {(status === 'ACTIVE' ||\n status === 'STOPPED' ||\n status === 'OFFLINE' ||\n status === 'DEGRADED') &&\n onDisconnect && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onDisconnect(agent);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-action-danger-text hover:bg-bg-sunken\"\n >\n <Square className=\"w-4 h-4\" />\n {status === 'OFFLINE' ? 'Clear Connection' : 'Disconnect'}\n </motion.button>\n )}\n {onEditTunnelUrl && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onEditTunnelUrl(agent);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Link className=\"w-4 h-4\" />\n Edit Tunnel\n </motion.button>\n )}\n {handleConfigure && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n handleConfigure(agent);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken rounded-b-lg\"\n >\n <Settings className=\"w-4 h-4\" />\n Settings\n </motion.button>\n )}\n </motion.div>\n )}\n </AnimatePresence>\n </div>\n )}\n </div>\n </div>\n\n <div className=\"mt-4 grid grid-cols-2 gap-3 text-sm\">\n <div className=\"flex items-center gap-2 text-text-secondary\">\n <Clock className=\"w-4 h-4\" />\n <span>{formatLastHeartbeat(agent.lastHeartbeat)}</span>\n </div>\n {agent.architecture && (\n <div className=\"flex items-center gap-2 text-text-secondary\">\n <Cpu className=\"w-4 h-4\" />\n <span>{agent.architecture}</span>\n </div>\n )}\n </div>\n\n {agent.tunnelUrl && (\n // Plain \"Tunnel: <link>\" row. The colored status chip that used\n // to sit here was a redundant second status surface — the\n // canonical state lives in the <AgentStatusBadge> top-right.\n // The Globe glyph stays as a neutral icon (no status color).\n <div className=\"mt-3 flex items-center gap-2 text-sm\">\n <Globe className=\"w-4 h-4 flex-shrink-0 text-text-secondary\" />\n <span className=\"text-text-secondary flex-shrink-0\">Tunnel:</span>\n <a\n href={agent.tunnelUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n onClick={(e) => e.stopPropagation()}\n className=\"text-action-primary-text hover:underline truncate\"\n >\n {agent.tunnelUrl}\n </a>\n </div>\n )}\n\n {agent.capabilities && agent.capabilities.length > 0 && (\n <div className=\"mt-3 flex flex-wrap gap-1\">\n {agent.capabilities.slice(0, 4).map((cap) => (\n <span\n key={cap}\n className=\"px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded\"\n >\n {cap}\n </span>\n ))}\n {agent.capabilities.length > 4 && (\n <span className=\"px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded\">\n +{agent.capabilities.length - 4}\n </span>\n )}\n </div>\n )}\n </motion.div>\n );\n};\n"],"mappings":"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"}