@burdenoff/microfe-vibecontrols 2026.527.4 → 2026.527.6

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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"}
@@ -30,7 +30,7 @@ function E({ agent: e, agentApiKey: t, tunnelUrl: n }) {
30
30
  let { t: u } = f(), m = s((e, t) => {
31
31
  let n = u(e);
32
32
  return n === e ? t : n;
33
- }, [u]), [h, _] = d(null), [y, b] = d(null), [x, S] = d(!0), [T, E] = d([]), [A, j] = d(!1), [M, N] = d(null), [P, F] = d(null), [I, L] = d(null), R = s(async (t = !1) => {
33
+ }, [u]), [h, _] = d(null), [y, b] = d(null), [x, S] = d(!0), [T, E] = d([]), [A, j] = d(!1), [M, N] = d(null), [P, F] = d(null), [I, L] = d(null), [R, z] = d(!1), B = s(async (t = !1) => {
34
34
  t || (S(!0), b(null));
35
35
  try {
36
36
  if (t) {
@@ -46,13 +46,13 @@ function E({ agent: e, agentApiKey: t, tunnelUrl: n }) {
46
46
  }
47
47
  }, [e]);
48
48
  c(() => {
49
- R();
50
- }, [R]);
51
- let z = s(async () => {
49
+ B();
50
+ }, [B]);
51
+ let V = s(async () => {
52
52
  j(!0), L(null), F(null);
53
53
  try {
54
54
  let t = await a(e);
55
- t.ok ? (F(m("plan.cliInstalled", "Plannotator CLI installed.")), await R()) : L(t.error ?? "Install failed");
55
+ t.ok ? (F(m("plan.cliInstalled", "Plannotator CLI installed.")), await B()) : L(t.error ?? "Install failed");
56
56
  } catch (e) {
57
57
  L(e instanceof Error ? e.message : "Install failed");
58
58
  } finally {
@@ -60,13 +60,13 @@ function E({ agent: e, agentApiKey: t, tunnelUrl: n }) {
60
60
  }
61
61
  }, [
62
62
  e,
63
- R,
63
+ B,
64
64
  m
65
- ]), B = s(async (t) => {
65
+ ]), H = s(async (t) => {
66
66
  N(t), L(null), F(null);
67
67
  try {
68
68
  let n = await r(e, t);
69
- n.ok ? (F(m("plan.hookConfigured", `Hook configured for ${t}.`)), await R()) : L(n.error ?? "Hook configure failed");
69
+ n.ok ? (F(m("plan.hookConfigured", `Hook configured for ${t}.`)), await B()) : L(n.error ?? "Hook configure failed");
70
70
  } catch (e) {
71
71
  L(e instanceof Error ? e.message : "Hook configure failed");
72
72
  } finally {
@@ -74,24 +74,25 @@ function E({ agent: e, agentApiKey: t, tunnelUrl: n }) {
74
74
  }
75
75
  }, [
76
76
  e,
77
- R,
77
+ B,
78
78
  m
79
- ]), V = l(() => x ? "loading" : y ? "status-error" : h?.installed ? T.some((e) => e.hookConfigured) ? "ready" : "wizard" : "install-cli", [
79
+ ]), U = l(() => x ? "loading" : y ? "status-error" : h?.installed ? !T.some((e) => e.hookConfigured) || R ? "wizard" : "ready" : "install-cli", [
80
80
  h,
81
81
  y,
82
82
  x,
83
- T
83
+ T,
84
+ R
84
85
  ]);
85
86
  return c(() => {
86
- if (V !== "ready") return;
87
+ if (U !== "ready") return;
87
88
  let e = setInterval(() => {
88
- document.hidden || R(!0);
89
+ document.hidden || B(!0);
89
90
  }, 5e3);
90
91
  return () => clearInterval(e);
91
- }, [V, R]), V === "loading" ? /* @__PURE__ */ w("div", {
92
+ }, [U, B]), U === "loading" ? /* @__PURE__ */ w("div", {
92
93
  className: "flex items-center gap-2 p-6 text-text-secondary",
93
94
  children: [/* @__PURE__ */ C(g, { className: "h-4 w-4 animate-spin" }), /* @__PURE__ */ C("span", { children: m("plan.loadingStatus", "Loading plan status…") })]
94
- }) : V === "status-error" ? /* @__PURE__ */ C("div", {
95
+ }) : U === "status-error" ? /* @__PURE__ */ C("div", {
95
96
  className: "rounded-md border border-status-error-border bg-status-error-bg p-4 text-sm text-status-error-text",
96
97
  children: /* @__PURE__ */ w("div", {
97
98
  className: "flex items-start gap-2",
@@ -107,7 +108,7 @@ function E({ agent: e, agentApiKey: t, tunnelUrl: n }) {
107
108
  children: y
108
109
  }),
109
110
  /* @__PURE__ */ w("button", {
110
- onClick: () => void R(),
111
+ onClick: () => void B(),
111
112
  className: "mt-3 inline-flex items-center gap-1 text-xs font-medium hover:underline",
112
113
  children: [
113
114
  /* @__PURE__ */ C(v, { className: "h-3 w-3" }),
@@ -131,24 +132,28 @@ function E({ agent: e, agentApiKey: t, tunnelUrl: n }) {
131
132
  className: "rounded-md border border-status-error-border bg-status-error-bg px-3 py-2 text-xs text-status-error-text",
132
133
  children: I
133
134
  }),
134
- V === "install-cli" && /* @__PURE__ */ C(D, {
135
+ U === "install-cli" && /* @__PURE__ */ C(D, {
135
136
  installing: A,
136
- onInstall: z,
137
+ onInstall: V,
137
138
  tr: m
138
139
  }),
139
- V === "wizard" && /* @__PURE__ */ C(O, {
140
+ U === "wizard" && /* @__PURE__ */ C(O, {
140
141
  agents: T,
141
142
  configuring: M,
142
- onConfigure: B,
143
+ onConfigure: H,
144
+ canExit: R && T.some((e) => e.hookConfigured),
145
+ onExit: () => z(!1),
143
146
  tr: m
144
147
  }),
145
- V === "ready" && /* @__PURE__ */ C(k, {
148
+ U === "ready" && /* @__PURE__ */ C(k, {
146
149
  agent: e,
147
150
  agents: T,
148
151
  agentApiKey: t,
149
152
  tunnelUrl: n,
150
153
  runningSessions: h?.runningSessions ?? [],
151
- onReconfigure: () => F(null),
154
+ onReconfigure: () => {
155
+ F(null), z(!0);
156
+ },
152
157
  tr: m
153
158
  })
154
159
  ]
@@ -181,8 +186,8 @@ function D({ installing: e, onInstall: t, tr: n }) {
181
186
  })
182
187
  });
183
188
  }
184
- function O({ agents: e, configuring: t, onConfigure: n, tr: r }) {
185
- let i = e.filter((e) => e.cliInstalled);
189
+ function O({ agents: e, configuring: t, onConfigure: n, canExit: r, onExit: i, tr: a }) {
190
+ let o = e.filter((e) => e.cliInstalled);
186
191
  return /* @__PURE__ */ C("div", {
187
192
  className: "rounded-lg border border-border-default bg-bg-elevated p-6",
188
193
  children: /* @__PURE__ */ w("div", {
@@ -190,17 +195,24 @@ function O({ agents: e, configuring: t, onConfigure: n, tr: r }) {
190
195
  children: [/* @__PURE__ */ C(y, { className: "h-6 w-6 text-action-primary-bg shrink-0" }), /* @__PURE__ */ w("div", {
191
196
  className: "flex-1",
192
197
  children: [
193
- /* @__PURE__ */ C("h3", {
194
- className: "text-base font-semibold text-text-primary",
195
- children: r("plan.wizardTitle", "Connect an AI coding agent")
198
+ /* @__PURE__ */ w("div", {
199
+ className: "flex items-center justify-between gap-3",
200
+ children: [/* @__PURE__ */ C("h3", {
201
+ className: "text-base font-semibold text-text-primary",
202
+ children: a("plan.wizardTitle", "Connect an AI coding agent")
203
+ }), r && i && /* @__PURE__ */ C("button", {
204
+ onClick: i,
205
+ className: "shrink-0 inline-flex items-center gap-1 text-xs text-text-secondary hover:text-text-primary",
206
+ children: a("plan.wizardDone", "Done")
207
+ })]
196
208
  }),
197
209
  /* @__PURE__ */ C("p", {
198
210
  className: "text-sm text-text-secondary mt-1",
199
- children: r("plan.wizardDesc", "Configure a plannotator hook for at least one installed AI coding agent. The hook forwards plan content from the agent to plannotator on every plan-finalisation step.")
211
+ children: a("plan.wizardDesc", "Configure a plannotator hook for at least one installed AI coding agent. The hook forwards plan content from the agent to plannotator on every plan-finalisation step.")
200
212
  }),
201
- i.length === 0 && /* @__PURE__ */ C("p", {
213
+ o.length === 0 && /* @__PURE__ */ C("p", {
202
214
  className: "mt-4 text-sm text-status-warning-text",
203
- children: r("plan.wizardNoAgents", "No AI coding agents detected in this sandbox. Install one (Claude Code, OpenCode, Codex, Pi, or Gemini) and refresh.")
215
+ children: a("plan.wizardNoAgents", "No AI coding agents detected in this sandbox. Install one (Claude Code, OpenCode, Codex, Pi, or Gemini) and refresh.")
204
216
  }),
205
217
  /* @__PURE__ */ C("ul", {
206
218
  className: "mt-4 space-y-2",
@@ -221,15 +233,15 @@ function O({ agents: e, configuring: t, onConfigure: n, tr: r }) {
221
233
  }),
222
234
  !e.cliInstalled && /* @__PURE__ */ C("p", {
223
235
  className: "text-xs text-status-warning-text mt-1",
224
- children: r("plan.agentCliMissing", "CLI not detected on this sandbox.")
236
+ children: a("plan.agentCliMissing", "CLI not detected on this sandbox.")
225
237
  })
226
238
  ]
227
239
  }),
228
240
  /* @__PURE__ */ C("button", {
229
- disabled: !e.cliInstalled || e.hookConfigured || t === e.agent,
241
+ disabled: !e.cliInstalled || t === e.agent,
230
242
  onClick: () => n(e.agent),
231
243
  className: "px-3 py-1.5 text-xs rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bgHover disabled:opacity-50",
232
- children: e.hookConfigured ? r("plan.hookConfiguredLabel", "Configured") : t === e.agent ? r("plan.configuring", "Configuring…") : r("plan.configureHook", "Configure hook")
244
+ children: t === e.agent ? a("plan.configuring", "Configuring…") : e.hookConfigured ? a("plan.reconfigureHook", "Reconfigure") : a("plan.configureHook", "Configure hook")
233
245
  })
234
246
  ]
235
247
  }, e.agent))