@opengeni/react 0.13.0 → 0.20.0

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.
Files changed (113) hide show
  1. package/README.md +129 -23
  2. package/dist/chunk-6BXBGJ3B.js +1956 -0
  3. package/dist/chunk-6BXBGJ3B.js.map +1 -0
  4. package/dist/chunk-6UT5OC3P.js +2133 -0
  5. package/dist/chunk-6UT5OC3P.js.map +1 -0
  6. package/dist/chunk-EBTT3MYN.js +92 -0
  7. package/dist/chunk-EBTT3MYN.js.map +1 -0
  8. package/dist/chunk-HHFA4AFX.js +425 -0
  9. package/dist/chunk-HHFA4AFX.js.map +1 -0
  10. package/dist/chunk-LAZ2A743.js +2393 -0
  11. package/dist/chunk-LAZ2A743.js.map +1 -0
  12. package/dist/chunk-MRSECXRP.js +563 -0
  13. package/dist/chunk-MRSECXRP.js.map +1 -0
  14. package/dist/chunk-TK7G6XLT.js +18 -0
  15. package/dist/chunk-TK7G6XLT.js.map +1 -0
  16. package/dist/composer-DvUaa5ki.d.ts +585 -0
  17. package/dist/composer.d.ts +6 -0
  18. package/dist/composer.js +56 -0
  19. package/dist/composer.js.map +1 -0
  20. package/dist/index.d.ts +642 -1080
  21. package/dist/index.js +7672 -8005
  22. package/dist/index.js.map +1 -1
  23. package/dist/machines.d.ts +412 -3
  24. package/dist/machines.js +25 -1
  25. package/dist/noto-sans-arabic-loader-IA2T4X2A.js +21 -0
  26. package/dist/noto-sans-arabic-loader-IA2T4X2A.js.map +1 -0
  27. package/dist/noto-sans-jp-loader.generated-HFGLYBR3.js +292 -0
  28. package/dist/noto-sans-jp-loader.generated-HFGLYBR3.js.map +1 -0
  29. package/dist/queue-surface-implementation-NQMV2ML3.js +890 -0
  30. package/dist/queue-surface-implementation-NQMV2ML3.js.map +1 -0
  31. package/dist/session-Gd4iVPYw.d.ts +425 -0
  32. package/dist/session-context-DIFFlXKc.d.ts +52 -0
  33. package/dist/session.d.ts +4 -0
  34. package/dist/session.js +50 -0
  35. package/dist/session.js.map +1 -0
  36. package/dist/use-composer-BCfm4mzX.d.ts +112 -0
  37. package/package.json +23 -4
  38. package/src/approvals.ts +5 -3
  39. package/src/client.ts +50 -1
  40. package/src/components/approval-surface.tsx +198 -0
  41. package/src/components/chat-composer.tsx +104 -751
  42. package/src/components/code-editor.tsx +104 -26
  43. package/src/components/command-palette.tsx +8 -2
  44. package/src/components/composer-transcription-control.tsx +211 -0
  45. package/src/components/composer.tsx +1536 -0
  46. package/src/components/desktop-viewer.tsx +3 -3
  47. package/src/components/enrollment-consent.tsx +2 -2
  48. package/src/components/file-browser.tsx +367 -29
  49. package/src/components/human-input-form.tsx +417 -0
  50. package/src/components/machine-card.tsx +82 -16
  51. package/src/components/machine-health-pill.tsx +68 -0
  52. package/src/components/machine-metrics.tsx +10 -24
  53. package/src/components/machines/health.ts +146 -0
  54. package/src/components/machines/machine-detail.tsx +220 -0
  55. package/src/components/machines/metric-history-chart.tsx +298 -0
  56. package/src/components/machines/metric-sparkline.tsx +76 -0
  57. package/src/components/machines/series.ts +113 -0
  58. package/src/components/machines-dashboard.tsx +16 -4
  59. package/src/components/message-timeline.tsx +15 -7
  60. package/src/components/model-picker.tsx +12 -3
  61. package/src/components/pierre-diff.tsx +32 -6
  62. package/src/components/queue-surface-implementation.tsx +1041 -0
  63. package/src/components/queue-surface-state.tsx +94 -0
  64. package/src/components/queue-surface.tsx +60 -0
  65. package/src/components/sandbox-files.tsx +230 -21
  66. package/src/components/sandbox-terminal.tsx +8 -2
  67. package/src/components/sandbox-workspace.tsx +489 -135
  68. package/src/components/session-status.tsx +0 -6
  69. package/src/components/workbench-changes.tsx +145 -50
  70. package/src/components/workspace-dock.tsx +329 -68
  71. package/src/composer.ts +60 -0
  72. package/src/hooks/internal.ts +115 -34
  73. package/src/hooks/use-composer.ts +635 -74
  74. package/src/hooks/use-human-input.ts +131 -0
  75. package/src/hooks/use-machine-chip.ts +2 -2
  76. package/src/hooks/use-machines.ts +27 -11
  77. package/src/hooks/use-sandbox-files.ts +310 -62
  78. package/src/hooks/use-sandbox-git.ts +154 -40
  79. package/src/hooks/use-sandbox-terminal.ts +28 -6
  80. package/src/hooks/use-session-capabilities.ts +38 -9
  81. package/src/hooks/use-session-control.ts +43 -15
  82. package/src/hooks/use-session-events.ts +523 -48
  83. package/src/hooks/use-session-lineage.ts +15 -6
  84. package/src/hooks/use-session.ts +10 -2
  85. package/src/hooks/use-slash-commands.ts +38 -38
  86. package/src/hooks/use-transcription.ts +757 -0
  87. package/src/hooks/use-turn-queue.ts +273 -77
  88. package/src/hooks/use-windowed-sections.ts +2 -2
  89. package/src/hooks/use-workspace-capture.ts +146 -40
  90. package/src/hooks/use-workspace-edit.ts +50 -14
  91. package/src/hooks/use-workspace-sessions.ts +3 -0
  92. package/src/human-input.ts +72 -0
  93. package/src/index.ts +88 -12
  94. package/src/lib/noto-sans-arabic-loader.ts +21 -0
  95. package/src/lib/noto-sans-jp-loader.generated.ts +295 -0
  96. package/src/lib/use-portal-token-style.ts +51 -0
  97. package/src/lib/use-unicode-fonts.ts +57 -0
  98. package/src/machines.ts +16 -0
  99. package/src/provider.tsx +163 -41
  100. package/src/session-context.ts +135 -0
  101. package/src/session.ts +79 -0
  102. package/src/timeline/parsers.ts +78 -6
  103. package/src/timeline/projection.ts +36 -6
  104. package/src/timeline/tool-renderers.tsx +41 -0
  105. package/src/timeline/turn-summary.tsx +2 -2
  106. package/src/timeline/types.ts +16 -8
  107. package/styles/index.css +78 -0
  108. package/styles/index.d.ts +2 -0
  109. package/styles/tokens.css +11 -6
  110. package/styles/tokens.d.ts +2 -0
  111. package/dist/chunk-NFYVQWIB.js +0 -1377
  112. package/dist/chunk-NFYVQWIB.js.map +0 -1
  113. package/dist/machines-CnlMb7E-.d.ts +0 -329
@@ -0,0 +1,1956 @@
1
+ import {
2
+ cn
3
+ } from "./chunk-TK7G6XLT.js";
4
+ import {
5
+ OpenGeniContext,
6
+ formatBytes,
7
+ formatRelativeTime,
8
+ useMutationRunner,
9
+ useOpenGeni,
10
+ usePolledValue
11
+ } from "./chunk-HHFA4AFX.js";
12
+
13
+ // src/types/machines.ts
14
+ function connectionStatusForState(state) {
15
+ switch (state) {
16
+ case "online":
17
+ case "consent_required":
18
+ case "display_unavailable":
19
+ return "online";
20
+ case "reconnecting":
21
+ case "enrolling":
22
+ return "reconnecting";
23
+ case "offline":
24
+ return "offline";
25
+ default: {
26
+ const _never = state;
27
+ return _never;
28
+ }
29
+ }
30
+ }
31
+
32
+ // src/components/machine-status-pill.tsx
33
+ import { jsx, jsxs } from "react/jsx-runtime";
34
+ var CONNECTION_STATUS_META = {
35
+ online: {
36
+ label: "Online",
37
+ dotClassName: "bg-og-status-running",
38
+ badgeClassName: "text-og-status-running border-og-status-running/30 bg-og-status-running/10",
39
+ pulse: false
40
+ },
41
+ reconnecting: {
42
+ label: "Reconnecting",
43
+ dotClassName: "bg-og-status-waiting",
44
+ badgeClassName: "text-og-status-waiting border-og-status-waiting/35 bg-og-status-waiting/10",
45
+ pulse: true
46
+ },
47
+ offline: {
48
+ label: "Offline",
49
+ dotClassName: "bg-og-status-failed",
50
+ badgeClassName: "text-og-fg-subtle border-og-border bg-og-status-failed/10",
51
+ pulse: false
52
+ }
53
+ };
54
+ var MACHINE_STATE_BADGE_META = {
55
+ consent_required: {
56
+ label: "Consent required",
57
+ badgeClassName: "text-og-status-waiting border-og-status-waiting/35 bg-og-status-waiting/10"
58
+ },
59
+ display_unavailable: {
60
+ label: "No display",
61
+ badgeClassName: "text-og-fg-muted border-og-border bg-og-surface-2"
62
+ },
63
+ enrolling: {
64
+ label: "Enrolling",
65
+ badgeClassName: "text-og-accent border-og-accent/30 bg-og-accent-soft"
66
+ }
67
+ };
68
+ function ConnectionStatusPill({
69
+ status,
70
+ label,
71
+ size = "md",
72
+ className
73
+ }) {
74
+ const meta = CONNECTION_STATUS_META[status];
75
+ return /* @__PURE__ */ jsxs(
76
+ "span",
77
+ {
78
+ "data-connection-status": status,
79
+ className: cn(
80
+ "og-root inline-flex shrink-0 items-center rounded-full border font-medium",
81
+ size === "sm" ? "gap-1 px-1.5 py-px text-og-xs" : "gap-1.5 px-2 py-0.5 text-og-sm",
82
+ meta.badgeClassName,
83
+ className
84
+ ),
85
+ children: [
86
+ /* @__PURE__ */ jsx(ConnectionDot, { status, className: size === "sm" ? "size-1" : "size-1.5" }),
87
+ label ?? meta.label
88
+ ]
89
+ }
90
+ );
91
+ }
92
+ function ConnectionDot({ status, className }) {
93
+ const meta = CONNECTION_STATUS_META[status];
94
+ return /* @__PURE__ */ jsx(
95
+ "span",
96
+ {
97
+ className: cn(
98
+ "relative inline-flex size-1.5 shrink-0 rounded-full",
99
+ meta.dotClassName,
100
+ className
101
+ ),
102
+ children: meta.pulse ? /* @__PURE__ */ jsx("span", { className: cn("absolute inset-0 animate-og-pulse rounded-full", meta.dotClassName) }) : null
103
+ }
104
+ );
105
+ }
106
+ function MachineStatusPill({
107
+ state,
108
+ sharedSessionCount,
109
+ size = "md",
110
+ className
111
+ }) {
112
+ const stateBadge = MACHINE_STATE_BADGE_META[state];
113
+ const shared = (sharedSessionCount ?? 0) > 1;
114
+ return /* @__PURE__ */ jsxs(
115
+ "span",
116
+ {
117
+ className: cn("og-root inline-flex flex-wrap items-center gap-1", className),
118
+ "data-machine-state": state,
119
+ children: [
120
+ /* @__PURE__ */ jsx(ConnectionStatusPill, { status: connectionStatusForState(state), size }),
121
+ stateBadge ? /* @__PURE__ */ jsx(
122
+ "span",
123
+ {
124
+ "data-state-badge": state,
125
+ className: cn(
126
+ "inline-flex shrink-0 items-center rounded-full border font-medium",
127
+ size === "sm" ? "px-1.5 py-px text-og-xs" : "px-2 py-0.5 text-og-sm",
128
+ stateBadge.badgeClassName
129
+ ),
130
+ children: stateBadge.label
131
+ }
132
+ ) : null,
133
+ shared ? /* @__PURE__ */ jsxs(
134
+ "span",
135
+ {
136
+ "data-shared-chip": true,
137
+ className: cn(
138
+ "inline-flex shrink-0 items-center gap-1 rounded-full border font-medium",
139
+ "text-og-accent border-og-accent/30 bg-og-accent-soft",
140
+ size === "sm" ? "px-1.5 py-px text-og-xs" : "px-2 py-0.5 text-og-sm"
141
+ ),
142
+ title: `${sharedSessionCount} sessions are on this machine`,
143
+ children: [
144
+ "Shared \xB7 ",
145
+ sharedSessionCount
146
+ ]
147
+ }
148
+ ) : null
149
+ ]
150
+ }
151
+ );
152
+ }
153
+
154
+ // src/components/machine-metrics.tsx
155
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
156
+ function pct(value) {
157
+ if (!Number.isFinite(value)) return 0;
158
+ return Math.max(0, Math.min(100, value));
159
+ }
160
+ function Meter({
161
+ label,
162
+ value,
163
+ fillPct,
164
+ tone
165
+ }) {
166
+ const fillClass = tone === "hot" ? "bg-og-status-failed" : tone === "warn" ? "bg-og-status-running" : "bg-og-status-idle";
167
+ return /* @__PURE__ */ jsxs2("div", { className: "flex min-w-0 flex-col gap-1", "data-metric": label.toLowerCase(), children: [
168
+ /* @__PURE__ */ jsxs2("div", { className: "flex items-baseline justify-between gap-2", children: [
169
+ /* @__PURE__ */ jsx2("span", { className: "text-[10px] font-medium uppercase tracking-wide text-og-fg-subtle", children: label }),
170
+ /* @__PURE__ */ jsx2("span", { className: "font-og-mono text-[11px] tabular-nums text-og-fg-muted", children: value })
171
+ ] }),
172
+ /* @__PURE__ */ jsx2("div", { className: "h-1 w-full overflow-hidden rounded-full bg-og-surface-2", children: /* @__PURE__ */ jsx2(
173
+ "div",
174
+ {
175
+ className: cn("h-full rounded-full transition-[width] duration-500", fillClass),
176
+ style: { width: `${pct(fillPct)}%` }
177
+ }
178
+ ) })
179
+ ] });
180
+ }
181
+ function toneFor(p) {
182
+ if (p >= 90) return "hot";
183
+ if (p >= 70) return "warn";
184
+ return "ok";
185
+ }
186
+ function StatTriple({
187
+ load1,
188
+ load5,
189
+ load15,
190
+ runQueue
191
+ }) {
192
+ const stats = [
193
+ { label: "1m", value: load1 },
194
+ { label: "5m", value: load5 },
195
+ { label: "15m", value: load15 }
196
+ ];
197
+ return /* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between gap-3", "data-metric": "load", children: [
198
+ /* @__PURE__ */ jsxs2("div", { className: "flex min-w-0 flex-col gap-1", children: [
199
+ /* @__PURE__ */ jsx2("span", { className: "text-[10px] font-medium uppercase tracking-wide text-og-fg-subtle", children: "Load" }),
200
+ /* @__PURE__ */ jsx2("div", { className: "flex items-baseline gap-3", children: stats.map((s) => /* @__PURE__ */ jsxs2("div", { className: "flex items-baseline gap-1", children: [
201
+ /* @__PURE__ */ jsx2("span", { className: "text-[10px] font-medium uppercase tracking-wide text-og-fg-subtle", children: s.label }),
202
+ /* @__PURE__ */ jsx2("span", { className: "font-og-mono text-[12px] tabular-nums text-og-fg", children: s.value.toFixed(2) })
203
+ ] }, s.label)) })
204
+ ] }),
205
+ runQueue > 0 ? /* @__PURE__ */ jsxs2(
206
+ "div",
207
+ {
208
+ className: "flex shrink-0 items-baseline gap-1.5 rounded-og-sm bg-og-surface-2 px-2 py-1 text-[11px] text-og-fg-subtle",
209
+ "data-metric": "runqueue",
210
+ children: [
211
+ /* @__PURE__ */ jsx2("span", { className: "font-medium uppercase tracking-wide", children: "Queue" }),
212
+ /* @__PURE__ */ jsx2("span", { className: "font-og-mono tabular-nums text-og-fg-muted", children: runQueue })
213
+ ]
214
+ }
215
+ ) : null
216
+ ] });
217
+ }
218
+ function MachineMetrics({ metrics, density = "compact", className }) {
219
+ if (!metrics) {
220
+ return /* @__PURE__ */ jsx2("div", { className: cn("text-[11px] text-og-fg-subtle", className), "data-metrics-empty": true, children: "No metrics yet" });
221
+ }
222
+ const memPct2 = metrics.memTotalBytes > 0 ? metrics.memUsedBytes / metrics.memTotalBytes * 100 : 0;
223
+ const diskPct2 = metrics.diskTotalBytes > 0 ? metrics.diskUsedBytes / metrics.diskTotalBytes * 100 : 0;
224
+ const hasGpu = metrics.gpuUtilPct !== null;
225
+ const gpuMemLabel = metrics.gpuMemBytes !== null ? formatBytes(metrics.gpuMemBytes) : null;
226
+ const ratioGridClass = density === "full" ? "grid-cols-1" : "grid-cols-1 sm:grid-cols-2";
227
+ return /* @__PURE__ */ jsxs2("div", { className: cn("flex flex-col gap-3", className), "data-machine-metrics": true, children: [
228
+ /* @__PURE__ */ jsxs2("div", { className: cn("grid gap-x-4 gap-y-2.5", ratioGridClass), children: [
229
+ /* @__PURE__ */ jsx2(
230
+ Meter,
231
+ {
232
+ label: "Memory",
233
+ value: `${formatBytes(metrics.memUsedBytes)} / ${formatBytes(metrics.memTotalBytes)}`,
234
+ fillPct: memPct2,
235
+ tone: toneFor(memPct2)
236
+ }
237
+ ),
238
+ /* @__PURE__ */ jsx2(
239
+ Meter,
240
+ {
241
+ label: "Disk",
242
+ value: `${formatBytes(metrics.diskUsedBytes)} / ${formatBytes(metrics.diskTotalBytes)}`,
243
+ fillPct: diskPct2,
244
+ tone: toneFor(diskPct2)
245
+ }
246
+ )
247
+ ] }),
248
+ /* @__PURE__ */ jsxs2("div", { className: "grid grid-cols-2 gap-x-4 gap-y-2.5", children: [
249
+ /* @__PURE__ */ jsx2(
250
+ Meter,
251
+ {
252
+ label: "CPU",
253
+ value: `${metrics.cpuPct.toFixed(0)}%`,
254
+ fillPct: metrics.cpuPct,
255
+ tone: toneFor(metrics.cpuPct)
256
+ }
257
+ ),
258
+ hasGpu ? /* @__PURE__ */ jsx2(
259
+ Meter,
260
+ {
261
+ label: "GPU",
262
+ value: gpuMemLabel ? `${metrics.gpuUtilPct.toFixed(0)}% \xB7 ${gpuMemLabel}` : `${metrics.gpuUtilPct.toFixed(0)}%`,
263
+ fillPct: metrics.gpuUtilPct,
264
+ tone: toneFor(metrics.gpuUtilPct)
265
+ }
266
+ ) : null
267
+ ] }),
268
+ /* @__PURE__ */ jsx2(
269
+ StatTriple,
270
+ {
271
+ load1: metrics.load1,
272
+ load5: metrics.load5,
273
+ load15: metrics.load15,
274
+ runQueue: metrics.runQueue
275
+ }
276
+ )
277
+ ] });
278
+ }
279
+
280
+ // src/components/machine-card.tsx
281
+ import {
282
+ ArrowRightIcon,
283
+ CpuIcon,
284
+ LaptopIcon,
285
+ MonitorIcon,
286
+ MonitorOffIcon,
287
+ ServerIcon,
288
+ UsersIcon
289
+ } from "lucide-react";
290
+
291
+ // src/components/machines/health.ts
292
+ var PRESSURE = {
293
+ cpu: { warn: 90, crit: 98 },
294
+ mem: { warn: 85, crit: 95 },
295
+ disk: { warn: 90, crit: 96 }
296
+ };
297
+ var STALE_AFTER_MS = 45e3;
298
+ function pct2(used, total) {
299
+ if (!Number.isFinite(used) || !Number.isFinite(total) || total <= 0) return 0;
300
+ return used / total * 100;
301
+ }
302
+ function deriveHealth(state, metrics, now = Date.now()) {
303
+ if (state === "offline") {
304
+ return { level: "offline", label: "Offline", reason: "Not reachable", stale: true };
305
+ }
306
+ if (state === "enrolling") {
307
+ return { level: "unknown", label: "Enrolling", reason: "Completing enrollment", stale: false };
308
+ }
309
+ const stale = metrics ? now - new Date(metrics.sampledAt).getTime() > STALE_AFTER_MS : true;
310
+ if (state === "reconnecting") {
311
+ return { level: "degraded", label: "Reconnecting", reason: "Re-establishing link", stale };
312
+ }
313
+ if (!metrics) {
314
+ return { level: "unknown", label: "Online", reason: "Awaiting first sample", stale };
315
+ }
316
+ if (stale) {
317
+ return { level: "degraded", label: "Telemetry stale", reason: "No recent sample", stale: true };
318
+ }
319
+ const memPct2 = pct2(metrics.memUsedBytes, metrics.memTotalBytes);
320
+ const diskPct2 = pct2(metrics.diskUsedBytes, metrics.diskTotalBytes);
321
+ const cpu = metrics.cpuPct;
322
+ const pressures = [
323
+ resolve("Disk", diskPct2, PRESSURE.disk),
324
+ resolve("Memory", memPct2, PRESSURE.mem),
325
+ resolve("CPU", cpu, PRESSURE.cpu)
326
+ ];
327
+ const worst = pressures.reduce((a, b) => b.sev > a.sev ? b : a);
328
+ if (worst.sev === 2) return { level: "critical", label: "Critical", reason: worst.reason, stale };
329
+ if (worst.sev === 1)
330
+ return { level: "degraded", label: "Under load", reason: worst.reason, stale };
331
+ return { level: "healthy", label: "Healthy", reason: "All systems nominal", stale };
332
+ }
333
+ function resolve(name, value, t) {
334
+ const rounded = Math.round(value);
335
+ if (value >= t.crit) return { level: "critical", reason: `${name} ${rounded}%`, sev: 2 };
336
+ if (value >= t.warn) return { level: "degraded", reason: `${name} ${rounded}%`, sev: 1 };
337
+ return { level: "healthy", reason: `${name} ${rounded}%`, sev: 0 };
338
+ }
339
+ var HEALTH_TOKEN = {
340
+ healthy: {
341
+ text: "text-og-status-idle",
342
+ dot: "bg-og-status-idle",
343
+ soft: "bg-og-status-idle/10 border-og-status-idle/25"
344
+ },
345
+ degraded: {
346
+ text: "text-og-status-running",
347
+ dot: "bg-og-status-running",
348
+ soft: "bg-og-status-running/10 border-og-status-running/25"
349
+ },
350
+ critical: {
351
+ text: "text-og-status-failed",
352
+ dot: "bg-og-status-failed",
353
+ soft: "bg-og-status-failed/10 border-og-status-failed/30"
354
+ },
355
+ offline: {
356
+ text: "text-og-fg-subtle",
357
+ dot: "bg-og-fg-subtle",
358
+ soft: "bg-og-surface-2 border-og-border"
359
+ },
360
+ unknown: {
361
+ text: "text-og-fg-muted",
362
+ dot: "bg-og-fg-muted",
363
+ soft: "bg-og-surface-2 border-og-border"
364
+ }
365
+ };
366
+ function healthPulses(level) {
367
+ return level === "degraded" || level === "critical";
368
+ }
369
+
370
+ // src/components/machine-health-pill.tsx
371
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
372
+ function MachineHealthPill({
373
+ state,
374
+ metrics,
375
+ now,
376
+ size = "sm",
377
+ className
378
+ }) {
379
+ const health = deriveHealth(state, metrics, now ?? Date.now());
380
+ const tone = HEALTH_TOKEN[health.level];
381
+ const badge = MACHINE_STATE_BADGE_META[state];
382
+ const pad = size === "md" ? "px-2.5 py-1 text-og-sm" : "px-2 py-0.5 text-og-xs";
383
+ return /* @__PURE__ */ jsxs3("div", { className: cn("flex shrink-0 items-center gap-1.5", className), children: [
384
+ badge ? /* @__PURE__ */ jsx3(
385
+ "span",
386
+ {
387
+ className: cn(
388
+ "rounded-full border px-2 py-0.5 text-og-xs font-medium",
389
+ badge.badgeClassName
390
+ ),
391
+ children: badge.label
392
+ }
393
+ ) : null,
394
+ /* @__PURE__ */ jsxs3(
395
+ "span",
396
+ {
397
+ className: cn(
398
+ "inline-flex items-center gap-1.5 rounded-full border font-medium",
399
+ pad,
400
+ tone.soft,
401
+ tone.text
402
+ ),
403
+ "data-health": health.level,
404
+ title: health.reason,
405
+ children: [
406
+ /* @__PURE__ */ jsxs3("span", { className: "relative flex size-2", children: [
407
+ healthPulses(health.level) && /* @__PURE__ */ jsx3(
408
+ "span",
409
+ {
410
+ className: cn(
411
+ "absolute inline-flex size-full animate-og-pulse rounded-full opacity-60",
412
+ tone.dot
413
+ )
414
+ }
415
+ ),
416
+ /* @__PURE__ */ jsx3("span", { className: cn("relative inline-flex size-2 rounded-full", tone.dot) })
417
+ ] }),
418
+ health.label
419
+ ]
420
+ }
421
+ )
422
+ ] });
423
+ }
424
+
425
+ // src/components/machines/metric-sparkline.tsx
426
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
427
+ var VW = 120;
428
+ function MetricSparkline({
429
+ points,
430
+ color = "var(--og-color-accent)",
431
+ yMax = "auto",
432
+ height = 28,
433
+ className
434
+ }) {
435
+ const valued = points.filter(
436
+ (p) => p.v != null && Number.isFinite(p.v)
437
+ );
438
+ if (valued.length < 2) {
439
+ return /* @__PURE__ */ jsx4("div", { className, style: { height }, "aria-hidden": true });
440
+ }
441
+ const vh = height;
442
+ const top = yMax === "auto" ? Math.max(1, ...valued.map((p) => p.v)) * 1.1 : yMax;
443
+ const tMin = valued[0].t;
444
+ const tSpan = Math.max(1, valued[valued.length - 1].t - tMin);
445
+ const xy = valued.map((p) => ({
446
+ x: (p.t - tMin) / tSpan * VW,
447
+ y: vh - Math.min(top, Math.max(0, p.v)) / Math.max(1e-6, top) * (vh - 2) - 1
448
+ }));
449
+ let d = `M${xy[0].x},${xy[0].y}`;
450
+ for (let i = 0; i < xy.length - 1; i++) {
451
+ const p0 = xy[i - 1] ?? xy[i];
452
+ const p1 = xy[i];
453
+ const p2 = xy[i + 1];
454
+ const p3 = xy[i + 2] ?? p2;
455
+ d += `C${p1.x + (p2.x - p0.x) / 6},${p1.y + (p2.y - p0.y) / 6} ${p2.x - (p3.x - p1.x) / 6},${p2.y - (p3.y - p1.y) / 6} ${p2.x},${p2.y}`;
456
+ }
457
+ const last = xy[xy.length - 1];
458
+ const gid = `spark-${Math.round(top)}-${color.length}`;
459
+ return /* @__PURE__ */ jsxs4(
460
+ "svg",
461
+ {
462
+ viewBox: `0 0 ${VW} ${vh}`,
463
+ preserveAspectRatio: "none",
464
+ className,
465
+ style: { height, width: "100%" },
466
+ "aria-hidden": true,
467
+ children: [
468
+ /* @__PURE__ */ jsx4("defs", { children: /* @__PURE__ */ jsxs4("linearGradient", { id: gid, x1: "0", y1: "0", x2: "0", y2: "1", children: [
469
+ /* @__PURE__ */ jsx4("stop", { offset: "0%", stopColor: color, stopOpacity: "0.22" }),
470
+ /* @__PURE__ */ jsx4("stop", { offset: "100%", stopColor: color, stopOpacity: "0" })
471
+ ] }) }),
472
+ /* @__PURE__ */ jsx4("path", { d: `${d} L${last.x},${vh} L${xy[0].x},${vh} Z`, fill: `url(#${gid})` }),
473
+ /* @__PURE__ */ jsx4(
474
+ "path",
475
+ {
476
+ d,
477
+ fill: "none",
478
+ stroke: color,
479
+ strokeWidth: 1.25,
480
+ strokeLinejoin: "round",
481
+ strokeLinecap: "round",
482
+ vectorEffect: "non-scaling-stroke"
483
+ }
484
+ ),
485
+ /* @__PURE__ */ jsx4("circle", { cx: last.x, cy: last.y, r: 1.6, fill: color, vectorEffect: "non-scaling-stroke" })
486
+ ]
487
+ }
488
+ );
489
+ }
490
+
491
+ // src/components/machine-card.tsx
492
+ import { Fragment, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
493
+ function KindIcon({ machine }) {
494
+ if (machine.isSessionGroup)
495
+ return /* @__PURE__ */ jsx5(ServerIcon, { className: "size-4 text-og-fg-subtle", "aria-hidden": true });
496
+ if (machine.kind === "modal") return /* @__PURE__ */ jsx5(CpuIcon, { className: "size-4 text-og-fg-subtle", "aria-hidden": true });
497
+ return /* @__PURE__ */ jsx5(LaptopIcon, { className: "size-4 text-og-fg-subtle", "aria-hidden": true });
498
+ }
499
+ function MachineCard({
500
+ machine,
501
+ onAttach,
502
+ attaching,
503
+ series,
504
+ onOpenDetail,
505
+ now,
506
+ className
507
+ }) {
508
+ const offline = machine.state === "offline";
509
+ const attachable = !machine.active && !offline && Boolean(onAttach);
510
+ const shared = machine.sharedSessionCount > 1;
511
+ const clockNow = now ?? Date.now();
512
+ const sampledAgo = machine.metrics ? formatRelativeTime(machine.metrics.sampledAt, new Date(clockNow)) : null;
513
+ const cpuPts = (series ?? []).map((s) => ({ t: new Date(s.sampledAt).getTime(), v: s.cpuPct }));
514
+ const openable = Boolean(onOpenDetail);
515
+ return /* @__PURE__ */ jsxs5(
516
+ "div",
517
+ {
518
+ "data-machine-card": machine.sandboxId,
519
+ "data-active": machine.active ? "true" : "false",
520
+ role: openable ? "button" : void 0,
521
+ tabIndex: openable ? 0 : void 0,
522
+ onClick: openable ? () => onOpenDetail?.(machine) : void 0,
523
+ onKeyDown: openable ? (e) => {
524
+ if (e.key === "Enter" || e.key === " ") {
525
+ e.preventDefault();
526
+ onOpenDetail?.(machine);
527
+ }
528
+ } : void 0,
529
+ className: cn(
530
+ "og-root group relative flex flex-col gap-3 overflow-hidden rounded-og-lg border border-og-border",
531
+ "bg-og-surface-1 p-4 shadow-og-sm transition-colors",
532
+ openable && "cursor-pointer hover:border-og-border-strong focus-visible:border-og-accent/60 focus-visible:outline-none",
533
+ machine.active && "border-og-accent/40",
534
+ className
535
+ ),
536
+ children: [
537
+ machine.active ? /* @__PURE__ */ jsx5("span", { "aria-hidden": true, className: "absolute inset-y-0 left-0 w-0.5 bg-og-accent" }) : null,
538
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-start justify-between gap-3", children: [
539
+ /* @__PURE__ */ jsxs5("div", { className: "flex min-w-0 items-start gap-2.5", children: [
540
+ /* @__PURE__ */ jsx5("span", { className: "mt-0.5 shrink-0", children: /* @__PURE__ */ jsx5(KindIcon, { machine }) }),
541
+ /* @__PURE__ */ jsxs5("div", { className: "min-w-0", children: [
542
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2", children: [
543
+ /* @__PURE__ */ jsx5("span", { className: "truncate text-og-base font-medium text-og-fg", children: machine.name }),
544
+ machine.active ? /* @__PURE__ */ jsx5(
545
+ "span",
546
+ {
547
+ "data-active-marker": true,
548
+ className: "shrink-0 rounded-full border border-og-accent/30 bg-og-accent-soft px-1.5 py-px text-og-xs font-medium text-og-accent",
549
+ children: "Active"
550
+ }
551
+ ) : null
552
+ ] }),
553
+ /* @__PURE__ */ jsxs5("div", { className: "mt-0.5 flex flex-wrap items-center gap-x-2 gap-y-0.5 text-og-xs text-og-fg-subtle", children: [
554
+ /* @__PURE__ */ jsx5("span", { className: "capitalize", children: machine.isSessionGroup ? "session sandbox" : machine.kind }),
555
+ /* @__PURE__ */ jsx5("span", { "aria-hidden": true, children: "\xB7" }),
556
+ /* @__PURE__ */ jsxs5("span", { className: "font-og-mono", children: [
557
+ machine.os,
558
+ "/",
559
+ machine.arch
560
+ ] }),
561
+ /* @__PURE__ */ jsx5("span", { "aria-hidden": true, children: "\xB7" }),
562
+ /* @__PURE__ */ jsxs5("span", { className: "inline-flex items-center gap-1", children: [
563
+ machine.hasDisplay ? /* @__PURE__ */ jsx5(MonitorIcon, { className: "size-3", "aria-hidden": true }) : /* @__PURE__ */ jsx5(MonitorOffIcon, { className: "size-3", "aria-hidden": true }),
564
+ machine.hasDisplay ? "display" : "headless"
565
+ ] })
566
+ ] })
567
+ ] })
568
+ ] }),
569
+ /* @__PURE__ */ jsx5(
570
+ MachineHealthPill,
571
+ {
572
+ state: machine.state,
573
+ metrics: machine.metrics,
574
+ now: clockNow,
575
+ size: "sm",
576
+ className: "shrink-0"
577
+ }
578
+ )
579
+ ] }),
580
+ shared ? /* @__PURE__ */ jsxs5(
581
+ "p",
582
+ {
583
+ "data-shared-disclosure": true,
584
+ className: "flex items-center gap-1.5 rounded-og-md border border-og-accent/25 bg-og-accent-soft px-2.5 py-1.5 text-og-xs text-og-fg-muted",
585
+ children: [
586
+ /* @__PURE__ */ jsx5(UsersIcon, { className: "size-3.5 shrink-0 text-og-accent", "aria-hidden": true }),
587
+ "Shared \u2014 ",
588
+ machine.sharedSessionCount,
589
+ " sessions are on this machine."
590
+ ]
591
+ }
592
+ ) : null,
593
+ /* @__PURE__ */ jsx5(MachineMetrics, { metrics: machine.metrics }),
594
+ cpuPts.length > 1 ? /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-1 rounded-og-md bg-og-surface-2/40 p-2.5", children: [
595
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between text-[10px] font-medium uppercase tracking-wide text-og-fg-subtle", children: [
596
+ /* @__PURE__ */ jsx5("span", { children: "CPU \xB7 last 15m" }),
597
+ openable ? /* @__PURE__ */ jsxs5("span", { className: "flex items-center gap-0.5 opacity-0 transition-opacity group-hover:opacity-100", children: [
598
+ "Telemetry ",
599
+ /* @__PURE__ */ jsx5(ArrowRightIcon, { className: "size-2.5", "aria-hidden": true })
600
+ ] }) : null
601
+ ] }),
602
+ /* @__PURE__ */ jsx5(MetricSparkline, { points: cpuPts, color: "var(--og-color-accent)", yMax: 100, height: 26 })
603
+ ] }) : null,
604
+ /* @__PURE__ */ jsxs5("div", { className: "mt-1 flex items-center justify-between gap-3 text-og-xs text-og-fg-subtle", children: [
605
+ /* @__PURE__ */ jsx5("span", { className: "inline-flex items-center gap-1.5", children: sampledAgo ? /* @__PURE__ */ jsxs5(Fragment, { children: [
606
+ /* @__PURE__ */ jsx5(
607
+ "span",
608
+ {
609
+ className: cn(
610
+ "size-1.5 rounded-full",
611
+ sampledAgo === "now" ? "bg-og-status-idle" : "bg-og-fg-subtle"
612
+ ),
613
+ "aria-hidden": true
614
+ }
615
+ ),
616
+ sampledAgo === "now" ? "Live" : `Updated ${sampledAgo} ago`
617
+ ] }) : machine.lastSeenAt ? /* @__PURE__ */ jsxs5(Fragment, { children: [
618
+ "Last seen ",
619
+ formatRelativeTime(machine.lastSeenAt, new Date(clockNow))
620
+ ] }) : "Never connected" }),
621
+ machine.active ? /* @__PURE__ */ jsx5("span", { className: "text-og-accent", children: "Routing here" }) : attachable ? /* @__PURE__ */ jsx5(
622
+ "button",
623
+ {
624
+ type: "button",
625
+ "data-attach": true,
626
+ disabled: attaching,
627
+ onClick: (e) => {
628
+ e.stopPropagation();
629
+ onAttach?.(machine);
630
+ },
631
+ className: cn(
632
+ "rounded-og-sm border border-og-border px-2.5 py-1 text-og-xs font-medium text-og-fg-muted transition-colors pointer-coarse:min-h-11",
633
+ "hover:border-og-border-strong hover:text-og-fg disabled:cursor-not-allowed disabled:opacity-50"
634
+ ),
635
+ children: attaching ? "Switching\u2026" : "Attach"
636
+ }
637
+ ) : /* @__PURE__ */ jsx5("span", { className: "text-og-fg-subtle/70", children: offline ? "Unavailable" : "\u2014" })
638
+ ] })
639
+ ]
640
+ }
641
+ );
642
+ }
643
+
644
+ // src/components/machines/metric-history-chart.tsx
645
+ import { useLayoutEffect, useRef, useState } from "react";
646
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
647
+ var PAD = { top: 10, right: 12, bottom: 18, left: 40 };
648
+ function niceCeil(v) {
649
+ if (v <= 0) return 1;
650
+ const mag = 10 ** Math.floor(Math.log10(v));
651
+ const n = v / mag;
652
+ const step = n <= 1 ? 1 : n <= 2 ? 2 : n <= 5 ? 5 : 10;
653
+ return step * mag;
654
+ }
655
+ function smoothPath(pts) {
656
+ if (pts.length === 0) return "";
657
+ if (pts.length === 1) return `M${pts[0].x},${pts[0].y}`;
658
+ let d = `M${pts[0].x},${pts[0].y}`;
659
+ for (let i = 0; i < pts.length - 1; i++) {
660
+ const p0 = pts[i - 1] ?? pts[i];
661
+ const p1 = pts[i];
662
+ const p2 = pts[i + 1];
663
+ const p3 = pts[i + 2] ?? p2;
664
+ const cp1x = p1.x + (p2.x - p0.x) / 6;
665
+ const cp1y = p1.y + (p2.y - p0.y) / 6;
666
+ const cp2x = p2.x - (p3.x - p1.x) / 6;
667
+ const cp2y = p2.y - (p3.y - p1.y) / 6;
668
+ d += `C${cp1x},${cp1y} ${cp2x},${cp2y} ${p2.x},${p2.y}`;
669
+ }
670
+ return d;
671
+ }
672
+ function fmtClock(t) {
673
+ const d = new Date(t);
674
+ return `${d.getHours().toString().padStart(2, "0")}:${d.getMinutes().toString().padStart(2, "0")}`;
675
+ }
676
+ function MetricHistoryChart({
677
+ points,
678
+ yMax = "auto",
679
+ yMin = 0,
680
+ format,
681
+ unit = "",
682
+ color = "var(--og-color-accent)",
683
+ thresholds,
684
+ height = 132,
685
+ rangeLabel = "in this range",
686
+ className
687
+ }) {
688
+ const ref = useRef(null);
689
+ const [w, setW] = useState(560);
690
+ const [hover, setHover] = useState(null);
691
+ useLayoutEffect(() => {
692
+ const el = ref.current;
693
+ if (!el) return;
694
+ const ro = new ResizeObserver((entries) => {
695
+ const cw = entries[0]?.contentRect.width;
696
+ if (cw && cw > 0) setW(cw);
697
+ });
698
+ ro.observe(el);
699
+ return () => ro.disconnect();
700
+ }, []);
701
+ const valued = points.filter(
702
+ (p) => p.v != null && Number.isFinite(p.v)
703
+ );
704
+ const fmt = format ?? ((v) => `${Math.round(v)}${unit}`);
705
+ const H = height;
706
+ const plotW = Math.max(1, w - PAD.left - PAD.right);
707
+ const plotH = Math.max(1, H - PAD.top - PAD.bottom);
708
+ const dataMax = valued.length ? Math.max(...valued.map((p) => p.v)) : 1;
709
+ const top = yMax === "auto" ? niceCeil(Math.max(dataMax * 1.15, thresholds?.warn ?? 0, 1)) : yMax;
710
+ const span = Math.max(1e-6, top - yMin);
711
+ const tMin = valued.length ? valued[0].t : 0;
712
+ const tMax = valued.length ? valued[valued.length - 1].t : 1;
713
+ const tSpan = Math.max(1, tMax - tMin);
714
+ const xOf = (t) => PAD.left + (t - tMin) / tSpan * plotW;
715
+ const yOf = (v) => PAD.top + (1 - (Math.min(top, Math.max(yMin, v)) - yMin) / span) * plotH;
716
+ const pxPts = valued.map((p) => ({ x: xOf(p.t), y: yOf(p.v), t: p.t, v: p.v }));
717
+ const sparse = pxPts.length > 0 && pxPts.length <= 4;
718
+ const line = smoothPath(pxPts);
719
+ const area = pxPts.length > 1 ? `${line} L${pxPts[pxPts.length - 1].x},${PAD.top + plotH} L${pxPts[0].x},${PAD.top + plotH} Z` : "";
720
+ const yTicks = [yMin, yMin + span / 2, top];
721
+ const gid = `og-mh-${Math.round(top)}-${Math.round(color.length)}`;
722
+ const hoverPt = hover != null ? pxPts[hover] : null;
723
+ return /* @__PURE__ */ jsxs6(
724
+ "div",
725
+ {
726
+ ref,
727
+ className: cn("relative w-full select-none", className),
728
+ style: { height: H },
729
+ "data-metric-chart": true,
730
+ children: [
731
+ valued.length === 0 ? /* @__PURE__ */ jsx6("div", { className: "absolute inset-0 flex items-center justify-center", children: /* @__PURE__ */ jsxs6("span", { className: "text-og-xs text-og-fg-subtle", children: [
732
+ "No samples ",
733
+ rangeLabel
734
+ ] }) }) : /* @__PURE__ */ jsxs6(
735
+ "svg",
736
+ {
737
+ width: w,
738
+ height: H,
739
+ className: "overflow-visible",
740
+ role: "img",
741
+ onMouseLeave: () => setHover(null),
742
+ onMouseMove: (e) => {
743
+ const rect = e.currentTarget.getBoundingClientRect();
744
+ const mx = e.clientX - rect.left;
745
+ let best = 0;
746
+ let bestD = Infinity;
747
+ for (let i = 0; i < pxPts.length; i++) {
748
+ const d = Math.abs(pxPts[i].x - mx);
749
+ if (d < bestD) {
750
+ bestD = d;
751
+ best = i;
752
+ }
753
+ }
754
+ setHover(best);
755
+ },
756
+ children: [
757
+ /* @__PURE__ */ jsx6("defs", { children: /* @__PURE__ */ jsxs6("linearGradient", { id: gid, x1: "0", y1: "0", x2: "0", y2: "1", children: [
758
+ /* @__PURE__ */ jsx6("stop", { offset: "0%", stopColor: color, stopOpacity: "0.26" }),
759
+ /* @__PURE__ */ jsx6("stop", { offset: "100%", stopColor: color, stopOpacity: "0" })
760
+ ] }) }),
761
+ yTicks.map((v, i) => {
762
+ const y = yOf(v);
763
+ return /* @__PURE__ */ jsxs6("g", { children: [
764
+ /* @__PURE__ */ jsx6(
765
+ "line",
766
+ {
767
+ x1: PAD.left,
768
+ x2: w - PAD.right,
769
+ y1: y,
770
+ y2: y,
771
+ stroke: "var(--og-color-border)",
772
+ strokeOpacity: i === 0 ? 0.9 : 0.4,
773
+ strokeWidth: 1
774
+ }
775
+ ),
776
+ /* @__PURE__ */ jsx6(
777
+ "text",
778
+ {
779
+ x: PAD.left - 8,
780
+ y: y + 3,
781
+ textAnchor: "end",
782
+ className: "font-og-mono",
783
+ fontSize: 10,
784
+ fill: "var(--og-color-fg-subtle)",
785
+ children: fmt(v)
786
+ }
787
+ )
788
+ ] }, `yt-${v}`);
789
+ }),
790
+ ["warn", "crit"].map((k) => {
791
+ const tv = thresholds?.[k];
792
+ if (tv == null || tv > top) return null;
793
+ const y = yOf(tv);
794
+ const stroke = k === "crit" ? "var(--og-color-status-failed)" : "var(--og-color-status-waiting)";
795
+ return /* @__PURE__ */ jsx6(
796
+ "line",
797
+ {
798
+ x1: PAD.left,
799
+ x2: w - PAD.right,
800
+ y1: y,
801
+ y2: y,
802
+ stroke,
803
+ strokeOpacity: 0.5,
804
+ strokeWidth: 1,
805
+ strokeDasharray: "3 4"
806
+ },
807
+ k
808
+ );
809
+ }),
810
+ valued.length > 1 && [tMin, tMax].map((t, i) => /* @__PURE__ */ jsx6(
811
+ "text",
812
+ {
813
+ x: i === 0 ? PAD.left : w - PAD.right,
814
+ y: H - 4,
815
+ textAnchor: i === 0 ? "start" : "end",
816
+ className: "font-og-mono",
817
+ fontSize: 10,
818
+ fill: "var(--og-color-fg-subtle)",
819
+ children: fmtClock(t)
820
+ },
821
+ i === 0 ? "x-start" : "x-end"
822
+ )),
823
+ area && /* @__PURE__ */ jsx6("path", { d: area, fill: `url(#${gid})` }),
824
+ /* @__PURE__ */ jsx6(
825
+ "path",
826
+ {
827
+ d: line,
828
+ fill: "none",
829
+ stroke: color,
830
+ strokeWidth: 1.75,
831
+ strokeLinejoin: "round",
832
+ strokeLinecap: "round",
833
+ pathLength: 1,
834
+ className: "og-mh-draw"
835
+ }
836
+ ),
837
+ sparse && pxPts.map((p) => /* @__PURE__ */ jsx6("circle", { cx: p.x, cy: p.y, r: 2.5, fill: color }, `m-${p.t}`)),
838
+ hoverPt && /* @__PURE__ */ jsxs6("g", { pointerEvents: "none", children: [
839
+ /* @__PURE__ */ jsx6(
840
+ "line",
841
+ {
842
+ x1: hoverPt.x,
843
+ x2: hoverPt.x,
844
+ y1: PAD.top,
845
+ y2: PAD.top + plotH,
846
+ stroke: "var(--og-color-border-strong)",
847
+ strokeWidth: 1
848
+ }
849
+ ),
850
+ /* @__PURE__ */ jsx6(
851
+ "circle",
852
+ {
853
+ cx: hoverPt.x,
854
+ cy: hoverPt.y,
855
+ r: 3.5,
856
+ fill: color,
857
+ stroke: "var(--og-color-bg)",
858
+ strokeWidth: 1.5
859
+ }
860
+ )
861
+ ] })
862
+ ]
863
+ }
864
+ ),
865
+ hoverPt && /* @__PURE__ */ jsxs6(
866
+ "div",
867
+ {
868
+ className: "pointer-events-none absolute z-10 flex flex-col rounded-og-sm border border-og-border-strong bg-og-surface-3/95 px-2 py-1 shadow-og-md backdrop-blur-sm",
869
+ style: {
870
+ left: Math.min(Math.max(hoverPt.x - 30, 0), w - 78),
871
+ top: Math.max(hoverPt.y - 42, 0)
872
+ },
873
+ children: [
874
+ /* @__PURE__ */ jsx6("span", { className: "font-og-mono text-og-sm font-medium tabular-nums text-og-fg", children: fmt(hoverPt.v) }),
875
+ /* @__PURE__ */ jsx6("span", { className: "font-og-mono text-[10px] tabular-nums text-og-fg-subtle", children: fmtClock(hoverPt.t) })
876
+ ]
877
+ }
878
+ )
879
+ ]
880
+ }
881
+ );
882
+ }
883
+
884
+ // src/components/machines/machine-detail.tsx
885
+ import { ArrowLeftIcon, CpuIcon as CpuIcon2, LaptopIcon as LaptopIcon2, RadioIcon, ServerIcon as ServerIcon2 } from "lucide-react";
886
+
887
+ // src/components/machines/series.ts
888
+ var METRIC_WINDOWS = ["15m", "1h", "6h", "24h"];
889
+ var WINDOW_LABEL = {
890
+ "15m": "in the last 15 min",
891
+ "1h": "in the last hour",
892
+ "6h": "in the last 6 hours",
893
+ "24h": "in the last 24 hours"
894
+ };
895
+ function ptsFrom(samples, pick) {
896
+ return samples.map((s) => ({ t: new Date(s.sampledAt).getTime(), v: pick(s) }));
897
+ }
898
+ var memPct = (s) => s.memTotalBytes > 0 ? s.memUsedBytes / s.memTotalBytes * 100 : null;
899
+ var diskPct = (s) => s.diskTotalBytes > 0 ? s.diskUsedBytes / s.diskTotalBytes * 100 : null;
900
+ var fmtBytes = (b) => {
901
+ if (b < 1024) return `${b} B`;
902
+ const u = ["KB", "MB", "GB", "TB"];
903
+ let v = b / 1024;
904
+ for (const unit of u) {
905
+ if (v < 1024 || unit === "TB") return `${v.toFixed(v < 10 ? 1 : 0)} ${unit}`;
906
+ v /= 1024;
907
+ }
908
+ return `${b} B`;
909
+ };
910
+ var METRICS = [
911
+ {
912
+ key: "cpu",
913
+ title: "CPU",
914
+ unit: "%",
915
+ yMax: 100,
916
+ color: "var(--og-color-accent)",
917
+ thresholds: { warn: 90, crit: 98 },
918
+ pick: (s) => s.cpuPct,
919
+ current: (s) => `${Math.round(s.cpuPct)}%`,
920
+ sub: (s) => `${s.runQueue} in run queue`
921
+ },
922
+ {
923
+ key: "mem",
924
+ title: "Memory",
925
+ unit: "%",
926
+ yMax: 100,
927
+ color: "var(--og-color-status-idle)",
928
+ thresholds: { warn: 85, crit: 95 },
929
+ pick: memPct,
930
+ current: (s) => `${Math.round(memPct(s) ?? 0)}%`,
931
+ sub: (s) => `${fmtBytes(s.memUsedBytes)} / ${fmtBytes(s.memTotalBytes)}`
932
+ },
933
+ {
934
+ key: "disk",
935
+ title: "Disk",
936
+ unit: "%",
937
+ yMax: 100,
938
+ color: "var(--og-color-status-waiting)",
939
+ thresholds: { warn: 90, crit: 96 },
940
+ pick: diskPct,
941
+ current: (s) => `${Math.round(diskPct(s) ?? 0)}%`,
942
+ sub: (s) => `${fmtBytes(s.diskUsedBytes)} / ${fmtBytes(s.diskTotalBytes)}`
943
+ },
944
+ {
945
+ key: "load",
946
+ title: "Load average",
947
+ unit: "",
948
+ yMax: "auto",
949
+ color: "var(--og-color-accent-strong)",
950
+ pick: (s) => s.load1,
951
+ current: (s) => s.load1.toFixed(2),
952
+ sub: (s) => `5m ${s.load5.toFixed(2)} \xB7 15m ${s.load15.toFixed(2)}`
953
+ },
954
+ {
955
+ key: "gpu",
956
+ title: "GPU",
957
+ unit: "%",
958
+ yMax: 100,
959
+ color: "var(--og-color-status-running)",
960
+ thresholds: { warn: 90, crit: 98 },
961
+ pick: (s) => s.gpuUtilPct,
962
+ current: (s) => s.gpuUtilPct == null ? "\u2014" : `${Math.round(s.gpuUtilPct)}%`,
963
+ sub: (s) => s.gpuMemBytes == null ? null : `${fmtBytes(s.gpuMemBytes)} used`
964
+ }
965
+ ];
966
+ function pointsFor(def, samples) {
967
+ return ptsFrom(samples, def.pick);
968
+ }
969
+
970
+ // src/components/machines/machine-detail.tsx
971
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
972
+ function KindIcon2({ kind, session }) {
973
+ if (session) return /* @__PURE__ */ jsx7(ServerIcon2, { className: "size-4 text-og-fg-subtle", "aria-hidden": true });
974
+ if (kind === "modal") return /* @__PURE__ */ jsx7(CpuIcon2, { className: "size-4 text-og-fg-subtle", "aria-hidden": true });
975
+ return /* @__PURE__ */ jsx7(LaptopIcon2, { className: "size-4 text-og-fg-subtle", "aria-hidden": true });
976
+ }
977
+ function MachineDetail({
978
+ machine,
979
+ series,
980
+ window: window2,
981
+ onWindowChange,
982
+ loadingSeries,
983
+ onBack,
984
+ now = Date.now(),
985
+ className
986
+ }) {
987
+ const health = deriveHealth(machine.state, machine.metrics, now);
988
+ const tone = HEALTH_TOKEN[health.level];
989
+ const latest = machine.metrics;
990
+ const sampledAgo = latest ? formatRelativeTime(latest.sampledAt, new Date(now)) : null;
991
+ const visible = METRICS.filter((m) => !(m.key === "gpu" && latest?.gpuUtilPct == null));
992
+ const tileCols = visible.length >= 5 ? "grid-cols-2 sm:grid-cols-3 lg:grid-cols-5" : "grid-cols-2 sm:grid-cols-4";
993
+ return /* @__PURE__ */ jsxs7("div", { className: cn("og-root mx-auto flex w-full max-w-5xl flex-col gap-5", className), children: [
994
+ /* @__PURE__ */ jsxs7("div", { className: "flex items-center justify-between gap-3", children: [
995
+ /* @__PURE__ */ jsxs7("div", { className: "flex min-w-0 items-center gap-2.5", children: [
996
+ onBack && /* @__PURE__ */ jsx7(
997
+ "button",
998
+ {
999
+ type: "button",
1000
+ onClick: onBack,
1001
+ className: "flex size-7 shrink-0 items-center justify-center rounded-og-sm border border-og-border bg-og-surface-1 text-og-fg-muted transition-colors hover:bg-og-surface-2 hover:text-og-fg",
1002
+ "aria-label": "Back to machines",
1003
+ children: /* @__PURE__ */ jsx7(ArrowLeftIcon, { className: "size-3.5", "aria-hidden": true })
1004
+ }
1005
+ ),
1006
+ /* @__PURE__ */ jsx7(KindIcon2, { kind: machine.kind, session: machine.isSessionGroup }),
1007
+ /* @__PURE__ */ jsx7("h1", { className: "truncate text-og-md font-semibold text-og-fg", children: machine.name }),
1008
+ /* @__PURE__ */ jsxs7("span", { className: "hidden shrink-0 rounded-og-sm border border-og-border bg-og-surface-1 px-1.5 py-0.5 font-og-mono text-og-xs text-og-fg-subtle sm:inline", children: [
1009
+ machine.os,
1010
+ " \xB7 ",
1011
+ machine.arch
1012
+ ] })
1013
+ ] }),
1014
+ /* @__PURE__ */ jsx7("div", { className: "flex shrink-0 items-center gap-0.5 rounded-og-md border border-og-border bg-og-surface-1 p-0.5", children: METRIC_WINDOWS.map((w) => /* @__PURE__ */ jsx7(
1015
+ "button",
1016
+ {
1017
+ type: "button",
1018
+ onClick: () => onWindowChange(w),
1019
+ "data-active": w === window2,
1020
+ className: cn(
1021
+ "rounded-og-sm px-2 py-1 font-og-mono text-og-xs transition-colors",
1022
+ w === window2 ? "bg-og-surface-3 text-og-fg shadow-og-sm" : "text-og-fg-subtle hover:text-og-fg-muted"
1023
+ ),
1024
+ children: w
1025
+ },
1026
+ w
1027
+ )) })
1028
+ ] }),
1029
+ /* @__PURE__ */ jsxs7(
1030
+ "div",
1031
+ {
1032
+ className: cn(
1033
+ "relative flex flex-col gap-4 overflow-hidden rounded-og-lg border p-5 shadow-og-sm",
1034
+ tone.soft
1035
+ ),
1036
+ children: [
1037
+ /* @__PURE__ */ jsx7("span", { "aria-hidden": true, className: cn("absolute inset-y-0 left-0 w-1", tone.dot) }),
1038
+ /* @__PURE__ */ jsxs7("div", { className: "flex flex-wrap items-center justify-between gap-3 pl-1", children: [
1039
+ /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-3", children: [
1040
+ /* @__PURE__ */ jsxs7("span", { className: "relative flex size-3", children: [
1041
+ healthPulses(health.level) && /* @__PURE__ */ jsx7(
1042
+ "span",
1043
+ {
1044
+ className: cn(
1045
+ "absolute inline-flex size-full animate-og-pulse rounded-full opacity-60",
1046
+ tone.dot
1047
+ )
1048
+ }
1049
+ ),
1050
+ /* @__PURE__ */ jsx7("span", { className: cn("relative inline-flex size-3 rounded-full", tone.dot) })
1051
+ ] }),
1052
+ /* @__PURE__ */ jsxs7("div", { className: "flex flex-col", children: [
1053
+ /* @__PURE__ */ jsx7("span", { className: cn("text-og-md font-semibold", tone.text), children: health.label }),
1054
+ /* @__PURE__ */ jsx7("span", { className: "text-og-sm text-og-fg-muted", children: health.reason })
1055
+ ] })
1056
+ ] }),
1057
+ /* @__PURE__ */ jsxs7("div", { className: "flex items-start gap-5 pr-1", children: [
1058
+ sampledAgo && /* @__PURE__ */ jsxs7("div", { className: "flex flex-col items-end gap-0.5", children: [
1059
+ /* @__PURE__ */ jsx7("span", { className: "text-[10px] font-medium uppercase tracking-wide text-og-fg-subtle", children: "Sampled" }),
1060
+ /* @__PURE__ */ jsxs7("span", { className: "flex items-center gap-1 font-og-mono text-og-sm tabular-nums text-og-fg", children: [
1061
+ /* @__PURE__ */ jsx7(
1062
+ RadioIcon,
1063
+ {
1064
+ className: cn("size-3", health.stale ? "text-og-fg-subtle" : tone.text),
1065
+ "aria-hidden": true
1066
+ }
1067
+ ),
1068
+ sampledAgo === "now" ? "live" : `${sampledAgo} ago`
1069
+ ] })
1070
+ ] }),
1071
+ machine.lastSeenAt && /* @__PURE__ */ jsxs7("div", { className: "hidden flex-col items-end gap-0.5 sm:flex", children: [
1072
+ /* @__PURE__ */ jsx7("span", { className: "text-[10px] font-medium uppercase tracking-wide text-og-fg-subtle", children: "Last seen" }),
1073
+ /* @__PURE__ */ jsx7("span", { className: "font-og-mono text-og-sm tabular-nums text-og-fg-muted", children: formatRelativeTime(machine.lastSeenAt, new Date(now)) })
1074
+ ] })
1075
+ ] })
1076
+ ] }),
1077
+ latest && /* @__PURE__ */ jsx7(
1078
+ "div",
1079
+ {
1080
+ className: cn(
1081
+ "grid gap-px overflow-hidden rounded-og-md border border-og-border bg-og-border",
1082
+ tileCols
1083
+ ),
1084
+ children: visible.map((m) => {
1085
+ const pts = pointsFor(m, series);
1086
+ return /* @__PURE__ */ jsxs7("div", { className: "flex flex-col gap-1.5 bg-og-surface-1 p-3", children: [
1087
+ /* @__PURE__ */ jsx7("span", { className: "text-[10px] font-medium uppercase tracking-wide text-og-fg-subtle", children: m.title }),
1088
+ /* @__PURE__ */ jsx7("span", { className: "font-og-mono text-og-md font-medium tabular-nums text-og-fg", children: m.current(latest) }),
1089
+ /* @__PURE__ */ jsx7(MetricSparkline, { points: pts, color: m.color, yMax: m.yMax, height: 22 }),
1090
+ m.sub && /* @__PURE__ */ jsx7("span", { className: "truncate font-og-mono text-[10px] text-og-fg-subtle", children: m.sub(latest) })
1091
+ ] }, m.key);
1092
+ })
1093
+ }
1094
+ )
1095
+ ]
1096
+ }
1097
+ ),
1098
+ /* @__PURE__ */ jsx7("div", { className: "grid grid-cols-1 gap-3 lg:grid-cols-2", children: visible.map((m) => {
1099
+ const pts = pointsFor(m, series);
1100
+ return /* @__PURE__ */ jsxs7(
1101
+ "div",
1102
+ {
1103
+ className: "flex flex-col gap-3 rounded-og-lg border border-og-border bg-og-surface-1 p-4 shadow-og-sm",
1104
+ children: [
1105
+ /* @__PURE__ */ jsxs7("div", { className: "flex items-baseline justify-between", children: [
1106
+ /* @__PURE__ */ jsx7("span", { className: "text-og-sm font-medium text-og-fg", children: m.title }),
1107
+ latest && /* @__PURE__ */ jsx7("span", { className: "font-og-mono text-og-sm tabular-nums text-og-fg-muted", children: m.current(latest) })
1108
+ ] }),
1109
+ /* @__PURE__ */ jsx7(
1110
+ MetricHistoryChart,
1111
+ {
1112
+ points: pts,
1113
+ yMax: m.yMax,
1114
+ unit: m.unit,
1115
+ color: m.color,
1116
+ thresholds: m.thresholds,
1117
+ rangeLabel: WINDOW_LABEL[window2],
1118
+ height: 128,
1119
+ className: cn(loadingSeries && "opacity-50 transition-opacity")
1120
+ }
1121
+ )
1122
+ ]
1123
+ },
1124
+ m.key
1125
+ );
1126
+ }) })
1127
+ ] });
1128
+ }
1129
+
1130
+ // src/components/machines-dashboard.tsx
1131
+ import { LaptopIcon as LaptopIcon3, PlusIcon, RefreshCwIcon } from "lucide-react";
1132
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1133
+ function EmptyState({ onEnroll }) {
1134
+ return /* @__PURE__ */ jsxs8(
1135
+ "div",
1136
+ {
1137
+ "data-machines-empty": true,
1138
+ className: "flex flex-col items-center justify-center gap-3 rounded-og-lg border border-dashed border-og-border bg-og-surface-1 px-6 py-12 text-center",
1139
+ children: [
1140
+ /* @__PURE__ */ jsx8("span", { className: "flex size-10 items-center justify-center rounded-full bg-og-surface-2 text-og-fg-subtle", children: /* @__PURE__ */ jsx8(LaptopIcon3, { className: "size-5", "aria-hidden": true }) }),
1141
+ /* @__PURE__ */ jsxs8("div", { className: "space-y-1", children: [
1142
+ /* @__PURE__ */ jsx8("p", { className: "text-og-base font-medium text-og-fg", children: "No machines yet" }),
1143
+ /* @__PURE__ */ jsx8("p", { className: "max-w-xs text-og-sm text-og-fg-muted", children: "Enroll your own computer to run the agent on it \u2014 your files, your terminal, your desktop." })
1144
+ ] }),
1145
+ onEnroll ? /* @__PURE__ */ jsxs8(
1146
+ "button",
1147
+ {
1148
+ type: "button",
1149
+ "data-enroll-cta": true,
1150
+ onClick: onEnroll,
1151
+ className: "inline-flex items-center gap-1.5 rounded-og-sm bg-og-accent px-3 py-1.5 text-og-sm font-medium text-og-accent-fg transition-colors hover:bg-og-accent-strong pointer-coarse:min-h-11",
1152
+ children: [
1153
+ /* @__PURE__ */ jsx8(PlusIcon, { className: "size-3.5", "aria-hidden": true }),
1154
+ "Enroll a machine"
1155
+ ]
1156
+ }
1157
+ ) : null
1158
+ ]
1159
+ }
1160
+ );
1161
+ }
1162
+ function Header({
1163
+ count,
1164
+ onEnroll,
1165
+ onRefresh,
1166
+ loading
1167
+ }) {
1168
+ return /* @__PURE__ */ jsxs8("div", { className: "flex items-center justify-between gap-3", children: [
1169
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-baseline gap-2", children: [
1170
+ /* @__PURE__ */ jsx8("h2", { className: "text-og-base font-semibold text-og-fg", children: "Machines" }),
1171
+ /* @__PURE__ */ jsx8("span", { className: "font-og-mono text-og-xs text-og-fg-subtle", children: count })
1172
+ ] }),
1173
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-1.5", children: [
1174
+ onRefresh ? /* @__PURE__ */ jsx8(
1175
+ "button",
1176
+ {
1177
+ type: "button",
1178
+ "data-refresh": true,
1179
+ onClick: onRefresh,
1180
+ title: "Refresh",
1181
+ className: "rounded-og-sm p-1.5 text-og-fg-subtle transition-colors hover:bg-og-surface-2 hover:text-og-fg",
1182
+ children: /* @__PURE__ */ jsx8(RefreshCwIcon, { className: cn("size-3.5", loading && "animate-og-spin"), "aria-hidden": true })
1183
+ }
1184
+ ) : null,
1185
+ onEnroll ? /* @__PURE__ */ jsxs8(
1186
+ "button",
1187
+ {
1188
+ type: "button",
1189
+ "data-enroll-cta": true,
1190
+ onClick: onEnroll,
1191
+ className: "inline-flex items-center gap-1.5 rounded-og-sm border border-og-border px-2.5 py-1 text-og-sm font-medium text-og-fg-muted transition-colors hover:border-og-border-strong hover:text-og-fg pointer-coarse:min-h-11",
1192
+ children: [
1193
+ /* @__PURE__ */ jsx8(PlusIcon, { className: "size-3.5", "aria-hidden": true }),
1194
+ "Enroll machine"
1195
+ ]
1196
+ }
1197
+ ) : null
1198
+ ] })
1199
+ ] });
1200
+ }
1201
+ function MachinesDashboard({
1202
+ machines,
1203
+ activeSandboxId,
1204
+ loading,
1205
+ error,
1206
+ onAttach,
1207
+ attachingSandboxId,
1208
+ seriesByMachine,
1209
+ onOpenDetail,
1210
+ now,
1211
+ onEnroll,
1212
+ onRefresh,
1213
+ className
1214
+ }) {
1215
+ const isEmpty = !loading && !error && machines.length === 0;
1216
+ return /* @__PURE__ */ jsxs8("section", { "data-machines-dashboard": true, className: cn("og-root flex flex-col gap-3", className), children: [
1217
+ /* @__PURE__ */ jsx8(Header, { count: machines.length, onEnroll, onRefresh, loading }),
1218
+ error ? /* @__PURE__ */ jsxs8(
1219
+ "div",
1220
+ {
1221
+ "data-machines-error": true,
1222
+ className: "flex flex-wrap items-center justify-between gap-2 rounded-og-md border border-og-status-failed/30 bg-og-status-failed/10 px-3 py-2 text-og-sm text-og-status-failed",
1223
+ children: [
1224
+ /* @__PURE__ */ jsxs8("span", { children: [
1225
+ "Could not load machines: ",
1226
+ error.message
1227
+ ] }),
1228
+ onRefresh ? /* @__PURE__ */ jsxs8(
1229
+ "button",
1230
+ {
1231
+ type: "button",
1232
+ "data-machines-retry": true,
1233
+ onClick: onRefresh,
1234
+ disabled: loading,
1235
+ className: "inline-flex items-center gap-1.5 rounded-og-sm border border-og-status-failed/30 px-2 py-1 text-og-xs font-medium transition-colors hover:border-og-status-failed/50 disabled:cursor-not-allowed disabled:opacity-60 pointer-coarse:min-h-11",
1236
+ children: [
1237
+ /* @__PURE__ */ jsx8(RefreshCwIcon, { className: cn("size-3.5", loading && "animate-og-spin"), "aria-hidden": true }),
1238
+ loading ? "Retrying\u2026" : "Retry"
1239
+ ]
1240
+ }
1241
+ ) : null
1242
+ ]
1243
+ }
1244
+ ) : null,
1245
+ loading && machines.length === 0 ? /* @__PURE__ */ jsx8("div", { "data-machines-loading": true, className: "grid gap-3 sm:grid-cols-2", children: [0, 1].map((i) => /* @__PURE__ */ jsx8(
1246
+ "div",
1247
+ {
1248
+ className: "h-36 animate-og-pulse rounded-og-lg border border-og-border bg-og-surface-1"
1249
+ },
1250
+ i
1251
+ )) }) : isEmpty ? /* @__PURE__ */ jsx8(EmptyState, { onEnroll }) : /* @__PURE__ */ jsx8("div", { className: "grid gap-3 sm:grid-cols-2 xl:grid-cols-3", "data-machines-grid": true, children: machines.map((machine) => /* @__PURE__ */ jsx8(
1252
+ MachineCard,
1253
+ {
1254
+ machine: {
1255
+ ...machine,
1256
+ active: machine.active || machine.sandboxId === activeSandboxId
1257
+ },
1258
+ onAttach,
1259
+ attaching: attachingSandboxId === machine.sandboxId,
1260
+ series: seriesByMachine?.[machine.sandboxId],
1261
+ onOpenDetail,
1262
+ now
1263
+ },
1264
+ machine.sandboxId
1265
+ )) })
1266
+ ] });
1267
+ }
1268
+
1269
+ // src/components/machine-dock-bar.tsx
1270
+ import { LaptopIcon as LaptopIcon4, CpuIcon as CpuIcon3, UsersIcon as UsersIcon2 } from "lucide-react";
1271
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1272
+ function MachineDockBar({ name, kind, state, className }) {
1273
+ const Icon = kind === "selfhosted" ? LaptopIcon4 : CpuIcon3;
1274
+ const stateBadge = MACHINE_STATE_BADGE_META[state];
1275
+ return /* @__PURE__ */ jsxs9(
1276
+ "div",
1277
+ {
1278
+ "data-machine-dock-bar": true,
1279
+ className: cn(
1280
+ "flex shrink-0 items-center justify-between gap-2 border-b border-og-border bg-og-surface-1 px-2.5 py-1",
1281
+ className
1282
+ ),
1283
+ children: [
1284
+ /* @__PURE__ */ jsxs9("div", { className: "flex min-w-0 items-center gap-1.5", children: [
1285
+ /* @__PURE__ */ jsx9(Icon, { className: "size-3.5 shrink-0 text-og-fg-subtle", "aria-hidden": true }),
1286
+ /* @__PURE__ */ jsx9("span", { className: "truncate text-[11px] font-medium text-og-fg", children: name }),
1287
+ stateBadge ? /* @__PURE__ */ jsx9(
1288
+ "span",
1289
+ {
1290
+ "data-state-badge": state,
1291
+ className: cn(
1292
+ "shrink-0 rounded-full border px-1.5 py-px text-[10px] font-medium",
1293
+ stateBadge.badgeClassName
1294
+ ),
1295
+ children: stateBadge.label
1296
+ }
1297
+ ) : null
1298
+ ] }),
1299
+ /* @__PURE__ */ jsx9(
1300
+ ConnectionStatusPill,
1301
+ {
1302
+ status: connectionStatusForState(state),
1303
+ size: "sm",
1304
+ className: "shrink-0"
1305
+ }
1306
+ )
1307
+ ]
1308
+ }
1309
+ );
1310
+ }
1311
+ function SharedMachineDisclosure({
1312
+ sharedSessionCount,
1313
+ density = "compact",
1314
+ className
1315
+ }) {
1316
+ const others = Math.max(0, sharedSessionCount - 1);
1317
+ return /* @__PURE__ */ jsxs9(
1318
+ "div",
1319
+ {
1320
+ "data-shared-disclosure": true,
1321
+ className: cn(
1322
+ "flex items-center gap-1.5 border-og-accent/25 bg-og-accent-soft text-og-fg-muted",
1323
+ density === "full" ? "rounded-og-md border px-3 py-2 text-[12px]" : "border-b px-2.5 py-1 text-[11px]",
1324
+ className
1325
+ ),
1326
+ children: [
1327
+ /* @__PURE__ */ jsx9(UsersIcon2, { className: "size-3.5 shrink-0 text-og-accent", "aria-hidden": true }),
1328
+ /* @__PURE__ */ jsxs9("span", { children: [
1329
+ "Shared \u2014 ",
1330
+ others === 1 ? "another session is" : `${others} other sessions are`,
1331
+ " on this machine. They see the same terminal, files, and desktop."
1332
+ ] })
1333
+ ]
1334
+ }
1335
+ );
1336
+ }
1337
+
1338
+ // src/components/enrollment-device-flow.tsx
1339
+ import { CopyIcon, ExternalLinkIcon, LaptopIcon as LaptopIcon5, TerminalIcon } from "lucide-react";
1340
+ import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
1341
+ var PHASE_TO_STATE = {
1342
+ pending: "enrolling",
1343
+ authorized: "online",
1344
+ denied: "offline",
1345
+ expired: "offline",
1346
+ disabled: "offline"
1347
+ };
1348
+ function PhaseLabel({ phase }) {
1349
+ const text = {
1350
+ pending: "Waiting for approval",
1351
+ authorized: "Connected",
1352
+ denied: "Denied",
1353
+ expired: "Code expired",
1354
+ disabled: "Enrollment disabled"
1355
+ };
1356
+ return /* @__PURE__ */ jsx10("span", { children: text[phase] });
1357
+ }
1358
+ function EnrollmentDeviceFlow({
1359
+ userCode,
1360
+ verificationUri,
1361
+ verificationUriComplete,
1362
+ installCommand,
1363
+ phase = "pending",
1364
+ expiresInSeconds,
1365
+ onCopyCode,
1366
+ onCopyInstall,
1367
+ onOpenVerification,
1368
+ className
1369
+ }) {
1370
+ const href = verificationUriComplete ?? verificationUri;
1371
+ const codeIsPlaceholder = !/[A-Za-z0-9]/.test(userCode);
1372
+ const awaitingCode = phase === "pending" && codeIsPlaceholder;
1373
+ return /* @__PURE__ */ jsxs10(
1374
+ "div",
1375
+ {
1376
+ "data-enrollment-device-flow": true,
1377
+ className: cn(
1378
+ "og-root flex w-full max-w-md flex-col gap-4 rounded-og-lg border border-og-border bg-og-surface-1 p-5 shadow-og-sm",
1379
+ className
1380
+ ),
1381
+ children: [
1382
+ /* @__PURE__ */ jsxs10("div", { className: "flex items-center justify-between gap-3", children: [
1383
+ /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2", children: [
1384
+ /* @__PURE__ */ jsx10("span", { className: "flex size-7 items-center justify-center rounded-full bg-og-surface-2 text-og-fg-muted", children: /* @__PURE__ */ jsx10(LaptopIcon5, { className: "size-4", "aria-hidden": true }) }),
1385
+ /* @__PURE__ */ jsx10("h2", { className: "text-sm font-semibold text-og-fg", children: "Connect a machine" })
1386
+ ] }),
1387
+ /* @__PURE__ */ jsx10(
1388
+ ConnectionStatusPill,
1389
+ {
1390
+ status: PHASE_TO_STATE[phase] === "online" ? "online" : PHASE_TO_STATE[phase] === "offline" ? "offline" : "reconnecting",
1391
+ label: /* @__PURE__ */ jsx10(PhaseLabel, { phase }),
1392
+ size: "sm"
1393
+ }
1394
+ )
1395
+ ] }),
1396
+ installCommand ? /* @__PURE__ */ jsxs10("div", { className: "flex flex-col gap-1.5", children: [
1397
+ /* @__PURE__ */ jsx10("span", { className: "text-[11px] font-medium uppercase tracking-wide text-og-fg-subtle", children: "1 \xB7 Run on the machine" }),
1398
+ /* @__PURE__ */ jsxs10("div", { className: "flex items-center justify-between gap-2 overflow-hidden rounded-og-md border border-og-border bg-og-bg px-2.5 py-1.5", children: [
1399
+ /* @__PURE__ */ jsxs10("code", { className: "flex min-w-0 items-center gap-2 overflow-x-auto font-og-mono text-[12px] text-og-fg", children: [
1400
+ /* @__PURE__ */ jsx10(TerminalIcon, { className: "size-3.5 shrink-0 text-og-fg-subtle", "aria-hidden": true }),
1401
+ installCommand
1402
+ ] }),
1403
+ /* @__PURE__ */ jsx10(
1404
+ "button",
1405
+ {
1406
+ type: "button",
1407
+ "data-copy-install": true,
1408
+ onClick: onCopyInstall,
1409
+ title: "Copy command",
1410
+ className: "shrink-0 rounded-og-sm p-1.5 text-og-fg-subtle transition-colors hover:bg-og-surface-2 hover:text-og-fg",
1411
+ children: /* @__PURE__ */ jsx10(CopyIcon, { className: "size-4", "aria-hidden": true })
1412
+ }
1413
+ )
1414
+ ] })
1415
+ ] }) : null,
1416
+ /* @__PURE__ */ jsxs10("div", { className: "flex flex-col gap-1.5", children: [
1417
+ /* @__PURE__ */ jsx10("span", { className: "text-[11px] font-medium uppercase tracking-wide text-og-fg-subtle", children: installCommand ? "2 \xB7 Approve the machine" : "Approve the machine" }),
1418
+ /* @__PURE__ */ jsxs10("div", { className: "flex items-center justify-between gap-3 rounded-og-md border border-og-border bg-og-bg px-3 py-2.5", children: [
1419
+ awaitingCode ? /* @__PURE__ */ jsx10("span", { "data-user-code": true, className: "font-og-mono text-[13px] text-og-fg-subtle", children: "Run the command above \u2014 your code appears here" }) : /* @__PURE__ */ jsx10(
1420
+ "span",
1421
+ {
1422
+ "data-user-code": true,
1423
+ className: "select-all font-og-mono text-2xl font-semibold tracking-[0.25em] text-og-fg",
1424
+ children: userCode
1425
+ }
1426
+ ),
1427
+ /* @__PURE__ */ jsx10(
1428
+ "button",
1429
+ {
1430
+ type: "button",
1431
+ "data-copy-code": true,
1432
+ onClick: onCopyCode,
1433
+ disabled: awaitingCode,
1434
+ title: "Copy code",
1435
+ className: "shrink-0 rounded-og-sm p-1.5 text-og-fg-subtle transition-colors hover:bg-og-surface-2 hover:text-og-fg disabled:cursor-not-allowed disabled:opacity-40 disabled:hover:bg-transparent disabled:hover:text-og-fg-subtle",
1436
+ children: /* @__PURE__ */ jsx10(CopyIcon, { className: "size-4", "aria-hidden": true })
1437
+ }
1438
+ )
1439
+ ] })
1440
+ ] }),
1441
+ /* @__PURE__ */ jsxs10(
1442
+ "a",
1443
+ {
1444
+ href,
1445
+ "data-open-verification": true,
1446
+ target: "_blank",
1447
+ rel: "noreferrer",
1448
+ onClick: onOpenVerification,
1449
+ className: "inline-flex items-center justify-center gap-1.5 rounded-og-sm bg-og-accent px-3 py-2 text-sm font-medium text-og-accent-fg transition-colors hover:bg-og-accent-strong",
1450
+ children: [
1451
+ "Open approval page",
1452
+ /* @__PURE__ */ jsx10(ExternalLinkIcon, { className: "size-3.5", "aria-hidden": true })
1453
+ ]
1454
+ }
1455
+ ),
1456
+ /* @__PURE__ */ jsx10("p", { className: "text-center text-[11px] text-og-fg-subtle", children: awaitingCode ? /* @__PURE__ */ jsxs10(Fragment2, { children: [
1457
+ "The one-liner prints a short code. Open",
1458
+ " ",
1459
+ /* @__PURE__ */ jsx10("span", { className: "font-og-mono text-og-fg-muted", children: verificationUri }),
1460
+ " and confirm the code shown there matches."
1461
+ ] }) : /* @__PURE__ */ jsxs10(Fragment2, { children: [
1462
+ "Confirm code ",
1463
+ /* @__PURE__ */ jsx10("span", { className: "font-og-mono text-og-fg-muted", children: userCode }),
1464
+ " \u2014 the one printed by the one-liner \u2014 at",
1465
+ " ",
1466
+ /* @__PURE__ */ jsx10("span", { className: "font-og-mono text-og-fg-muted", children: verificationUri }),
1467
+ ".",
1468
+ typeof expiresInSeconds === "number" ? /* @__PURE__ */ jsxs10(Fragment2, { children: [
1469
+ " Expires in ",
1470
+ Math.max(0, Math.round(expiresInSeconds / 60)),
1471
+ " min."
1472
+ ] }) : null
1473
+ ] }) })
1474
+ ]
1475
+ }
1476
+ );
1477
+ }
1478
+
1479
+ // src/components/enrollment-consent.tsx
1480
+ import { useState as useState2 } from "react";
1481
+ import {
1482
+ CircleAlertIcon,
1483
+ LaptopIcon as LaptopIcon6,
1484
+ MonitorIcon as MonitorIcon2,
1485
+ ScreenShareIcon,
1486
+ ShieldAlertIcon,
1487
+ TerminalIcon as TerminalIcon2,
1488
+ UsersIcon as UsersIcon3
1489
+ } from "lucide-react";
1490
+ import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
1491
+ function Capability({ icon, title, body }) {
1492
+ return /* @__PURE__ */ jsxs11("li", { className: "flex items-start gap-2.5", children: [
1493
+ /* @__PURE__ */ jsx11("span", { className: "mt-0.5 shrink-0 text-og-status-failed", children: icon }),
1494
+ /* @__PURE__ */ jsxs11("span", { className: "min-w-0", children: [
1495
+ /* @__PURE__ */ jsx11("span", { className: "block text-og-base font-medium text-og-fg", children: title }),
1496
+ /* @__PURE__ */ jsx11("span", { className: "block text-og-sm text-og-fg-muted", children: body })
1497
+ ] })
1498
+ ] });
1499
+ }
1500
+ function EnrollmentConsent({
1501
+ userCode,
1502
+ machine,
1503
+ phase = "review",
1504
+ onApprove,
1505
+ onDeny,
1506
+ errorMessage,
1507
+ className
1508
+ }) {
1509
+ const [allowScreenControl, setAllowScreenControl] = useState2(machine.requestsScreenControl);
1510
+ const busy = phase === "approving";
1511
+ if (phase === "approved") {
1512
+ return /* @__PURE__ */ jsx11(
1513
+ ConsentResult,
1514
+ {
1515
+ className,
1516
+ tone: "ok",
1517
+ title: "Machine connected",
1518
+ body: `${machine.machineName} is now enrolled in this workspace. You can close this page \u2014 it will appear in your Machines dashboard.`
1519
+ }
1520
+ );
1521
+ }
1522
+ if (phase === "denied") {
1523
+ return /* @__PURE__ */ jsx11(
1524
+ ConsentResult,
1525
+ {
1526
+ className,
1527
+ tone: "muted",
1528
+ title: "Enrollment denied",
1529
+ body: `You declined to connect ${machine.machineName}. The agent has no access to this machine. You can re-run the install one-liner to try again.`
1530
+ }
1531
+ );
1532
+ }
1533
+ if (phase === "error") {
1534
+ return /* @__PURE__ */ jsx11(
1535
+ ConsentResult,
1536
+ {
1537
+ className,
1538
+ tone: "danger",
1539
+ title: "Could not complete enrollment",
1540
+ body: errorMessage ?? "The code may have expired. Re-run the install one-liner on the machine for a fresh code."
1541
+ }
1542
+ );
1543
+ }
1544
+ return /* @__PURE__ */ jsxs11(
1545
+ "div",
1546
+ {
1547
+ "data-enrollment-consent": true,
1548
+ className: cn(
1549
+ "og-root mx-auto flex w-full max-w-md flex-col gap-5 rounded-og-lg border border-og-status-failed/30 bg-og-surface-1 p-6 shadow-og-md",
1550
+ className
1551
+ ),
1552
+ children: [
1553
+ /* @__PURE__ */ jsxs11("div", { className: "flex items-start gap-3", children: [
1554
+ /* @__PURE__ */ jsx11("span", { className: "flex size-9 shrink-0 items-center justify-center rounded-full bg-og-status-failed/15 text-og-status-failed", children: /* @__PURE__ */ jsx11(ShieldAlertIcon, { className: "size-5", "aria-hidden": true }) }),
1555
+ /* @__PURE__ */ jsxs11("div", { className: "min-w-0", children: [
1556
+ /* @__PURE__ */ jsx11("h1", { className: "text-og-md font-semibold text-og-fg", children: "Give the agent your whole machine?" }),
1557
+ /* @__PURE__ */ jsxs11("p", { className: "mt-1 text-og-base text-og-fg-muted", children: [
1558
+ "Approving lets the OpenGeni agent run on",
1559
+ " ",
1560
+ /* @__PURE__ */ jsx11("span", { className: "font-medium text-og-fg", children: machine.machineName }),
1561
+ " with full access. This is your real computer \u2014 not a sandbox."
1562
+ ] })
1563
+ ] })
1564
+ ] }),
1565
+ /* @__PURE__ */ jsxs11("div", { className: "flex flex-wrap items-center gap-2 rounded-og-md border border-og-border bg-og-surface-2 px-3 py-2 text-og-sm", children: [
1566
+ /* @__PURE__ */ jsx11(LaptopIcon6, { className: "size-4 text-og-fg-subtle", "aria-hidden": true }),
1567
+ /* @__PURE__ */ jsx11("span", { className: "font-medium text-og-fg", children: machine.machineName }),
1568
+ /* @__PURE__ */ jsxs11("span", { className: "font-og-mono text-og-fg-subtle", children: [
1569
+ machine.os,
1570
+ "/",
1571
+ machine.arch
1572
+ ] }),
1573
+ /* @__PURE__ */ jsx11("span", { "aria-hidden": true, className: "text-og-fg-subtle", children: "\xB7" }),
1574
+ /* @__PURE__ */ jsxs11("span", { className: "text-og-fg-subtle", children: [
1575
+ "code ",
1576
+ /* @__PURE__ */ jsx11("span", { className: "font-og-mono font-medium text-og-fg", children: userCode })
1577
+ ] })
1578
+ ] }),
1579
+ /* @__PURE__ */ jsxs11("ul", { className: "flex flex-col gap-3", children: [
1580
+ /* @__PURE__ */ jsx11(
1581
+ Capability,
1582
+ {
1583
+ icon: /* @__PURE__ */ jsx11(TerminalIcon2, { className: "size-4", "aria-hidden": true }),
1584
+ title: "Read, write & run anything",
1585
+ body: "The agent can read and modify any file, run any command, and use your git credentials \u2014 as if it were you at the keyboard."
1586
+ }
1587
+ ),
1588
+ machine.canOfferDisplay ? /* @__PURE__ */ jsx11(
1589
+ Capability,
1590
+ {
1591
+ icon: /* @__PURE__ */ jsx11(MonitorIcon2, { className: "size-4", "aria-hidden": true }),
1592
+ title: "See your screen",
1593
+ body: "The agent can capture this machine's display to watch what it is doing."
1594
+ }
1595
+ ) : null,
1596
+ /* @__PURE__ */ jsx11(
1597
+ Capability,
1598
+ {
1599
+ icon: /* @__PURE__ */ jsx11(UsersIcon3, { className: "size-4", "aria-hidden": true }),
1600
+ title: "Shared while connected",
1601
+ body: "Any session in this workspace can use this machine while it is online. Disconnect the agent to revoke access instantly."
1602
+ }
1603
+ )
1604
+ ] }),
1605
+ machine.canOfferDisplay ? /* @__PURE__ */ jsxs11(
1606
+ "label",
1607
+ {
1608
+ "data-screen-control-toggle": true,
1609
+ className: "flex cursor-pointer items-start gap-3 rounded-og-md border border-og-border bg-og-bg px-3 py-2.5",
1610
+ children: [
1611
+ /* @__PURE__ */ jsx11(
1612
+ "input",
1613
+ {
1614
+ type: "checkbox",
1615
+ checked: allowScreenControl,
1616
+ disabled: busy,
1617
+ onChange: (e) => setAllowScreenControl(e.target.checked),
1618
+ className: "mt-0.5 size-4 accent-og-accent"
1619
+ }
1620
+ ),
1621
+ /* @__PURE__ */ jsxs11("span", { className: "min-w-0", children: [
1622
+ /* @__PURE__ */ jsxs11("span", { className: "flex items-center gap-1.5 text-og-base font-medium text-og-fg", children: [
1623
+ /* @__PURE__ */ jsx11(ScreenShareIcon, { className: "size-3.5 text-og-fg-muted", "aria-hidden": true }),
1624
+ "Also let the agent control my mouse & keyboard"
1625
+ ] }),
1626
+ /* @__PURE__ */ jsx11("span", { className: "mt-0.5 block text-og-sm text-og-fg-muted", children: "Optional. Enables computer-use \u2014 the agent can move the pointer, type, and click on this machine's desktop. Leave off to let it only watch." })
1627
+ ] })
1628
+ ]
1629
+ }
1630
+ ) : /* @__PURE__ */ jsxs11("p", { className: "flex items-start gap-2 rounded-og-md border border-og-border bg-og-bg px-3 py-2 text-og-sm text-og-fg-muted", children: [
1631
+ /* @__PURE__ */ jsx11(CircleAlertIcon, { className: "mt-px size-3.5 shrink-0 text-og-fg-subtle", "aria-hidden": true }),
1632
+ "This machine has no display, so screen control isn't available \u2014 files, terminal, and git only."
1633
+ ] }),
1634
+ /* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-2", children: [
1635
+ /* @__PURE__ */ jsx11(
1636
+ "button",
1637
+ {
1638
+ type: "button",
1639
+ "data-deny": true,
1640
+ disabled: busy,
1641
+ onClick: () => onDeny?.(),
1642
+ className: "flex-1 rounded-og-sm border border-og-border px-3 py-2 text-og-sm font-medium text-og-fg-muted transition-colors hover:border-og-border-strong hover:text-og-fg disabled:opacity-50 pointer-coarse:min-h-11",
1643
+ children: "Cancel"
1644
+ }
1645
+ ),
1646
+ /* @__PURE__ */ jsx11(
1647
+ "button",
1648
+ {
1649
+ type: "button",
1650
+ "data-approve": true,
1651
+ disabled: busy,
1652
+ onClick: () => onApprove?.(allowScreenControl),
1653
+ className: "flex-1 rounded-og-sm bg-og-status-failed px-3 py-2 text-og-sm font-semibold text-og-accent-fg transition-colors hover:opacity-90 disabled:opacity-50 pointer-coarse:min-h-11",
1654
+ children: busy ? "Connecting\u2026" : "Grant full access"
1655
+ }
1656
+ )
1657
+ ] })
1658
+ ]
1659
+ }
1660
+ );
1661
+ }
1662
+ function ConsentResult({
1663
+ tone,
1664
+ title,
1665
+ body,
1666
+ className
1667
+ }) {
1668
+ const ring = tone === "ok" ? "border-og-status-running/30" : tone === "danger" ? "border-og-status-failed/30" : "border-og-border";
1669
+ const iconClass = tone === "ok" ? "text-og-status-running" : tone === "danger" ? "text-og-status-failed" : "text-og-fg-subtle";
1670
+ return /* @__PURE__ */ jsxs11(
1671
+ "div",
1672
+ {
1673
+ "data-enrollment-result": tone,
1674
+ className: cn(
1675
+ "og-root mx-auto flex w-full max-w-md flex-col items-center gap-3 rounded-og-lg border bg-og-surface-1 p-6 text-center shadow-og-md",
1676
+ ring,
1677
+ className
1678
+ ),
1679
+ children: [
1680
+ /* @__PURE__ */ jsx11(
1681
+ "span",
1682
+ {
1683
+ className: cn(
1684
+ "flex size-10 items-center justify-center rounded-full bg-og-surface-2",
1685
+ iconClass
1686
+ ),
1687
+ children: /* @__PURE__ */ jsx11(LaptopIcon6, { className: "size-5", "aria-hidden": true })
1688
+ }
1689
+ ),
1690
+ /* @__PURE__ */ jsx11("h1", { className: "text-og-md font-semibold text-og-fg", children: title }),
1691
+ /* @__PURE__ */ jsx11("p", { className: "max-w-sm text-og-base text-og-fg-muted", children: body })
1692
+ ]
1693
+ }
1694
+ );
1695
+ }
1696
+
1697
+ // src/hooks/use-machines.ts
1698
+ import { useCallback as useCallback2, useRef as useRef3, useState as useState4 } from "react";
1699
+
1700
+ // src/provider.tsx
1701
+ import {
1702
+ OpenGeniApiContractMismatchError,
1703
+ OPENGENI_API_CONTRACT_REVISION
1704
+ } from "@opengeni/sdk";
1705
+ import { useCallback, useEffect, useMemo, useRef as useRef2, useState as useState3 } from "react";
1706
+ import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
1707
+ var CONTRACT_RELOAD_STORAGE_PREFIX = "opengeni.reloadForApiContract:";
1708
+ function OpenGeniProvider({
1709
+ client,
1710
+ workspaceId,
1711
+ onWorkspaceControlEvent,
1712
+ children
1713
+ }) {
1714
+ const [workspaceControlEvent, setWorkspaceControlEvent] = useState3(
1715
+ null
1716
+ );
1717
+ const [workspaceControlConnectionState, setWorkspaceControlConnectionState] = useState3("idle");
1718
+ const [contractMismatch, setContractMismatch] = useState3(
1719
+ null
1720
+ );
1721
+ const callbackRef = useRef2(onWorkspaceControlEvent);
1722
+ const reconcilersRef = useRef2(/* @__PURE__ */ new Map());
1723
+ callbackRef.current = onWorkspaceControlEvent;
1724
+ const verifyApiContract = useCallback(async () => {
1725
+ try {
1726
+ const config = await client.getClientConfig();
1727
+ if (config.apiContractRevision !== OPENGENI_API_CONTRACT_REVISION) {
1728
+ throw new OpenGeniApiContractMismatchError(
1729
+ OPENGENI_API_CONTRACT_REVISION,
1730
+ String(config.apiContractRevision || "(missing)")
1731
+ );
1732
+ }
1733
+ } catch (error) {
1734
+ if (error instanceof OpenGeniApiContractMismatchError) {
1735
+ setContractMismatch(error);
1736
+ reloadForContractMismatchOnce(error);
1737
+ }
1738
+ throw error;
1739
+ }
1740
+ }, [client]);
1741
+ const registerSessionReconciler = useMemo(
1742
+ () => (sessionId, key, reconcile) => {
1743
+ const sessionReconcilers = reconcilersRef.current.get(sessionId) ?? /* @__PURE__ */ new Map();
1744
+ sessionReconcilers.set(key, reconcile);
1745
+ reconcilersRef.current.set(sessionId, sessionReconcilers);
1746
+ return () => {
1747
+ const current = reconcilersRef.current.get(sessionId);
1748
+ current?.delete(key);
1749
+ if (current?.size === 0) reconcilersRef.current.delete(sessionId);
1750
+ };
1751
+ },
1752
+ []
1753
+ );
1754
+ const reconcileSession = useMemo(
1755
+ () => async (sessionId) => {
1756
+ await verifyApiContract();
1757
+ const callbacks = [...reconcilersRef.current.get(sessionId)?.values() ?? []];
1758
+ await Promise.all(callbacks.map((reconcile) => reconcile()));
1759
+ },
1760
+ [verifyApiContract]
1761
+ );
1762
+ useEffect(() => {
1763
+ const controller = new AbortController();
1764
+ setWorkspaceControlEvent(null);
1765
+ setWorkspaceControlConnectionState("connecting");
1766
+ void (async () => {
1767
+ try {
1768
+ await verifyApiContract();
1769
+ const workspace = await client.getWorkspace(workspaceId);
1770
+ const stream = client.streamWorkspaceControlEvents(workspaceId, {
1771
+ after: workspace.inferenceControl.revision,
1772
+ signal: controller.signal,
1773
+ onStateChange: setWorkspaceControlConnectionState
1774
+ });
1775
+ for await (const event of stream) {
1776
+ if (controller.signal.aborted) return;
1777
+ setWorkspaceControlEvent(
1778
+ (current) => !current || event.sequence > current.sequence ? event : current
1779
+ );
1780
+ callbackRef.current?.(event);
1781
+ }
1782
+ } catch (error) {
1783
+ if (error instanceof OpenGeniApiContractMismatchError) {
1784
+ setContractMismatch(error);
1785
+ reloadForContractMismatchOnce(error);
1786
+ }
1787
+ if (!controller.signal.aborted) setWorkspaceControlConnectionState("error");
1788
+ }
1789
+ })();
1790
+ return () => controller.abort();
1791
+ }, [client, verifyApiContract, workspaceId]);
1792
+ const value = useMemo(
1793
+ () => ({
1794
+ client,
1795
+ workspaceId,
1796
+ workspaceControlEvent,
1797
+ workspaceControlConnectionState,
1798
+ registerSessionReconciler,
1799
+ reconcileSession
1800
+ }),
1801
+ [
1802
+ client,
1803
+ workspaceId,
1804
+ workspaceControlEvent,
1805
+ workspaceControlConnectionState,
1806
+ registerSessionReconciler,
1807
+ reconcileSession
1808
+ ]
1809
+ );
1810
+ return /* @__PURE__ */ jsxs12(OpenGeniContext.Provider, { value, children: [
1811
+ children,
1812
+ contractMismatch ? /* @__PURE__ */ jsx12(ApiContractMismatchScreen, { mismatch: contractMismatch }) : null
1813
+ ] });
1814
+ }
1815
+ function ApiContractMismatchScreen({ mismatch }) {
1816
+ return /* @__PURE__ */ jsx12(
1817
+ "div",
1818
+ {
1819
+ className: "og-root fixed inset-0 z-[2147483647] grid place-items-center bg-og-bg/95 p-6 backdrop-blur-sm",
1820
+ role: "alert",
1821
+ "aria-live": "assertive",
1822
+ "data-opengeni-api-contract-mismatch": true,
1823
+ children: /* @__PURE__ */ jsxs12("div", { className: "w-full max-w-md rounded-xl border border-og-border bg-og-surface p-6 shadow-2xl", children: [
1824
+ /* @__PURE__ */ jsx12("p", { className: "text-sm font-semibold text-og-fg", children: "OpenGeni updated" }),
1825
+ /* @__PURE__ */ jsx12("p", { className: "mt-2 text-sm leading-6 text-og-muted", children: "This tab cannot safely continue with the new server version. Reload it before sending or controlling work." }),
1826
+ /* @__PURE__ */ jsxs12("p", { className: "mt-3 font-mono text-xs text-og-subtle", children: [
1827
+ "Client ",
1828
+ mismatch.expected,
1829
+ " \xB7 API ",
1830
+ mismatch.actual
1831
+ ] }),
1832
+ /* @__PURE__ */ jsx12(
1833
+ "button",
1834
+ {
1835
+ type: "button",
1836
+ className: "mt-5 inline-flex h-9 items-center rounded-md bg-og-fg px-3 text-sm font-medium text-og-bg",
1837
+ onClick: () => window.location.reload(),
1838
+ children: "Reload now"
1839
+ }
1840
+ )
1841
+ ] })
1842
+ }
1843
+ );
1844
+ }
1845
+ function reloadForContractMismatchOnce(mismatch) {
1846
+ if (typeof window === "undefined" || typeof sessionStorage === "undefined") return;
1847
+ const key = `${CONTRACT_RELOAD_STORAGE_PREFIX}${mismatch.actual}`;
1848
+ if (sessionStorage.getItem(key) === OPENGENI_API_CONTRACT_REVISION) return;
1849
+ sessionStorage.setItem(key, OPENGENI_API_CONTRACT_REVISION);
1850
+ window.setTimeout(() => window.location.reload(), 150);
1851
+ }
1852
+
1853
+ // src/hooks/use-machines.ts
1854
+ var EMPTY = { activeSandboxId: null, activeEpoch: 0, machines: [] };
1855
+ function useMachines(options = {}) {
1856
+ const { client, workspaceId } = useOpenGeni(options);
1857
+ const machinesClient = options.machinesClient ?? client;
1858
+ const sessionId = options.sessionId;
1859
+ const identityKey = `${workspaceId}\0${sessionId ?? ""}`;
1860
+ const identityRef = useRef3(identityKey);
1861
+ identityRef.current = identityKey;
1862
+ const load = useCallback2(
1863
+ async (signal) => {
1864
+ return await machinesClient.listMachines(workspaceId, {
1865
+ ...sessionId ? { sessionId } : {},
1866
+ ...signal ? { signal } : {}
1867
+ });
1868
+ },
1869
+ [machinesClient, workspaceId, sessionId]
1870
+ );
1871
+ const {
1872
+ data: loadedData,
1873
+ loading,
1874
+ error,
1875
+ refresh
1876
+ } = usePolledValue(load, {
1877
+ pollIntervalMs: options.pollIntervalMs,
1878
+ enabled: options.enabled
1879
+ });
1880
+ const { run, mutating, mutationError, clearMutationError } = useMutationRunner(identityKey);
1881
+ const [attachState, setAttachState] = useState4(() => ({ identity: identityKey, sandboxId: null }));
1882
+ const attachingSandboxId = attachState.identity === identityKey ? attachState.sandboxId : null;
1883
+ const data = loadedData ?? EMPTY;
1884
+ const canAttach = sessionId !== void 0 && (typeof machinesClient.attachMachine === "function" || typeof machinesClient.swapActiveSandbox === "function");
1885
+ const attach = useCallback2(
1886
+ async (sandboxId) => {
1887
+ if (sessionId === void 0) return false;
1888
+ const runSwap = machinesClient.attachMachine ? () => machinesClient.attachMachine(workspaceId, sessionId, sandboxId) : machinesClient.swapActiveSandbox ? () => machinesClient.swapActiveSandbox(workspaceId, sessionId, { target: sandboxId }) : null;
1889
+ if (!runSwap) return false;
1890
+ const ownedIdentity = identityKey;
1891
+ setAttachState({ identity: ownedIdentity, sandboxId });
1892
+ const result = await run(async () => {
1893
+ await runSwap();
1894
+ return true;
1895
+ });
1896
+ if (identityRef.current === ownedIdentity) {
1897
+ setAttachState({ identity: ownedIdentity, sandboxId: null });
1898
+ if (result) await refresh();
1899
+ }
1900
+ return result === true;
1901
+ },
1902
+ [machinesClient, workspaceId, sessionId, identityKey, run, refresh]
1903
+ );
1904
+ const fetchSeries = useCallback2(
1905
+ async (enrollmentId, window2 = "1h") => {
1906
+ if (!machinesClient.machineMetricsSeries) return [];
1907
+ return await machinesClient.machineMetricsSeries(workspaceId, enrollmentId, { window: window2 });
1908
+ },
1909
+ [machinesClient, workspaceId]
1910
+ );
1911
+ return {
1912
+ machines: data.machines,
1913
+ activeSandboxId: data.activeSandboxId,
1914
+ activeEpoch: data.activeEpoch,
1915
+ loading,
1916
+ error,
1917
+ refresh,
1918
+ attach,
1919
+ canAttach,
1920
+ fetchSeries,
1921
+ attaching: mutating,
1922
+ attachingSandboxId,
1923
+ mutationError,
1924
+ clearMutationError
1925
+ };
1926
+ }
1927
+
1928
+ export {
1929
+ OpenGeniProvider,
1930
+ useMachines,
1931
+ connectionStatusForState,
1932
+ CONNECTION_STATUS_META,
1933
+ MACHINE_STATE_BADGE_META,
1934
+ ConnectionStatusPill,
1935
+ ConnectionDot,
1936
+ MachineStatusPill,
1937
+ MachineDockBar,
1938
+ SharedMachineDisclosure,
1939
+ MachineMetrics,
1940
+ deriveHealth,
1941
+ HEALTH_TOKEN,
1942
+ healthPulses,
1943
+ MachineHealthPill,
1944
+ MetricSparkline,
1945
+ MachineCard,
1946
+ MetricHistoryChart,
1947
+ METRIC_WINDOWS,
1948
+ WINDOW_LABEL,
1949
+ METRICS,
1950
+ pointsFor,
1951
+ MachineDetail,
1952
+ MachinesDashboard,
1953
+ EnrollmentDeviceFlow,
1954
+ EnrollmentConsent
1955
+ };
1956
+ //# sourceMappingURL=chunk-6BXBGJ3B.js.map