@opengeni/react 1.0.1 → 2.3.0-canary.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 (174) hide show
  1. package/README.md +33 -10
  2. package/dist/artifacts-spreadsheet.js +1 -1
  3. package/dist/artifacts.js +1 -1
  4. package/dist/browser-viewer-GXUICIAG.js +14 -0
  5. package/dist/{chunk-R3AZOAB5.js → chunk-4MDLEHXL.js} +9 -115
  6. package/dist/chunk-4MDLEHXL.js.map +1 -0
  7. package/dist/chunk-5YK2ZDIG.js +116 -0
  8. package/dist/chunk-5YK2ZDIG.js.map +1 -0
  9. package/dist/chunk-H5YGYNXS.js +329 -0
  10. package/dist/chunk-H5YGYNXS.js.map +1 -0
  11. package/dist/{chunk-7WCLAEFV.js → chunk-ICA6R6TW.js} +2 -2
  12. package/dist/{chunk-7WCLAEFV.js.map → chunk-ICA6R6TW.js.map} +1 -1
  13. package/dist/{chunk-IGC6A2ZI.js → chunk-JOAVA2TW.js} +589 -217
  14. package/dist/chunk-JOAVA2TW.js.map +1 -0
  15. package/dist/{chunk-Y7OEDUJ5.js → chunk-LS4POUTY.js} +1057 -426
  16. package/dist/chunk-LS4POUTY.js.map +1 -0
  17. package/dist/{chunk-5UQPNLIO.js → chunk-LTCHJ4MS.js} +9 -9
  18. package/dist/chunk-LTCHJ4MS.js.map +1 -0
  19. package/dist/{chunk-C5ZPSSS5.js → chunk-NEXLLTP4.js} +2 -1
  20. package/dist/{chunk-C5ZPSSS5.js.map → chunk-NEXLLTP4.js.map} +1 -1
  21. package/dist/chunk-QGBJ6GHG.js +2300 -0
  22. package/dist/chunk-QGBJ6GHG.js.map +1 -0
  23. package/dist/{chunk-6KLX2RH5.js → chunk-RSXJN73N.js} +365 -21
  24. package/dist/chunk-RSXJN73N.js.map +1 -0
  25. package/dist/{chunk-B6WPZSTT.js → chunk-SE4W4WQ3.js} +78 -16
  26. package/dist/chunk-SE4W4WQ3.js.map +1 -0
  27. package/dist/{chunk-MKPIAGVL.js → chunk-W6NFJXSA.js} +8 -19
  28. package/dist/chunk-W6NFJXSA.js.map +1 -0
  29. package/dist/{chunk-XBZWOM2K.js → chunk-X4A66EWZ.js} +8 -3
  30. package/dist/chunk-X4A66EWZ.js.map +1 -0
  31. package/dist/chunk-X4JAFD5R.js +3907 -0
  32. package/dist/chunk-X4JAFD5R.js.map +1 -0
  33. package/dist/{chunk-HA46CYQK.js → chunk-XXWH2JLC.js} +792 -206
  34. package/dist/chunk-XXWH2JLC.js.map +1 -0
  35. package/dist/client.d.ts +2 -2
  36. package/dist/components/browser-viewer.d.ts +7 -1
  37. package/dist/components/computer-viewer.d.ts +4 -1
  38. package/dist/components/enrollment-consent.d.ts +6 -3
  39. package/dist/components/file-browser.d.ts +7 -1
  40. package/dist/components/human-input-form.d.ts +1 -1
  41. package/dist/components/human-input-surface.d.ts +1 -1
  42. package/dist/components/machine-card.d.ts +4 -1
  43. package/dist/components/machines/machine-detail.d.ts +4 -1
  44. package/dist/components/machines/machine-operation-policy.d.ts +11 -0
  45. package/dist/components/machines-dashboard.d.ts +3 -1
  46. package/dist/components/markdown.d.ts +20 -1
  47. package/dist/components/message-timeline.d.ts +1 -1
  48. package/dist/components/model-policy-picker.d.ts +2 -0
  49. package/dist/components/pierre-file.d.ts +1 -1
  50. package/dist/components/sandbox-files.d.ts +8 -2
  51. package/dist/components/sandbox-workspace.d.ts +33 -3
  52. package/dist/components/session-chrome.d.ts +15 -3
  53. package/dist/components/workspace-dock.d.ts +6 -2
  54. package/dist/composer.js +5 -5
  55. package/dist/computer-viewer-ZOP4Q7A7.js +16 -0
  56. package/dist/computer-viewer-ZOP4Q7A7.js.map +1 -0
  57. package/dist/hooks/channel-cache.d.ts +23 -0
  58. package/dist/hooks/shared-machines-list.d.ts +14 -0
  59. package/dist/hooks/use-attached-browsers.d.ts +2 -1
  60. package/dist/hooks/use-channels.d.ts +1 -0
  61. package/dist/hooks/use-composer.d.ts +37 -9
  62. package/dist/hooks/use-machine-chip.d.ts +3 -3
  63. package/dist/hooks/use-machines.d.ts +19 -1
  64. package/dist/hooks/use-sandbox-files.d.ts +1 -0
  65. package/dist/hooks/use-session-background-commands.d.ts +14 -0
  66. package/dist/hooks/use-turn-queue.d.ts +12 -0
  67. package/dist/hooks/use-workspace-sessions.d.ts +1 -0
  68. package/dist/index.d.ts +7 -5
  69. package/dist/index.js +1096 -623
  70. package/dist/index.js.map +1 -1
  71. package/dist/interaction.js +29 -181
  72. package/dist/interaction.js.map +1 -1
  73. package/dist/lib/interaction-errors.d.ts +3 -0
  74. package/dist/machines.d.ts +4 -2
  75. package/dist/machines.js +10 -2
  76. package/dist/model-policy.d.ts +1 -1
  77. package/dist/model-policy.js +1 -1
  78. package/dist/platform-activity-row-BJHUHXAT.js +169 -0
  79. package/dist/platform-activity-row-BJHUHXAT.js.map +1 -0
  80. package/dist/{queue-surface-implementation-2KVJTYXH.js → queue-surface-implementation-BSKUWHRI.js} +76 -16
  81. package/dist/queue-surface-implementation-BSKUWHRI.js.map +1 -0
  82. package/dist/realtime/realtime-control.d.ts +6 -3
  83. package/dist/realtime.js +36 -14
  84. package/dist/realtime.js.map +1 -1
  85. package/dist/session-ui.d.ts +1 -1
  86. package/dist/session-ui.js +6 -2
  87. package/dist/session.d.ts +2 -2
  88. package/dist/session.js +4 -4
  89. package/dist/timeline/index.d.ts +1 -1
  90. package/dist/timeline/platform-activity-row.d.ts +15 -0
  91. package/dist/timeline/screenshot-lightbox.d.ts +1 -1
  92. package/dist/timeline/shared.d.ts +4 -2
  93. package/dist/timeline/types.d.ts +50 -5
  94. package/dist/types/machines.d.ts +2 -2
  95. package/package.json +3 -3
  96. package/src/client.ts +12 -1
  97. package/src/components/artifacts/editable-spreadsheet.tsx +1 -1
  98. package/src/components/browser-viewer.tsx +361 -58
  99. package/src/components/chat-composer.tsx +1 -1
  100. package/src/components/composer.tsx +6 -6
  101. package/src/components/computer-viewer.tsx +368 -115
  102. package/src/components/enrollment-consent.tsx +64 -6
  103. package/src/components/file-browser.tsx +117 -2
  104. package/src/components/human-input-form.tsx +103 -59
  105. package/src/components/human-input-surface.tsx +1 -1
  106. package/src/components/machine-card.tsx +111 -0
  107. package/src/components/machine-input-display.ts +15 -0
  108. package/src/components/machines/machine-detail.tsx +47 -0
  109. package/src/components/machines/machine-operation-policy.tsx +179 -0
  110. package/src/components/machines-dashboard.tsx +6 -0
  111. package/src/components/markdown.tsx +174 -20
  112. package/src/components/message-timeline.tsx +215 -18
  113. package/src/components/model-policy-picker.tsx +9 -17
  114. package/src/components/pierre-file.tsx +1 -1
  115. package/src/components/queue-surface-implementation.tsx +82 -16
  116. package/src/components/sandbox-files.tsx +135 -19
  117. package/src/components/sandbox-workspace.tsx +204 -92
  118. package/src/components/session-chrome.tsx +415 -208
  119. package/src/components/session-status.tsx +1 -1
  120. package/src/components/user-message-body.tsx +168 -48
  121. package/src/components/workspace-dock.tsx +141 -175
  122. package/src/hooks/channel-cache.ts +104 -0
  123. package/src/hooks/shared-machines-list.ts +239 -0
  124. package/src/hooks/use-attached-browsers.ts +5 -1
  125. package/src/hooks/use-browser-frame-stream.ts +6 -1
  126. package/src/hooks/use-browser-session.ts +6 -4
  127. package/src/hooks/use-browser-sessions.ts +7 -1
  128. package/src/hooks/use-channels.ts +32 -10
  129. package/src/hooks/use-composer.ts +760 -291
  130. package/src/hooks/use-computer-frame-stream.ts +14 -9
  131. package/src/hooks/use-computer-session.ts +16 -14
  132. package/src/hooks/use-computer-sessions.ts +8 -1
  133. package/src/hooks/use-last-started-turn-policy.ts +1 -1
  134. package/src/hooks/use-machine-chip.ts +23 -6
  135. package/src/hooks/use-machines.ts +104 -6
  136. package/src/hooks/use-sandbox-files.ts +195 -140
  137. package/src/hooks/use-sandbox-git.ts +58 -29
  138. package/src/hooks/use-session-background-commands.ts +56 -0
  139. package/src/hooks/use-session-capabilities.ts +18 -0
  140. package/src/hooks/use-turn-queue.ts +111 -12
  141. package/src/hooks/use-workspace-sessions.ts +5 -1
  142. package/src/index.ts +26 -4
  143. package/src/lib/interaction-errors.ts +18 -0
  144. package/src/machines.ts +9 -1
  145. package/src/model-policy.ts +10 -2
  146. package/src/realtime/realtime-control.tsx +53 -17
  147. package/src/session-context.ts +1 -0
  148. package/src/session-ui.ts +6 -1
  149. package/src/session.ts +9 -1
  150. package/src/timeline/activity-rail.tsx +20 -75
  151. package/src/timeline/index.ts +4 -0
  152. package/src/timeline/platform-activity-row.tsx +201 -0
  153. package/src/timeline/projection.ts +529 -25
  154. package/src/timeline/screenshot-lightbox.tsx +48 -14
  155. package/src/timeline/shared.tsx +8 -2
  156. package/src/timeline/tool-renderers.tsx +104 -6
  157. package/src/timeline/turn-summary.tsx +6 -1
  158. package/src/timeline/types.ts +77 -2
  159. package/src/types/machines.ts +10 -0
  160. package/styles/compiled.css +115 -104
  161. package/dist/chunk-5UQPNLIO.js.map +0 -1
  162. package/dist/chunk-6KLX2RH5.js.map +0 -1
  163. package/dist/chunk-B6WPZSTT.js.map +0 -1
  164. package/dist/chunk-HA46CYQK.js.map +0 -1
  165. package/dist/chunk-IGC6A2ZI.js.map +0 -1
  166. package/dist/chunk-LLOLUHVG.js +0 -5937
  167. package/dist/chunk-LLOLUHVG.js.map +0 -1
  168. package/dist/chunk-MKPIAGVL.js.map +0 -1
  169. package/dist/chunk-R3AZOAB5.js.map +0 -1
  170. package/dist/chunk-XBZWOM2K.js.map +0 -1
  171. package/dist/chunk-Y7OEDUJ5.js.map +0 -1
  172. package/dist/interactive-workbench-viewers-TOHBXJEE.js +0 -15
  173. package/dist/queue-surface-implementation-2KVJTYXH.js.map +0 -1
  174. /package/dist/{interactive-workbench-viewers-TOHBXJEE.js.map → browser-viewer-GXUICIAG.js.map} +0 -0
@@ -1,12 +1,11 @@
1
1
  import {
2
2
  useMutationRunner,
3
- usePageLiveActivity,
4
- usePolledValue
3
+ usePageLiveActivity
5
4
  } from "./chunk-KZ73RL76.js";
6
5
  import {
7
6
  OpenGeniContext,
8
7
  useOpenGeni
9
- } from "./chunk-C5ZPSSS5.js";
8
+ } from "./chunk-NEXLLTP4.js";
10
9
  import {
11
10
  formatBytes,
12
11
  formatRelativeTime
@@ -284,11 +283,15 @@ function MachineMetrics({ metrics, density = "compact", className }) {
284
283
 
285
284
  // src/components/machine-card.tsx
286
285
  import {
286
+ AlertTriangleIcon,
287
287
  ArrowRightIcon,
288
288
  CpuIcon,
289
+ CircleCheckIcon,
290
+ DownloadIcon,
289
291
  LaptopIcon,
290
292
  MonitorIcon,
291
293
  MonitorOffIcon,
294
+ LoaderCircleIcon,
292
295
  ServerIcon,
293
296
  UsersIcon
294
297
  } from "lucide-react";
@@ -505,6 +508,8 @@ function MachineCard({
505
508
  machine,
506
509
  onAttach,
507
510
  attaching,
511
+ onUpdateAgent,
512
+ updatingAgent,
508
513
  series,
509
514
  onOpenDetail,
510
515
  now,
@@ -517,6 +522,34 @@ function MachineCard({
517
522
  const sampledAgo = machine.metrics ? formatRelativeTime(machine.metrics.sampledAt, new Date(clockNow)) : null;
518
523
  const cpuPts = (series ?? []).map((s) => ({ t: new Date(s.sampledAt).getTime(), v: s.cpuPct }));
519
524
  const openable = Boolean(onOpenDetail);
525
+ const runtime = machine.runtime;
526
+ const updateRunning = runtime?.versionState === "updating";
527
+ const dispatchConfirmationStalled = runtime?.update?.status === "requested" && clockNow - new Date(runtime.update.updatedAt).getTime() >= 3e4;
528
+ const canUpdate = Boolean(machine.enrollmentId && onUpdateAgent) && !offline && (runtime?.versionState === "outdated" || runtime?.versionState === "update_failed" || dispatchConfirmationStalled);
529
+ const updateLabel = (() => {
530
+ const update = runtime?.update;
531
+ if (!update) return null;
532
+ switch (update.status) {
533
+ case "requested":
534
+ return dispatchConfirmationStalled ? "Agent confirmation delayed" : "Confirming with agent";
535
+ case "accepted":
536
+ return "Update accepted";
537
+ case "waiting_for_idle":
538
+ return "Waiting for current work";
539
+ case "downloading":
540
+ case "verifying":
541
+ case "applying":
542
+ return `${update.status}\u2026`;
543
+ case "restarting":
544
+ return "Restarting agent";
545
+ case "succeeded":
546
+ return `Updated \xB7 v${update.targetVersion}`;
547
+ case "failed": {
548
+ const detail = update.errorCode?.replaceAll("_", " ");
549
+ return update.rolledBack ? `Update rolled back safely${detail ? ` \xB7 ${detail}` : ""}` : `Update failed${detail ? ` \xB7 ${detail}` : ""}`;
550
+ }
551
+ }
552
+ })();
520
553
  return /* @__PURE__ */ jsxs5(
521
554
  "div",
522
555
  {
@@ -556,6 +589,8 @@ function MachineCard({
556
589
  ) : null
557
590
  ] }),
558
591
  /* @__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: [
592
+ /* @__PURE__ */ jsx5("span", { "data-machine-scope": machine.scope, className: "capitalize", children: machine.scope === "user" ? "personal" : machine.scope }),
593
+ /* @__PURE__ */ jsx5("span", { "aria-hidden": true, children: "\xB7" }),
559
594
  /* @__PURE__ */ jsx5("span", { className: "capitalize", children: machine.isSessionGroup ? "session sandbox" : machine.kind }),
560
595
  /* @__PURE__ */ jsx5("span", { "aria-hidden": true, children: "\xB7" }),
561
596
  /* @__PURE__ */ jsxs5("span", { className: "font-og-mono", children: [
@@ -595,6 +630,51 @@ function MachineCard({
595
630
  ]
596
631
  }
597
632
  ) : null,
633
+ machine.connectionAuthority.duplicateRunnerDeniedCount > 0 ? /* @__PURE__ */ jsxs5(
634
+ "p",
635
+ {
636
+ "data-runner-conflict": true,
637
+ className: "flex items-center gap-1.5 rounded-og-md border border-og-status-failed/30 bg-og-status-failed/5 px-2.5 py-1.5 text-og-xs text-og-status-failed",
638
+ children: [
639
+ /* @__PURE__ */ jsx5(AlertTriangleIcon, { className: "size-3.5 shrink-0", "aria-hidden": true }),
640
+ "Competing agent process blocked. Open diagnostics for details."
641
+ ]
642
+ }
643
+ ) : null,
644
+ runtime ? /* @__PURE__ */ jsxs5(
645
+ "div",
646
+ {
647
+ "data-agent-runtime": true,
648
+ className: "flex items-center justify-between gap-3 rounded-og-md border border-og-border/70 bg-og-surface-2/35 px-2.5 py-2 text-og-xs",
649
+ children: [
650
+ /* @__PURE__ */ jsxs5("div", { className: "min-w-0", children: [
651
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-1.5 text-og-fg-muted", children: [
652
+ runtime.versionState === "current" ? /* @__PURE__ */ jsx5(CircleCheckIcon, { className: "size-3.5 text-og-status-idle", "aria-hidden": true }) : updateRunning ? /* @__PURE__ */ jsx5(LoaderCircleIcon, { className: "size-3.5 animate-og-spin text-og-accent", "aria-hidden": true }) : /* @__PURE__ */ jsx5(DownloadIcon, { className: "size-3.5", "aria-hidden": true }),
653
+ /* @__PURE__ */ jsxs5("span", { className: "truncate", children: [
654
+ "Agent",
655
+ " ",
656
+ runtime.installedVersion ? `v${runtime.installedVersion}` : "version unknown"
657
+ ] })
658
+ ] }),
659
+ /* @__PURE__ */ jsx5("p", { className: "mt-0.5 truncate text-og-fg-subtle", children: updateLabel ?? (runtime.versionState === "current" ? `Current \xB7 ${runtime.updateChannel ?? "channel unknown"}` : runtime.desiredVersion ? `Promoted v${runtime.desiredVersion}` : "No promoted version configured") })
660
+ ] }),
661
+ canUpdate ? /* @__PURE__ */ jsx5(
662
+ "button",
663
+ {
664
+ type: "button",
665
+ "data-update-agent": true,
666
+ disabled: updatingAgent,
667
+ onClick: (event) => {
668
+ event.stopPropagation();
669
+ onUpdateAgent?.(machine);
670
+ },
671
+ className: "shrink-0 rounded-og-sm border border-og-border px-2 py-1 font-medium text-og-fg-muted transition-colors hover:border-og-border-strong hover:text-og-fg disabled:cursor-not-allowed disabled:opacity-50 pointer-coarse:min-h-11",
672
+ children: updatingAgent ? "Starting\u2026" : dispatchConfirmationStalled ? "Retry delivery" : runtime.versionState === "update_failed" ? "Retry" : "Update"
673
+ }
674
+ ) : null
675
+ ]
676
+ }
677
+ ) : null,
598
678
  /* @__PURE__ */ jsx5(MachineMetrics, { metrics: machine.metrics }),
599
679
  cpuPts.length > 1 ? /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-1 rounded-og-md bg-og-surface-2/40 p-2.5", children: [
600
680
  /* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between text-[10px] font-medium uppercase tracking-wide text-og-fg-subtle", children: [
@@ -888,6 +968,7 @@ function MetricHistoryChart({
888
968
 
889
969
  // src/components/machines/machine-detail.tsx
890
970
  import {
971
+ AlertTriangleIcon as AlertTriangleIcon3,
891
972
  ArrowLeftIcon,
892
973
  CpuIcon as CpuIcon2,
893
974
  LaptopIcon as LaptopIcon2,
@@ -979,12 +1060,177 @@ function pointsFor(def, samples) {
979
1060
  return ptsFrom(samples, def.pick);
980
1061
  }
981
1062
 
982
- // src/components/machines/machine-detail.tsx
1063
+ // src/components/machines/machine-operation-policy.tsx
1064
+ import { AlertTriangleIcon as AlertTriangleIcon2, Loader2Icon } from "lucide-react";
1065
+ import { useEffect, useState as useState2 } from "react";
983
1066
  import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1067
+ function fieldValue(value) {
1068
+ return value === null ? "" : String(value);
1069
+ }
1070
+ function parseByteCount(label, value) {
1071
+ const trimmed = value.trim();
1072
+ if (!trimmed) return null;
1073
+ if (!/^\d+$/.test(trimmed)) {
1074
+ throw new Error(`${label} must be a whole number of bytes or blank`);
1075
+ }
1076
+ const parsed = Number(trimmed);
1077
+ if (!Number.isSafeInteger(parsed) || parsed <= 0) {
1078
+ throw new Error(`${label} must be a positive safe-integer byte count or blank`);
1079
+ }
1080
+ return parsed;
1081
+ }
1082
+ function parseCpuMillicores(value) {
1083
+ const trimmed = value.trim();
1084
+ if (!trimmed) return null;
1085
+ if (!/^\d+$/.test(trimmed)) {
1086
+ throw new Error("CPU maximum must be a whole number of millicores or blank");
1087
+ }
1088
+ const parsed = Number(trimmed);
1089
+ if (!Number.isSafeInteger(parsed) || parsed <= 0 || parsed > 4294967295) {
1090
+ throw new Error("CPU maximum must be between 1 and 4294967295 millicores or blank");
1091
+ }
1092
+ return parsed;
1093
+ }
1094
+ function MachineOperationPolicyEditor({
1095
+ policy,
1096
+ memorySupported,
1097
+ cpuSupported,
1098
+ saving = false,
1099
+ onSave
1100
+ }) {
1101
+ const [memoryMaxBytes, setMemoryMaxBytes] = useState2(fieldValue(policy.memoryMaxBytes));
1102
+ const [memoryHighBytes, setMemoryHighBytes] = useState2(fieldValue(policy.memoryHighBytes));
1103
+ const [cpuMaxMillicores, setCpuMaxMillicores] = useState2(fieldValue(policy.cpuMaxMillicores));
1104
+ const [validationError, setValidationError] = useState2(null);
1105
+ useEffect(() => {
1106
+ setMemoryMaxBytes(fieldValue(policy.memoryMaxBytes));
1107
+ setMemoryHighBytes(fieldValue(policy.memoryHighBytes));
1108
+ setCpuMaxMillicores(fieldValue(policy.cpuMaxMillicores));
1109
+ setValidationError(null);
1110
+ }, [policy.memoryMaxBytes, policy.memoryHighBytes, policy.cpuMaxMillicores, policy.revision]);
1111
+ const submit = async (event) => {
1112
+ event.preventDefault();
1113
+ try {
1114
+ const max = parseByteCount("Hard ceiling", memoryMaxBytes);
1115
+ const high = parseByteCount("Reclaim threshold", memoryHighBytes);
1116
+ if (max !== null && high !== null && high > max) {
1117
+ throw new Error("Reclaim threshold cannot exceed the hard ceiling");
1118
+ }
1119
+ const cpuMax = parseCpuMillicores(cpuMaxMillicores);
1120
+ setValidationError(null);
1121
+ await onSave({
1122
+ memoryMaxBytes: max,
1123
+ memoryHighBytes: high,
1124
+ cpuMaxMillicores: cpuMax,
1125
+ expectedRevision: policy.revision
1126
+ });
1127
+ } catch (error) {
1128
+ setValidationError(error instanceof Error ? error.message : String(error));
1129
+ }
1130
+ };
1131
+ const configured = policy.memoryMaxBytes !== null || policy.memoryHighBytes !== null || policy.cpuMaxMillicores !== null;
1132
+ const memoryConfigured = policy.memoryMaxBytes !== null || policy.memoryHighBytes !== null;
1133
+ const unsupportedMemory = memoryConfigured && !memorySupported;
1134
+ const unsupportedCpu = policy.cpuMaxMillicores !== null && (!memorySupported || !cpuSupported);
1135
+ return /* @__PURE__ */ jsxs7(
1136
+ "section",
1137
+ {
1138
+ "data-machine-operation-policy": true,
1139
+ className: "rounded-og-lg border border-og-border bg-og-surface-1 p-4 shadow-og-sm",
1140
+ children: [
1141
+ /* @__PURE__ */ jsxs7("div", { className: "flex flex-col gap-1", children: [
1142
+ /* @__PURE__ */ jsx7("h2", { className: "text-og-sm font-semibold text-og-fg", children: "Per-command resource policy" }),
1143
+ /* @__PURE__ */ jsx7("p", { className: "text-og-xs leading-relaxed text-og-fg-muted", children: "Optional limits applied separately to each newly admitted exec and Git command. Running commands keep their admitted policy. Blank means unrestricted. The runner or host may apply a stricter explicit policy; browser, computer-use, and terminal capabilities remain unchanged." })
1144
+ ] }),
1145
+ configured && (unsupportedMemory || unsupportedCpu) ? /* @__PURE__ */ jsxs7("div", { className: "mt-3 flex gap-2 rounded-og-md border border-og-status-failed/40 bg-og-status-failed/5 p-3 text-og-xs text-og-status-failed", children: [
1146
+ /* @__PURE__ */ jsx7(AlertTriangleIcon2, { className: "mt-0.5 size-3.5 shrink-0", "aria-hidden": true }),
1147
+ /* @__PURE__ */ jsxs7("span", { children: [
1148
+ unsupportedCpu ? "This runner cannot enforce the configured CPU limit." : "This runner cannot enforce the configured memory limit.",
1149
+ " ",
1150
+ "Command execution fails closed until the current runner, OS, and service configuration can enforce it, or the unsupported limit is cleared. You can still save limits now for later enforcement."
1151
+ ] })
1152
+ ] }) : null,
1153
+ /* @__PURE__ */ jsxs7("form", { className: "mt-4 grid gap-3 sm:grid-cols-2", onSubmit: submit, children: [
1154
+ /* @__PURE__ */ jsxs7("label", { className: "flex flex-col gap-1.5 text-og-xs text-og-fg-muted", children: [
1155
+ "Hard ceiling (bytes)",
1156
+ /* @__PURE__ */ jsx7(
1157
+ "input",
1158
+ {
1159
+ inputMode: "numeric",
1160
+ pattern: "[0-9]*",
1161
+ value: memoryMaxBytes,
1162
+ onChange: (event) => setMemoryMaxBytes(event.target.value),
1163
+ placeholder: "Unlimited",
1164
+ disabled: saving,
1165
+ className: "min-h-9 rounded-og-sm border border-og-border bg-og-bg px-2.5 font-og-mono text-og-sm text-og-fg outline-hidden focus-visible:ring-2 focus-visible:ring-og-accent/50 disabled:opacity-60"
1166
+ }
1167
+ )
1168
+ ] }),
1169
+ /* @__PURE__ */ jsxs7("label", { className: "flex flex-col gap-1.5 text-og-xs text-og-fg-muted", children: [
1170
+ "Reclaim threshold (bytes)",
1171
+ /* @__PURE__ */ jsx7(
1172
+ "input",
1173
+ {
1174
+ inputMode: "numeric",
1175
+ pattern: "[0-9]*",
1176
+ value: memoryHighBytes,
1177
+ onChange: (event) => setMemoryHighBytes(event.target.value),
1178
+ placeholder: "Unlimited",
1179
+ disabled: saving,
1180
+ className: "min-h-9 rounded-og-sm border border-og-border bg-og-bg px-2.5 font-og-mono text-og-sm text-og-fg outline-hidden focus-visible:ring-2 focus-visible:ring-og-accent/50 disabled:opacity-60"
1181
+ }
1182
+ )
1183
+ ] }),
1184
+ /* @__PURE__ */ jsxs7("label", { className: "flex flex-col gap-1.5 text-og-xs text-og-fg-muted", children: [
1185
+ "CPU maximum (millicores)",
1186
+ /* @__PURE__ */ jsx7(
1187
+ "input",
1188
+ {
1189
+ inputMode: "numeric",
1190
+ pattern: "[0-9]*",
1191
+ value: cpuMaxMillicores,
1192
+ onChange: (event) => setCpuMaxMillicores(event.target.value),
1193
+ placeholder: "Unlimited",
1194
+ disabled: saving,
1195
+ className: "min-h-9 rounded-og-sm border border-og-border bg-og-bg px-2.5 font-og-mono text-og-sm text-og-fg outline-hidden focus-visible:ring-2 focus-visible:ring-og-accent/50 disabled:opacity-60"
1196
+ }
1197
+ ),
1198
+ /* @__PURE__ */ jsx7("span", { children: "1000 millicores = one CPU core. Blank is unlimited." })
1199
+ ] }),
1200
+ /* @__PURE__ */ jsxs7("div", { className: "flex min-h-9 items-center justify-between gap-3 sm:col-span-2", children: [
1201
+ /* @__PURE__ */ jsx7(
1202
+ "span",
1203
+ {
1204
+ role: validationError ? "alert" : void 0,
1205
+ className: "text-og-xs text-og-status-failed",
1206
+ children: validationError
1207
+ }
1208
+ ),
1209
+ /* @__PURE__ */ jsxs7(
1210
+ "button",
1211
+ {
1212
+ type: "submit",
1213
+ disabled: saving,
1214
+ className: "inline-flex min-h-9 items-center gap-1.5 rounded-og-sm bg-og-accent px-3 py-1.5 text-og-xs font-medium text-og-accent-fg transition-colors hover:bg-og-accent-strong disabled:cursor-not-allowed disabled:opacity-60",
1215
+ children: [
1216
+ saving ? /* @__PURE__ */ jsx7(Loader2Icon, { className: "size-3.5 animate-spin", "aria-hidden": true }) : null,
1217
+ "Save policy"
1218
+ ]
1219
+ }
1220
+ )
1221
+ ] })
1222
+ ] })
1223
+ ]
1224
+ }
1225
+ );
1226
+ }
1227
+
1228
+ // src/components/machines/machine-detail.tsx
1229
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
984
1230
  function KindIcon2({ kind, session }) {
985
- if (session) return /* @__PURE__ */ jsx7(ServerIcon2, { className: "size-4 text-og-fg-subtle", "aria-hidden": true });
986
- if (kind === "modal") return /* @__PURE__ */ jsx7(CpuIcon2, { className: "size-4 text-og-fg-subtle", "aria-hidden": true });
987
- return /* @__PURE__ */ jsx7(LaptopIcon2, { className: "size-4 text-og-fg-subtle", "aria-hidden": true });
1231
+ if (session) return /* @__PURE__ */ jsx8(ServerIcon2, { className: "size-4 text-og-fg-subtle", "aria-hidden": true });
1232
+ if (kind === "modal") return /* @__PURE__ */ jsx8(CpuIcon2, { className: "size-4 text-og-fg-subtle", "aria-hidden": true });
1233
+ return /* @__PURE__ */ jsx8(LaptopIcon2, { className: "size-4 text-og-fg-subtle", "aria-hidden": true });
988
1234
  }
989
1235
  function MachineDetail({
990
1236
  machine,
@@ -994,6 +1240,8 @@ function MachineDetail({
994
1240
  loadingSeries,
995
1241
  onBack,
996
1242
  onRemove,
1243
+ onUpdateOperationPolicy,
1244
+ updatingOperationPolicy,
997
1245
  now = Date.now(),
998
1246
  className
999
1247
  }) {
@@ -1001,31 +1249,33 @@ function MachineDetail({
1001
1249
  const tone = HEALTH_TOKEN[health.level];
1002
1250
  const latest = machine.metrics;
1003
1251
  const sampledAgo = latest ? formatRelativeTime(latest.sampledAt, new Date(now)) : null;
1252
+ const authority = machine.connectionAuthority;
1253
+ const duplicateDeniedAgo = authority.duplicateRunnerDeniedAt ? formatRelativeTime(authority.duplicateRunnerDeniedAt, new Date(now)) : null;
1004
1254
  const visible = METRICS.filter((m) => !(m.key === "gpu" && latest?.gpuUtilPct == null));
1005
1255
  const tileCols = visible.length >= 5 ? "grid-cols-2 sm:grid-cols-3 lg:grid-cols-5" : "grid-cols-2 sm:grid-cols-4";
1006
- return /* @__PURE__ */ jsxs7("div", { className: cn("og-root mx-auto flex w-full max-w-5xl flex-col gap-5", className), children: [
1007
- /* @__PURE__ */ jsxs7("div", { className: "flex items-center justify-between gap-3", children: [
1008
- /* @__PURE__ */ jsxs7("div", { className: "flex min-w-0 items-center gap-2.5", children: [
1009
- onBack && /* @__PURE__ */ jsx7(
1256
+ return /* @__PURE__ */ jsxs8("div", { className: cn("og-root mx-auto flex w-full max-w-5xl flex-col gap-5", className), children: [
1257
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center justify-between gap-3", children: [
1258
+ /* @__PURE__ */ jsxs8("div", { className: "flex min-w-0 items-center gap-2.5", children: [
1259
+ onBack && /* @__PURE__ */ jsx8(
1010
1260
  "button",
1011
1261
  {
1012
1262
  type: "button",
1013
1263
  onClick: onBack,
1014
1264
  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",
1015
1265
  "aria-label": "Back to machines",
1016
- children: /* @__PURE__ */ jsx7(ArrowLeftIcon, { className: "size-3.5", "aria-hidden": true })
1266
+ children: /* @__PURE__ */ jsx8(ArrowLeftIcon, { className: "size-3.5", "aria-hidden": true })
1017
1267
  }
1018
1268
  ),
1019
- /* @__PURE__ */ jsx7(KindIcon2, { kind: machine.kind, session: machine.isSessionGroup }),
1020
- /* @__PURE__ */ jsx7("h1", { className: "truncate text-og-md font-semibold text-og-fg", children: machine.name }),
1021
- /* @__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: [
1269
+ /* @__PURE__ */ jsx8(KindIcon2, { kind: machine.kind, session: machine.isSessionGroup }),
1270
+ /* @__PURE__ */ jsx8("h1", { className: "truncate text-og-md font-semibold text-og-fg", children: machine.name }),
1271
+ /* @__PURE__ */ jsxs8("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: [
1022
1272
  machine.os,
1023
1273
  " \xB7 ",
1024
1274
  machine.arch
1025
1275
  ] })
1026
1276
  ] }),
1027
- /* @__PURE__ */ jsxs7("div", { className: "flex shrink-0 items-center gap-2", children: [
1028
- onRemove && machine.enrollmentId && !machine.isSessionGroup ? /* @__PURE__ */ jsxs7(
1277
+ /* @__PURE__ */ jsxs8("div", { className: "flex shrink-0 items-center gap-2", children: [
1278
+ onRemove && machine.enrollmentId && !machine.isSessionGroup ? /* @__PURE__ */ jsxs8(
1029
1279
  "button",
1030
1280
  {
1031
1281
  type: "button",
@@ -1034,12 +1284,12 @@ function MachineDetail({
1034
1284
  onClick: () => onRemove(machine),
1035
1285
  className: "inline-flex min-h-9 items-center gap-1.5 rounded-og-sm border border-og-status-failed/40 px-2.5 py-1 font-medium text-og-xs text-og-status-failed transition-colors hover:bg-og-status-failed/10 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-og-status-failed/50 pointer-coarse:min-h-11",
1036
1286
  children: [
1037
- /* @__PURE__ */ jsx7(Trash2Icon, { className: "size-3.5", "aria-hidden": true }),
1038
- /* @__PURE__ */ jsx7("span", { className: "hidden sm:inline", children: "Remove machine" })
1287
+ /* @__PURE__ */ jsx8(Trash2Icon, { className: "size-3.5", "aria-hidden": true }),
1288
+ /* @__PURE__ */ jsx8("span", { className: "hidden sm:inline", children: "Remove machine" })
1039
1289
  ]
1040
1290
  }
1041
1291
  ) : null,
1042
- /* @__PURE__ */ jsx7("div", { className: "flex 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(
1292
+ /* @__PURE__ */ jsx8("div", { className: "flex 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__ */ jsx8(
1043
1293
  "button",
1044
1294
  {
1045
1295
  type: "button",
@@ -1055,7 +1305,7 @@ function MachineDetail({
1055
1305
  )) })
1056
1306
  ] })
1057
1307
  ] }),
1058
- /* @__PURE__ */ jsxs7(
1308
+ /* @__PURE__ */ jsxs8(
1059
1309
  "div",
1060
1310
  {
1061
1311
  className: cn(
@@ -1063,11 +1313,11 @@ function MachineDetail({
1063
1313
  tone.soft
1064
1314
  ),
1065
1315
  children: [
1066
- /* @__PURE__ */ jsx7("span", { "aria-hidden": true, className: cn("absolute inset-y-0 left-0 w-1", tone.dot) }),
1067
- /* @__PURE__ */ jsxs7("div", { className: "flex flex-wrap items-center justify-between gap-3 pl-1", children: [
1068
- /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-3", children: [
1069
- /* @__PURE__ */ jsxs7("span", { className: "relative flex size-3", children: [
1070
- healthPulses(health.level) && /* @__PURE__ */ jsx7(
1316
+ /* @__PURE__ */ jsx8("span", { "aria-hidden": true, className: cn("absolute inset-y-0 left-0 w-1", tone.dot) }),
1317
+ /* @__PURE__ */ jsxs8("div", { className: "flex flex-wrap items-center justify-between gap-3 pl-1", children: [
1318
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-3", children: [
1319
+ /* @__PURE__ */ jsxs8("span", { className: "relative flex size-3", children: [
1320
+ healthPulses(health.level) && /* @__PURE__ */ jsx8(
1071
1321
  "span",
1072
1322
  {
1073
1323
  className: cn(
@@ -1076,18 +1326,18 @@ function MachineDetail({
1076
1326
  )
1077
1327
  }
1078
1328
  ),
1079
- /* @__PURE__ */ jsx7("span", { className: cn("relative inline-flex size-3 rounded-full", tone.dot) })
1329
+ /* @__PURE__ */ jsx8("span", { className: cn("relative inline-flex size-3 rounded-full", tone.dot) })
1080
1330
  ] }),
1081
- /* @__PURE__ */ jsxs7("div", { className: "flex flex-col", children: [
1082
- /* @__PURE__ */ jsx7("span", { className: cn("text-og-md font-semibold", tone.text), children: health.label }),
1083
- /* @__PURE__ */ jsx7("span", { className: "text-og-sm text-og-fg-muted", children: health.reason })
1331
+ /* @__PURE__ */ jsxs8("div", { className: "flex flex-col", children: [
1332
+ /* @__PURE__ */ jsx8("span", { className: cn("text-og-md font-semibold", tone.text), children: health.label }),
1333
+ /* @__PURE__ */ jsx8("span", { className: "text-og-sm text-og-fg-muted", children: health.reason })
1084
1334
  ] })
1085
1335
  ] }),
1086
- /* @__PURE__ */ jsxs7("div", { className: "flex items-start gap-5 pr-1", children: [
1087
- sampledAgo && /* @__PURE__ */ jsxs7("div", { className: "flex flex-col items-end gap-0.5", children: [
1088
- /* @__PURE__ */ jsx7("span", { className: "text-[10px] font-medium uppercase tracking-wide text-og-fg-subtle", children: "Sampled" }),
1089
- /* @__PURE__ */ jsxs7("span", { className: "flex items-center gap-1 font-og-mono text-og-sm tabular-nums text-og-fg", children: [
1090
- /* @__PURE__ */ jsx7(
1336
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-start gap-5 pr-1", children: [
1337
+ sampledAgo && /* @__PURE__ */ jsxs8("div", { className: "flex flex-col items-end gap-0.5", children: [
1338
+ /* @__PURE__ */ jsx8("span", { className: "text-[10px] font-medium uppercase tracking-wide text-og-fg-subtle", children: "Sampled" }),
1339
+ /* @__PURE__ */ jsxs8("span", { className: "flex items-center gap-1 font-og-mono text-og-sm tabular-nums text-og-fg", children: [
1340
+ /* @__PURE__ */ jsx8(
1091
1341
  RadioIcon,
1092
1342
  {
1093
1343
  className: cn("size-3", health.stale ? "text-og-fg-subtle" : tone.text),
@@ -1097,13 +1347,13 @@ function MachineDetail({
1097
1347
  sampledAgo === "now" ? "live" : `${sampledAgo} ago`
1098
1348
  ] })
1099
1349
  ] }),
1100
- machine.lastSeenAt && /* @__PURE__ */ jsxs7("div", { className: "hidden flex-col items-end gap-0.5 sm:flex", children: [
1101
- /* @__PURE__ */ jsx7("span", { className: "text-[10px] font-medium uppercase tracking-wide text-og-fg-subtle", children: "Last seen" }),
1102
- /* @__PURE__ */ jsx7("span", { className: "font-og-mono text-og-sm tabular-nums text-og-fg-muted", children: formatRelativeTime(machine.lastSeenAt, new Date(now)) })
1350
+ machine.lastSeenAt && /* @__PURE__ */ jsxs8("div", { className: "hidden flex-col items-end gap-0.5 sm:flex", children: [
1351
+ /* @__PURE__ */ jsx8("span", { className: "text-[10px] font-medium uppercase tracking-wide text-og-fg-subtle", children: "Last seen" }),
1352
+ /* @__PURE__ */ jsx8("span", { className: "font-og-mono text-og-sm tabular-nums text-og-fg-muted", children: formatRelativeTime(machine.lastSeenAt, new Date(now)) })
1103
1353
  ] })
1104
1354
  ] })
1105
1355
  ] }),
1106
- latest && /* @__PURE__ */ jsx7(
1356
+ latest && /* @__PURE__ */ jsx8(
1107
1357
  "div",
1108
1358
  {
1109
1359
  className: cn(
@@ -1112,30 +1362,64 @@ function MachineDetail({
1112
1362
  ),
1113
1363
  children: visible.map((m) => {
1114
1364
  const pts = pointsFor(m, series);
1115
- return /* @__PURE__ */ jsxs7("div", { className: "flex flex-col gap-1.5 bg-og-surface-1 p-3", children: [
1116
- /* @__PURE__ */ jsx7("span", { className: "text-[10px] font-medium uppercase tracking-wide text-og-fg-subtle", children: m.title }),
1117
- /* @__PURE__ */ jsx7("span", { className: "font-og-mono text-og-md font-medium tabular-nums text-og-fg", children: m.current(latest) }),
1118
- /* @__PURE__ */ jsx7(MetricSparkline, { points: pts, color: m.color, yMax: m.yMax, height: 22 }),
1119
- m.sub && /* @__PURE__ */ jsx7("span", { className: "truncate font-og-mono text-[10px] text-og-fg-subtle", children: m.sub(latest) })
1365
+ return /* @__PURE__ */ jsxs8("div", { className: "flex flex-col gap-1.5 bg-og-surface-1 p-3", children: [
1366
+ /* @__PURE__ */ jsx8("span", { className: "text-[10px] font-medium uppercase tracking-wide text-og-fg-subtle", children: m.title }),
1367
+ /* @__PURE__ */ jsx8("span", { className: "font-og-mono text-og-md font-medium tabular-nums text-og-fg", children: m.current(latest) }),
1368
+ /* @__PURE__ */ jsx8(MetricSparkline, { points: pts, color: m.color, yMax: m.yMax, height: 22 }),
1369
+ m.sub && /* @__PURE__ */ jsx8("span", { className: "truncate font-og-mono text-[10px] text-og-fg-subtle", children: m.sub(latest) })
1120
1370
  ] }, m.key);
1121
1371
  })
1122
1372
  }
1123
- )
1373
+ ),
1374
+ authority.state !== "not_applicable" ? /* @__PURE__ */ jsxs8(
1375
+ "div",
1376
+ {
1377
+ "data-connection-authority": authority.state,
1378
+ className: "flex flex-wrap items-center justify-between gap-2 rounded-og-md border border-og-border bg-og-surface-1/75 px-3 py-2 text-og-xs",
1379
+ children: [
1380
+ /* @__PURE__ */ jsxs8("span", { className: "text-og-fg-muted", children: [
1381
+ "Runner ",
1382
+ authority.state.replace("_", " "),
1383
+ " \xB7 generation ",
1384
+ authority.generation,
1385
+ authority.supersededCount > 0 ? ` \xB7 ${authority.supersededCount} earlier ${authority.supersededCount === 1 ? "runner" : "runners"} fenced` : ""
1386
+ ] }),
1387
+ authority.duplicateRunnerDeniedCount > 0 ? /* @__PURE__ */ jsxs8("span", { className: "inline-flex items-center gap-1.5 text-og-status-failed", children: [
1388
+ /* @__PURE__ */ jsx8(AlertTriangleIcon3, { className: "size-3.5", "aria-hidden": true }),
1389
+ "Blocked ",
1390
+ authority.duplicateRunnerDeniedCount,
1391
+ " competing runner",
1392
+ authority.duplicateRunnerDeniedCount === 1 ? "" : "s",
1393
+ duplicateDeniedAgo ? ` \xB7 ${duplicateDeniedAgo === "now" ? "now" : `${duplicateDeniedAgo} ago`}` : ""
1394
+ ] }) : null
1395
+ ]
1396
+ }
1397
+ ) : null
1124
1398
  ]
1125
1399
  }
1126
1400
  ),
1127
- /* @__PURE__ */ jsx7("div", { className: "grid grid-cols-1 gap-3 lg:grid-cols-2", children: visible.map((m) => {
1401
+ onUpdateOperationPolicy && machine.enrollmentId && machine.operationPolicy ? /* @__PURE__ */ jsx8(
1402
+ MachineOperationPolicyEditor,
1403
+ {
1404
+ policy: machine.operationPolicy,
1405
+ memorySupported: machine.runtime?.capabilities.operationResourcePolicy === true,
1406
+ cpuSupported: machine.runtime?.capabilities.operationCpuQuota === true,
1407
+ saving: updatingOperationPolicy,
1408
+ onSave: (request) => onUpdateOperationPolicy(machine, request)
1409
+ }
1410
+ ) : null,
1411
+ /* @__PURE__ */ jsx8("div", { className: "grid grid-cols-1 gap-3 lg:grid-cols-2", children: visible.map((m) => {
1128
1412
  const pts = pointsFor(m, series);
1129
- return /* @__PURE__ */ jsxs7(
1413
+ return /* @__PURE__ */ jsxs8(
1130
1414
  "div",
1131
1415
  {
1132
1416
  className: "flex flex-col gap-3 rounded-og-lg border border-og-border bg-og-surface-1 p-4 shadow-og-sm",
1133
1417
  children: [
1134
- /* @__PURE__ */ jsxs7("div", { className: "flex items-baseline justify-between", children: [
1135
- /* @__PURE__ */ jsx7("span", { className: "text-og-sm font-medium text-og-fg", children: m.title }),
1136
- latest && /* @__PURE__ */ jsx7("span", { className: "font-og-mono text-og-sm tabular-nums text-og-fg-muted", children: m.current(latest) })
1418
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-baseline justify-between", children: [
1419
+ /* @__PURE__ */ jsx8("span", { className: "text-og-sm font-medium text-og-fg", children: m.title }),
1420
+ latest && /* @__PURE__ */ jsx8("span", { className: "font-og-mono text-og-sm tabular-nums text-og-fg-muted", children: m.current(latest) })
1137
1421
  ] }),
1138
- /* @__PURE__ */ jsx7(
1422
+ /* @__PURE__ */ jsx8(
1139
1423
  MetricHistoryChart,
1140
1424
  {
1141
1425
  points: pts,
@@ -1158,20 +1442,20 @@ function MachineDetail({
1158
1442
 
1159
1443
  // src/components/machines-dashboard.tsx
1160
1444
  import { LaptopIcon as LaptopIcon3, PlusIcon, RefreshCwIcon } from "lucide-react";
1161
- import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1445
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1162
1446
  function EmptyState({ onEnroll }) {
1163
- return /* @__PURE__ */ jsxs8(
1447
+ return /* @__PURE__ */ jsxs9(
1164
1448
  "div",
1165
1449
  {
1166
1450
  "data-machines-empty": true,
1167
1451
  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",
1168
1452
  children: [
1169
- /* @__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 }) }),
1170
- /* @__PURE__ */ jsxs8("div", { className: "space-y-1", children: [
1171
- /* @__PURE__ */ jsx8("p", { className: "text-og-base font-medium text-og-fg", children: "No machines yet" }),
1172
- /* @__PURE__ */ jsx8("p", { className: "max-w-xs text-og-sm text-og-fg-muted", children: "Connect your own computer to run the agent on it \u2014 your files, your terminal, your desktop." })
1453
+ /* @__PURE__ */ jsx9("span", { className: "flex size-10 items-center justify-center rounded-full bg-og-surface-2 text-og-fg-subtle", children: /* @__PURE__ */ jsx9(LaptopIcon3, { className: "size-5", "aria-hidden": true }) }),
1454
+ /* @__PURE__ */ jsxs9("div", { className: "space-y-1", children: [
1455
+ /* @__PURE__ */ jsx9("p", { className: "text-og-base font-medium text-og-fg", children: "No machines yet" }),
1456
+ /* @__PURE__ */ jsx9("p", { className: "max-w-xs text-og-sm text-og-fg-muted", children: "Connect your own computer to run the agent on it \u2014 your files, your terminal, your desktop." })
1173
1457
  ] }),
1174
- onEnroll ? /* @__PURE__ */ jsxs8(
1458
+ onEnroll ? /* @__PURE__ */ jsxs9(
1175
1459
  "button",
1176
1460
  {
1177
1461
  type: "button",
@@ -1179,7 +1463,7 @@ function EmptyState({ onEnroll }) {
1179
1463
  onClick: onEnroll,
1180
1464
  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",
1181
1465
  children: [
1182
- /* @__PURE__ */ jsx8(PlusIcon, { className: "size-3.5", "aria-hidden": true }),
1466
+ /* @__PURE__ */ jsx9(PlusIcon, { className: "size-3.5", "aria-hidden": true }),
1183
1467
  "Connect a machine"
1184
1468
  ]
1185
1469
  }
@@ -1194,13 +1478,13 @@ function Header({
1194
1478
  onRefresh,
1195
1479
  loading
1196
1480
  }) {
1197
- return /* @__PURE__ */ jsxs8("div", { className: "flex items-center justify-between gap-3", children: [
1198
- /* @__PURE__ */ jsxs8("div", { className: "flex items-baseline gap-2", children: [
1199
- /* @__PURE__ */ jsx8("h2", { className: "text-og-base font-semibold text-og-fg", children: "Machines" }),
1200
- /* @__PURE__ */ jsx8("span", { className: "font-og-mono text-og-xs text-og-fg-subtle", children: count })
1481
+ return /* @__PURE__ */ jsxs9("div", { className: "flex items-center justify-between gap-3", children: [
1482
+ /* @__PURE__ */ jsxs9("div", { className: "flex items-baseline gap-2", children: [
1483
+ /* @__PURE__ */ jsx9("h2", { className: "text-og-base font-semibold text-og-fg", children: "Machines" }),
1484
+ /* @__PURE__ */ jsx9("span", { className: "font-og-mono text-og-xs text-og-fg-subtle", children: count })
1201
1485
  ] }),
1202
- /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-1.5", children: [
1203
- onRefresh ? /* @__PURE__ */ jsx8(
1486
+ /* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-1.5", children: [
1487
+ onRefresh ? /* @__PURE__ */ jsx9(
1204
1488
  "button",
1205
1489
  {
1206
1490
  type: "button",
@@ -1208,10 +1492,10 @@ function Header({
1208
1492
  onClick: onRefresh,
1209
1493
  title: "Refresh",
1210
1494
  className: "rounded-og-sm p-1.5 text-og-fg-subtle transition-colors hover:bg-og-surface-2 hover:text-og-fg",
1211
- children: /* @__PURE__ */ jsx8(RefreshCwIcon, { className: cn("size-3.5", loading && "animate-og-spin"), "aria-hidden": true })
1495
+ children: /* @__PURE__ */ jsx9(RefreshCwIcon, { className: cn("size-3.5", loading && "animate-og-spin"), "aria-hidden": true })
1212
1496
  }
1213
1497
  ) : null,
1214
- onEnroll ? /* @__PURE__ */ jsxs8(
1498
+ onEnroll ? /* @__PURE__ */ jsxs9(
1215
1499
  "button",
1216
1500
  {
1217
1501
  type: "button",
@@ -1219,7 +1503,7 @@ function Header({
1219
1503
  onClick: onEnroll,
1220
1504
  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",
1221
1505
  children: [
1222
- /* @__PURE__ */ jsx8(PlusIcon, { className: "size-3.5", "aria-hidden": true }),
1506
+ /* @__PURE__ */ jsx9(PlusIcon, { className: "size-3.5", "aria-hidden": true }),
1223
1507
  "Connect a machine"
1224
1508
  ]
1225
1509
  }
@@ -1234,6 +1518,8 @@ function MachinesDashboard({
1234
1518
  error,
1235
1519
  onAttach,
1236
1520
  attachingSandboxId,
1521
+ onUpdateAgent,
1522
+ updatingEnrollmentId,
1237
1523
  seriesByMachine,
1238
1524
  onOpenDetail,
1239
1525
  now,
@@ -1242,19 +1528,19 @@ function MachinesDashboard({
1242
1528
  className
1243
1529
  }) {
1244
1530
  const isEmpty = !loading && !error && machines.length === 0;
1245
- return /* @__PURE__ */ jsxs8("section", { "data-machines-dashboard": true, className: cn("og-root flex flex-col gap-3", className), children: [
1246
- /* @__PURE__ */ jsx8(Header, { count: machines.length, onEnroll, onRefresh, loading }),
1247
- error ? /* @__PURE__ */ jsxs8(
1531
+ return /* @__PURE__ */ jsxs9("section", { "data-machines-dashboard": true, className: cn("og-root flex flex-col gap-3", className), children: [
1532
+ /* @__PURE__ */ jsx9(Header, { count: machines.length, onEnroll, onRefresh, loading }),
1533
+ error ? /* @__PURE__ */ jsxs9(
1248
1534
  "div",
1249
1535
  {
1250
1536
  "data-machines-error": true,
1251
1537
  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",
1252
1538
  children: [
1253
- /* @__PURE__ */ jsxs8("span", { children: [
1539
+ /* @__PURE__ */ jsxs9("span", { children: [
1254
1540
  "Could not load machines: ",
1255
1541
  error.message
1256
1542
  ] }),
1257
- onRefresh ? /* @__PURE__ */ jsxs8(
1543
+ onRefresh ? /* @__PURE__ */ jsxs9(
1258
1544
  "button",
1259
1545
  {
1260
1546
  type: "button",
@@ -1263,7 +1549,7 @@ function MachinesDashboard({
1263
1549
  disabled: loading,
1264
1550
  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",
1265
1551
  children: [
1266
- /* @__PURE__ */ jsx8(RefreshCwIcon, { className: cn("size-3.5", loading && "animate-og-spin"), "aria-hidden": true }),
1552
+ /* @__PURE__ */ jsx9(RefreshCwIcon, { className: cn("size-3.5", loading && "animate-og-spin"), "aria-hidden": true }),
1267
1553
  loading ? "Retrying\u2026" : "Retry"
1268
1554
  ]
1269
1555
  }
@@ -1271,13 +1557,13 @@ function MachinesDashboard({
1271
1557
  ]
1272
1558
  }
1273
1559
  ) : null,
1274
- 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(
1560
+ loading && machines.length === 0 ? /* @__PURE__ */ jsx9("div", { "data-machines-loading": true, className: "grid gap-3 sm:grid-cols-2", children: [0, 1].map((i) => /* @__PURE__ */ jsx9(
1275
1561
  "div",
1276
1562
  {
1277
1563
  className: "h-36 animate-og-pulse rounded-og-lg border border-og-border bg-og-surface-1"
1278
1564
  },
1279
1565
  i
1280
- )) }) : 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(
1566
+ )) }) : isEmpty ? /* @__PURE__ */ jsx9(EmptyState, { onEnroll }) : /* @__PURE__ */ jsx9("div", { className: "grid gap-3 sm:grid-cols-2 xl:grid-cols-3", "data-machines-grid": true, children: machines.map((machine) => /* @__PURE__ */ jsx9(
1281
1567
  MachineCard,
1282
1568
  {
1283
1569
  machine: {
@@ -1286,6 +1572,8 @@ function MachinesDashboard({
1286
1572
  },
1287
1573
  onAttach,
1288
1574
  attaching: attachingSandboxId === machine.sandboxId,
1575
+ onUpdateAgent,
1576
+ updatingAgent: updatingEnrollmentId === machine.enrollmentId,
1289
1577
  series: seriesByMachine?.[machine.sandboxId],
1290
1578
  onOpenDetail,
1291
1579
  now
@@ -1297,11 +1585,11 @@ function MachinesDashboard({
1297
1585
 
1298
1586
  // src/components/machine-dock-bar.tsx
1299
1587
  import { LaptopIcon as LaptopIcon4, CpuIcon as CpuIcon3, UsersIcon as UsersIcon2 } from "lucide-react";
1300
- import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1588
+ import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
1301
1589
  function MachineDockBar({ name, kind, state, className }) {
1302
1590
  const Icon = kind === "selfhosted" ? LaptopIcon4 : CpuIcon3;
1303
1591
  const stateBadge = MACHINE_STATE_BADGE_META[state];
1304
- return /* @__PURE__ */ jsxs9(
1592
+ return /* @__PURE__ */ jsxs10(
1305
1593
  "div",
1306
1594
  {
1307
1595
  "data-machine-dock-bar": true,
@@ -1310,10 +1598,10 @@ function MachineDockBar({ name, kind, state, className }) {
1310
1598
  className
1311
1599
  ),
1312
1600
  children: [
1313
- /* @__PURE__ */ jsxs9("div", { className: "flex min-w-0 items-center gap-1.5", children: [
1314
- /* @__PURE__ */ jsx9(Icon, { className: "size-3.5 shrink-0 text-og-fg-subtle", "aria-hidden": true }),
1315
- /* @__PURE__ */ jsx9("span", { className: "truncate text-[11px] font-medium text-og-fg", children: name }),
1316
- stateBadge ? /* @__PURE__ */ jsx9(
1601
+ /* @__PURE__ */ jsxs10("div", { className: "flex min-w-0 items-center gap-1.5", children: [
1602
+ /* @__PURE__ */ jsx10(Icon, { className: "size-3.5 shrink-0 text-og-fg-subtle", "aria-hidden": true }),
1603
+ /* @__PURE__ */ jsx10("span", { className: "truncate text-[11px] font-medium text-og-fg", children: name }),
1604
+ stateBadge ? /* @__PURE__ */ jsx10(
1317
1605
  "span",
1318
1606
  {
1319
1607
  "data-state-badge": state,
@@ -1325,7 +1613,7 @@ function MachineDockBar({ name, kind, state, className }) {
1325
1613
  }
1326
1614
  ) : null
1327
1615
  ] }),
1328
- /* @__PURE__ */ jsx9(
1616
+ /* @__PURE__ */ jsx10(
1329
1617
  ConnectionStatusPill,
1330
1618
  {
1331
1619
  status: connectionStatusForState(state),
@@ -1343,7 +1631,7 @@ function SharedMachineDisclosure({
1343
1631
  className
1344
1632
  }) {
1345
1633
  const others = Math.max(0, sharedSessionCount - 1);
1346
- return /* @__PURE__ */ jsxs9(
1634
+ return /* @__PURE__ */ jsxs10(
1347
1635
  "div",
1348
1636
  {
1349
1637
  "data-shared-disclosure": true,
@@ -1353,8 +1641,8 @@ function SharedMachineDisclosure({
1353
1641
  className
1354
1642
  ),
1355
1643
  children: [
1356
- /* @__PURE__ */ jsx9(UsersIcon2, { className: "size-3.5 shrink-0 text-og-accent", "aria-hidden": true }),
1357
- /* @__PURE__ */ jsxs9("span", { children: [
1644
+ /* @__PURE__ */ jsx10(UsersIcon2, { className: "size-3.5 shrink-0 text-og-accent", "aria-hidden": true }),
1645
+ /* @__PURE__ */ jsxs10("span", { children: [
1358
1646
  "Shared \u2014 ",
1359
1647
  others === 1 ? "another session is" : `${others} other sessions are`,
1360
1648
  " on this machine. They see the same terminal, files, and desktop."
@@ -1366,7 +1654,7 @@ function SharedMachineDisclosure({
1366
1654
 
1367
1655
  // src/components/enrollment-device-flow.tsx
1368
1656
  import { CopyIcon, ExternalLinkIcon, LaptopIcon as LaptopIcon5, TerminalIcon } from "lucide-react";
1369
- import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
1657
+ import { Fragment as Fragment2, jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
1370
1658
  var PHASE_TO_STATE = {
1371
1659
  pending: "enrolling",
1372
1660
  authorized: "online",
@@ -1382,7 +1670,7 @@ function PhaseLabel({ phase }) {
1382
1670
  expired: "Code expired",
1383
1671
  disabled: "Enrollment disabled"
1384
1672
  };
1385
- return /* @__PURE__ */ jsx10("span", { children: text[phase] });
1673
+ return /* @__PURE__ */ jsx11("span", { children: text[phase] });
1386
1674
  }
1387
1675
  function EnrollmentDeviceFlow({
1388
1676
  userCode,
@@ -1399,7 +1687,7 @@ function EnrollmentDeviceFlow({
1399
1687
  const href = verificationUriComplete ?? verificationUri;
1400
1688
  const codeIsPlaceholder = !/[A-Za-z0-9]/.test(userCode);
1401
1689
  const awaitingCode = phase === "pending" && codeIsPlaceholder;
1402
- return /* @__PURE__ */ jsxs10(
1690
+ return /* @__PURE__ */ jsxs11(
1403
1691
  "div",
1404
1692
  {
1405
1693
  "data-enrollment-device-flow": true,
@@ -1408,28 +1696,28 @@ function EnrollmentDeviceFlow({
1408
1696
  className
1409
1697
  ),
1410
1698
  children: [
1411
- /* @__PURE__ */ jsxs10("div", { className: "flex items-center justify-between gap-3", children: [
1412
- /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2", children: [
1413
- /* @__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 }) }),
1414
- /* @__PURE__ */ jsx10("h2", { className: "text-og-menu font-semibold text-og-fg", children: "Connect a machine" })
1699
+ /* @__PURE__ */ jsxs11("div", { className: "flex items-center justify-between gap-3", children: [
1700
+ /* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-2", children: [
1701
+ /* @__PURE__ */ jsx11("span", { className: "flex size-7 items-center justify-center rounded-full bg-og-surface-2 text-og-fg-muted", children: /* @__PURE__ */ jsx11(LaptopIcon5, { className: "size-4", "aria-hidden": true }) }),
1702
+ /* @__PURE__ */ jsx11("h2", { className: "text-og-menu font-semibold text-og-fg", children: "Connect a machine" })
1415
1703
  ] }),
1416
- /* @__PURE__ */ jsx10(
1704
+ /* @__PURE__ */ jsx11(
1417
1705
  ConnectionStatusPill,
1418
1706
  {
1419
1707
  status: PHASE_TO_STATE[phase] === "online" ? "online" : PHASE_TO_STATE[phase] === "offline" ? "offline" : "reconnecting",
1420
- label: /* @__PURE__ */ jsx10(PhaseLabel, { phase }),
1708
+ label: /* @__PURE__ */ jsx11(PhaseLabel, { phase }),
1421
1709
  size: "sm"
1422
1710
  }
1423
1711
  )
1424
1712
  ] }),
1425
- installCommand ? /* @__PURE__ */ jsxs10("div", { className: "flex flex-col gap-1.5", children: [
1426
- /* @__PURE__ */ jsx10("span", { className: "text-[11px] font-medium uppercase tracking-wide text-og-fg-subtle", children: "1 \xB7 Run on the machine" }),
1427
- /* @__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: [
1428
- /* @__PURE__ */ jsxs10("code", { className: "flex min-w-0 items-center gap-2 overflow-x-auto font-og-mono text-[12px] text-og-fg", children: [
1429
- /* @__PURE__ */ jsx10(TerminalIcon, { className: "size-3.5 shrink-0 text-og-fg-subtle", "aria-hidden": true }),
1713
+ installCommand ? /* @__PURE__ */ jsxs11("div", { className: "flex flex-col gap-1.5", children: [
1714
+ /* @__PURE__ */ jsx11("span", { className: "text-[11px] font-medium uppercase tracking-wide text-og-fg-subtle", children: "1 \xB7 Run on the machine" }),
1715
+ /* @__PURE__ */ jsxs11("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: [
1716
+ /* @__PURE__ */ jsxs11("code", { className: "flex min-w-0 items-center gap-2 overflow-x-auto font-og-mono text-[12px] text-og-fg", children: [
1717
+ /* @__PURE__ */ jsx11(TerminalIcon, { className: "size-3.5 shrink-0 text-og-fg-subtle", "aria-hidden": true }),
1430
1718
  installCommand
1431
1719
  ] }),
1432
- /* @__PURE__ */ jsx10(
1720
+ /* @__PURE__ */ jsx11(
1433
1721
  "button",
1434
1722
  {
1435
1723
  type: "button",
@@ -1437,15 +1725,15 @@ function EnrollmentDeviceFlow({
1437
1725
  onClick: onCopyInstall,
1438
1726
  title: "Copy command",
1439
1727
  className: "shrink-0 rounded-og-sm p-1.5 text-og-fg-subtle transition-colors hover:bg-og-surface-2 hover:text-og-fg",
1440
- children: /* @__PURE__ */ jsx10(CopyIcon, { className: "size-4", "aria-hidden": true })
1728
+ children: /* @__PURE__ */ jsx11(CopyIcon, { className: "size-4", "aria-hidden": true })
1441
1729
  }
1442
1730
  )
1443
1731
  ] })
1444
1732
  ] }) : null,
1445
- /* @__PURE__ */ jsxs10("div", { className: "flex flex-col gap-1.5", children: [
1446
- /* @__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" }),
1447
- /* @__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: [
1448
- 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(
1733
+ /* @__PURE__ */ jsxs11("div", { className: "flex flex-col gap-1.5", children: [
1734
+ /* @__PURE__ */ jsx11("span", { className: "text-[11px] font-medium uppercase tracking-wide text-og-fg-subtle", children: installCommand ? "2 \xB7 Approve the machine" : "Approve the machine" }),
1735
+ /* @__PURE__ */ jsxs11("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: [
1736
+ awaitingCode ? /* @__PURE__ */ jsx11("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__ */ jsx11(
1449
1737
  "span",
1450
1738
  {
1451
1739
  "data-user-code": true,
@@ -1453,7 +1741,7 @@ function EnrollmentDeviceFlow({
1453
1741
  children: userCode
1454
1742
  }
1455
1743
  ),
1456
- /* @__PURE__ */ jsx10(
1744
+ /* @__PURE__ */ jsx11(
1457
1745
  "button",
1458
1746
  {
1459
1747
  type: "button",
@@ -1462,12 +1750,12 @@ function EnrollmentDeviceFlow({
1462
1750
  disabled: awaitingCode,
1463
1751
  title: "Copy code",
1464
1752
  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",
1465
- children: /* @__PURE__ */ jsx10(CopyIcon, { className: "size-4", "aria-hidden": true })
1753
+ children: /* @__PURE__ */ jsx11(CopyIcon, { className: "size-4", "aria-hidden": true })
1466
1754
  }
1467
1755
  )
1468
1756
  ] })
1469
1757
  ] }),
1470
- /* @__PURE__ */ jsxs10(
1758
+ /* @__PURE__ */ jsxs11(
1471
1759
  "a",
1472
1760
  {
1473
1761
  href,
@@ -1478,23 +1766,23 @@ function EnrollmentDeviceFlow({
1478
1766
  className: "inline-flex items-center justify-center gap-1.5 rounded-og-sm bg-og-accent px-3 py-2 text-og-menu font-medium text-og-accent-fg transition-colors hover:bg-og-accent-strong",
1479
1767
  children: [
1480
1768
  "Open approval page",
1481
- /* @__PURE__ */ jsx10(ExternalLinkIcon, { className: "size-3.5", "aria-hidden": true })
1769
+ /* @__PURE__ */ jsx11(ExternalLinkIcon, { className: "size-3.5", "aria-hidden": true })
1482
1770
  ]
1483
1771
  }
1484
1772
  ),
1485
- /* @__PURE__ */ jsx10("p", { className: "text-center text-[11px] text-og-fg-subtle", children: awaitingCode ? /* @__PURE__ */ jsxs10(Fragment2, { children: [
1773
+ /* @__PURE__ */ jsx11("p", { className: "text-center text-[11px] text-og-fg-subtle", children: awaitingCode ? /* @__PURE__ */ jsxs11(Fragment2, { children: [
1486
1774
  "The one-liner prints a short code. Open",
1487
1775
  " ",
1488
- /* @__PURE__ */ jsx10("span", { className: "font-og-mono text-og-fg-muted", children: verificationUri }),
1776
+ /* @__PURE__ */ jsx11("span", { className: "font-og-mono text-og-fg-muted", children: verificationUri }),
1489
1777
  " and confirm the code shown there matches."
1490
- ] }) : /* @__PURE__ */ jsxs10(Fragment2, { children: [
1778
+ ] }) : /* @__PURE__ */ jsxs11(Fragment2, { children: [
1491
1779
  "Confirm code ",
1492
- /* @__PURE__ */ jsx10("span", { className: "font-og-mono text-og-fg-muted", children: userCode }),
1780
+ /* @__PURE__ */ jsx11("span", { className: "font-og-mono text-og-fg-muted", children: userCode }),
1493
1781
  " \u2014 the one printed by the one-liner \u2014 at",
1494
1782
  " ",
1495
- /* @__PURE__ */ jsx10("span", { className: "font-og-mono text-og-fg-muted", children: verificationUri }),
1783
+ /* @__PURE__ */ jsx11("span", { className: "font-og-mono text-og-fg-muted", children: verificationUri }),
1496
1784
  ".",
1497
- typeof expiresInSeconds === "number" ? /* @__PURE__ */ jsxs10(Fragment2, { children: [
1785
+ typeof expiresInSeconds === "number" ? /* @__PURE__ */ jsxs11(Fragment2, { children: [
1498
1786
  " Expires in ",
1499
1787
  Math.max(0, Math.round(expiresInSeconds / 60)),
1500
1788
  " min."
@@ -1506,7 +1794,7 @@ function EnrollmentDeviceFlow({
1506
1794
  }
1507
1795
 
1508
1796
  // src/components/enrollment-consent.tsx
1509
- import { useState as useState2 } from "react";
1797
+ import { useState as useState3 } from "react";
1510
1798
  import {
1511
1799
  CircleAlertIcon,
1512
1800
  LaptopIcon as LaptopIcon6,
@@ -1516,13 +1804,13 @@ import {
1516
1804
  TerminalIcon as TerminalIcon2,
1517
1805
  UsersIcon as UsersIcon3
1518
1806
  } from "lucide-react";
1519
- import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
1807
+ import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
1520
1808
  function Capability({ icon, title, body }) {
1521
- return /* @__PURE__ */ jsxs11("li", { className: "flex items-start gap-2.5", children: [
1522
- /* @__PURE__ */ jsx11("span", { className: "mt-0.5 shrink-0 text-og-status-failed", children: icon }),
1523
- /* @__PURE__ */ jsxs11("span", { className: "min-w-0", children: [
1524
- /* @__PURE__ */ jsx11("span", { className: "block text-og-base font-medium text-og-fg", children: title }),
1525
- /* @__PURE__ */ jsx11("span", { className: "block text-og-sm text-og-fg-muted", children: body })
1809
+ return /* @__PURE__ */ jsxs12("li", { className: "flex items-start gap-2.5", children: [
1810
+ /* @__PURE__ */ jsx12("span", { className: "mt-0.5 shrink-0 text-og-status-failed", children: icon }),
1811
+ /* @__PURE__ */ jsxs12("span", { className: "min-w-0", children: [
1812
+ /* @__PURE__ */ jsx12("span", { className: "block text-og-base font-medium text-og-fg", children: title }),
1813
+ /* @__PURE__ */ jsx12("span", { className: "block text-og-sm text-og-fg-muted", children: body })
1526
1814
  ] })
1527
1815
  ] });
1528
1816
  }
@@ -1530,26 +1818,42 @@ function EnrollmentConsent({
1530
1818
  userCode,
1531
1819
  machine,
1532
1820
  phase = "review",
1821
+ allowOrganizationScope = false,
1533
1822
  onApprove,
1534
1823
  onDeny,
1535
1824
  errorMessage,
1536
1825
  className
1537
1826
  }) {
1538
- const [allowScreenControl, setAllowScreenControl] = useState2(machine.requestsScreenControl);
1827
+ const [allowScreenControl, setAllowScreenControl] = useState3(machine.requestsScreenControl);
1828
+ const [scope, setScope] = useState3("user");
1539
1829
  const busy = phase === "approving";
1830
+ const scopeCopy = {
1831
+ user: {
1832
+ label: "Personal",
1833
+ body: "Only you can use this machine, from any workspace you can access in this organization."
1834
+ },
1835
+ workspace: {
1836
+ label: "Workspace",
1837
+ body: "Everyone with access to this workspace can use this machine."
1838
+ },
1839
+ organization: {
1840
+ label: "Organization",
1841
+ body: "Members of this organization can use this machine from their workspaces."
1842
+ }
1843
+ };
1540
1844
  if (phase === "approved") {
1541
- return /* @__PURE__ */ jsx11(
1845
+ return /* @__PURE__ */ jsx12(
1542
1846
  ConsentResult,
1543
1847
  {
1544
1848
  className,
1545
1849
  tone: "ok",
1546
1850
  title: "Machine connected",
1547
- body: `${machine.machineName} is now enrolled in this workspace. You can close this page \u2014 it will appear in your Machines dashboard.`
1851
+ body: `${machine.machineName} is now connected with ${scopeCopy[scope].label.toLowerCase()} access. You can close this page \u2014 it will appear in your Machines dashboard.`
1548
1852
  }
1549
1853
  );
1550
1854
  }
1551
1855
  if (phase === "denied") {
1552
- return /* @__PURE__ */ jsx11(
1856
+ return /* @__PURE__ */ jsx12(
1553
1857
  ConsentResult,
1554
1858
  {
1555
1859
  className,
@@ -1560,7 +1864,7 @@ function EnrollmentConsent({
1560
1864
  );
1561
1865
  }
1562
1866
  if (phase === "error") {
1563
- return /* @__PURE__ */ jsx11(
1867
+ return /* @__PURE__ */ jsx12(
1564
1868
  ConsentResult,
1565
1869
  {
1566
1870
  className,
@@ -1570,7 +1874,7 @@ function EnrollmentConsent({
1570
1874
  }
1571
1875
  );
1572
1876
  }
1573
- return /* @__PURE__ */ jsxs11(
1877
+ return /* @__PURE__ */ jsxs12(
1574
1878
  "div",
1575
1879
  {
1576
1880
  "data-enrollment-consent": true,
@@ -1579,65 +1883,104 @@ function EnrollmentConsent({
1579
1883
  className
1580
1884
  ),
1581
1885
  children: [
1582
- /* @__PURE__ */ jsxs11("div", { className: "flex items-start gap-3", children: [
1583
- /* @__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 }) }),
1584
- /* @__PURE__ */ jsxs11("div", { className: "min-w-0", children: [
1585
- /* @__PURE__ */ jsx11("h1", { className: "text-og-md font-semibold text-og-fg", children: "Give the agent your whole machine?" }),
1586
- /* @__PURE__ */ jsxs11("p", { className: "mt-1 text-og-base text-og-fg-muted", children: [
1886
+ /* @__PURE__ */ jsxs12("div", { className: "flex items-start gap-3", children: [
1887
+ /* @__PURE__ */ jsx12("span", { className: "flex size-9 shrink-0 items-center justify-center rounded-full bg-og-status-failed/15 text-og-status-failed", children: /* @__PURE__ */ jsx12(ShieldAlertIcon, { className: "size-5", "aria-hidden": true }) }),
1888
+ /* @__PURE__ */ jsxs12("div", { className: "min-w-0", children: [
1889
+ /* @__PURE__ */ jsx12("h1", { className: "text-og-md font-semibold text-og-fg", children: "Give the agent your whole machine?" }),
1890
+ /* @__PURE__ */ jsxs12("p", { className: "mt-1 text-og-base text-og-fg-muted", children: [
1587
1891
  "Approving lets the OpenGeni agent run on",
1588
1892
  " ",
1589
- /* @__PURE__ */ jsx11("span", { className: "font-medium text-og-fg", children: machine.machineName }),
1893
+ /* @__PURE__ */ jsx12("span", { className: "font-medium text-og-fg", children: machine.machineName }),
1590
1894
  " with full access. This is your real computer \u2014 not a sandbox."
1591
1895
  ] })
1592
1896
  ] })
1593
1897
  ] }),
1594
- /* @__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: [
1595
- /* @__PURE__ */ jsx11(LaptopIcon6, { className: "size-4 text-og-fg-subtle", "aria-hidden": true }),
1596
- /* @__PURE__ */ jsx11("span", { className: "font-medium text-og-fg", children: machine.machineName }),
1597
- /* @__PURE__ */ jsxs11("span", { className: "font-og-mono text-og-fg-subtle", children: [
1898
+ /* @__PURE__ */ jsxs12("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: [
1899
+ /* @__PURE__ */ jsx12(LaptopIcon6, { className: "size-4 text-og-fg-subtle", "aria-hidden": true }),
1900
+ /* @__PURE__ */ jsx12("span", { className: "font-medium text-og-fg", children: machine.machineName }),
1901
+ /* @__PURE__ */ jsxs12("span", { className: "font-og-mono text-og-fg-subtle", children: [
1598
1902
  machine.os,
1599
1903
  "/",
1600
1904
  machine.arch
1601
1905
  ] }),
1602
- /* @__PURE__ */ jsx11("span", { "aria-hidden": true, className: "text-og-fg-subtle", children: "\xB7" }),
1603
- /* @__PURE__ */ jsxs11("span", { className: "text-og-fg-subtle", children: [
1906
+ /* @__PURE__ */ jsx12("span", { "aria-hidden": true, className: "text-og-fg-subtle", children: "\xB7" }),
1907
+ /* @__PURE__ */ jsxs12("span", { className: "text-og-fg-subtle", children: [
1604
1908
  "code ",
1605
- /* @__PURE__ */ jsx11("span", { className: "font-og-mono font-medium text-og-fg", children: userCode })
1909
+ /* @__PURE__ */ jsx12("span", { className: "font-og-mono font-medium text-og-fg", children: userCode })
1606
1910
  ] })
1607
1911
  ] }),
1608
- /* @__PURE__ */ jsxs11("ul", { className: "flex flex-col gap-3", children: [
1609
- /* @__PURE__ */ jsx11(
1912
+ /* @__PURE__ */ jsxs12("ul", { className: "flex flex-col gap-3", children: [
1913
+ /* @__PURE__ */ jsx12(
1610
1914
  Capability,
1611
1915
  {
1612
- icon: /* @__PURE__ */ jsx11(TerminalIcon2, { className: "size-4", "aria-hidden": true }),
1916
+ icon: /* @__PURE__ */ jsx12(TerminalIcon2, { className: "size-4", "aria-hidden": true }),
1613
1917
  title: "Read, write & run anything",
1614
1918
  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."
1615
1919
  }
1616
1920
  ),
1617
- machine.canOfferDisplay ? /* @__PURE__ */ jsx11(
1921
+ machine.canOfferDisplay ? /* @__PURE__ */ jsx12(
1618
1922
  Capability,
1619
1923
  {
1620
- icon: /* @__PURE__ */ jsx11(MonitorIcon2, { className: "size-4", "aria-hidden": true }),
1924
+ icon: /* @__PURE__ */ jsx12(MonitorIcon2, { className: "size-4", "aria-hidden": true }),
1621
1925
  title: "See your screen",
1622
1926
  body: "The agent can capture this machine's display to watch what it is doing."
1623
1927
  }
1624
1928
  ) : null,
1625
- /* @__PURE__ */ jsx11(
1929
+ /* @__PURE__ */ jsx12(
1626
1930
  Capability,
1627
1931
  {
1628
- icon: /* @__PURE__ */ jsx11(UsersIcon3, { className: "size-4", "aria-hidden": true }),
1629
- title: "Shared while connected",
1630
- body: "Any session in this workspace can use this machine while it is online. Disconnect the agent to revoke access instantly."
1932
+ icon: /* @__PURE__ */ jsx12(UsersIcon3, { className: "size-4", "aria-hidden": true }),
1933
+ title: `${scopeCopy[scope].label} access while connected`,
1934
+ body: `${scopeCopy[scope].body} Disconnect the agent to revoke access instantly.`
1631
1935
  }
1632
1936
  )
1633
1937
  ] }),
1634
- machine.canOfferDisplay ? /* @__PURE__ */ jsxs11(
1938
+ /* @__PURE__ */ jsxs12("fieldset", { className: "flex flex-col gap-2", disabled: busy, children: [
1939
+ /* @__PURE__ */ jsx12("legend", { className: "mb-1 text-og-sm font-medium text-og-fg", children: "Who can use this machine?" }),
1940
+ ["user", "workspace", "organization"].map((candidate) => {
1941
+ const disabled = candidate === "organization" && !allowOrganizationScope;
1942
+ return /* @__PURE__ */ jsxs12(
1943
+ "label",
1944
+ {
1945
+ "data-enrollment-scope": candidate,
1946
+ "data-disabled": disabled ? "true" : "false",
1947
+ className: cn(
1948
+ "flex items-start gap-2.5 rounded-og-md border border-og-border bg-og-bg px-3 py-2.5",
1949
+ disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer"
1950
+ ),
1951
+ children: [
1952
+ /* @__PURE__ */ jsx12(
1953
+ "input",
1954
+ {
1955
+ type: "radio",
1956
+ name: "enrollment-scope",
1957
+ value: candidate,
1958
+ checked: scope === candidate,
1959
+ disabled: disabled || busy,
1960
+ onChange: () => setScope(candidate),
1961
+ className: "mt-0.5 size-4 accent-og-accent"
1962
+ }
1963
+ ),
1964
+ /* @__PURE__ */ jsxs12("span", { className: "min-w-0", children: [
1965
+ /* @__PURE__ */ jsxs12("span", { className: "block text-og-base font-medium text-og-fg", children: [
1966
+ scopeCopy[candidate].label,
1967
+ disabled ? " \xB7 organization admin required" : ""
1968
+ ] }),
1969
+ /* @__PURE__ */ jsx12("span", { className: "block text-og-sm text-og-fg-muted", children: scopeCopy[candidate].body })
1970
+ ] })
1971
+ ]
1972
+ },
1973
+ candidate
1974
+ );
1975
+ })
1976
+ ] }),
1977
+ machine.canOfferDisplay ? /* @__PURE__ */ jsxs12(
1635
1978
  "label",
1636
1979
  {
1637
1980
  "data-screen-control-toggle": true,
1638
1981
  className: "flex cursor-pointer items-start gap-3 rounded-og-md border border-og-border bg-og-bg px-3 py-2.5",
1639
1982
  children: [
1640
- /* @__PURE__ */ jsx11(
1983
+ /* @__PURE__ */ jsx12(
1641
1984
  "input",
1642
1985
  {
1643
1986
  type: "checkbox",
@@ -1647,21 +1990,21 @@ function EnrollmentConsent({
1647
1990
  className: "mt-0.5 size-4 accent-og-accent"
1648
1991
  }
1649
1992
  ),
1650
- /* @__PURE__ */ jsxs11("span", { className: "min-w-0", children: [
1651
- /* @__PURE__ */ jsxs11("span", { className: "flex items-center gap-1.5 text-og-base font-medium text-og-fg", children: [
1652
- /* @__PURE__ */ jsx11(ScreenShareIcon, { className: "size-3.5 text-og-fg-muted", "aria-hidden": true }),
1993
+ /* @__PURE__ */ jsxs12("span", { className: "min-w-0", children: [
1994
+ /* @__PURE__ */ jsxs12("span", { className: "flex items-center gap-1.5 text-og-base font-medium text-og-fg", children: [
1995
+ /* @__PURE__ */ jsx12(ScreenShareIcon, { className: "size-3.5 text-og-fg-muted", "aria-hidden": true }),
1653
1996
  "Also let the agent control my mouse & keyboard"
1654
1997
  ] }),
1655
- /* @__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." })
1998
+ /* @__PURE__ */ jsx12("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." })
1656
1999
  ] })
1657
2000
  ]
1658
2001
  }
1659
- ) : /* @__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: [
1660
- /* @__PURE__ */ jsx11(CircleAlertIcon, { className: "mt-px size-3.5 shrink-0 text-og-fg-subtle", "aria-hidden": true }),
2002
+ ) : /* @__PURE__ */ jsxs12("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: [
2003
+ /* @__PURE__ */ jsx12(CircleAlertIcon, { className: "mt-px size-3.5 shrink-0 text-og-fg-subtle", "aria-hidden": true }),
1661
2004
  "This machine has no display, so screen control isn't available \u2014 files, terminal, and git only."
1662
2005
  ] }),
1663
- /* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-2", children: [
1664
- /* @__PURE__ */ jsx11(
2006
+ /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-2", children: [
2007
+ /* @__PURE__ */ jsx12(
1665
2008
  "button",
1666
2009
  {
1667
2010
  type: "button",
@@ -1672,13 +2015,13 @@ function EnrollmentConsent({
1672
2015
  children: "Cancel"
1673
2016
  }
1674
2017
  ),
1675
- /* @__PURE__ */ jsx11(
2018
+ /* @__PURE__ */ jsx12(
1676
2019
  "button",
1677
2020
  {
1678
2021
  type: "button",
1679
2022
  "data-approve": true,
1680
2023
  disabled: busy,
1681
- onClick: () => onApprove?.(allowScreenControl),
2024
+ onClick: () => onApprove?.(allowScreenControl, scope),
1682
2025
  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",
1683
2026
  children: busy ? "Connecting\u2026" : "Grant full access"
1684
2027
  }
@@ -1696,7 +2039,7 @@ function ConsentResult({
1696
2039
  }) {
1697
2040
  const ring = tone === "ok" ? "border-og-status-running/30" : tone === "danger" ? "border-og-status-failed/30" : "border-og-border";
1698
2041
  const iconClass = tone === "ok" ? "text-og-status-running" : tone === "danger" ? "text-og-status-failed" : "text-og-fg-subtle";
1699
- return /* @__PURE__ */ jsxs11(
2042
+ return /* @__PURE__ */ jsxs12(
1700
2043
  "div",
1701
2044
  {
1702
2045
  "data-enrollment-result": tone,
@@ -1706,33 +2049,33 @@ function ConsentResult({
1706
2049
  className
1707
2050
  ),
1708
2051
  children: [
1709
- /* @__PURE__ */ jsx11(
2052
+ /* @__PURE__ */ jsx12(
1710
2053
  "span",
1711
2054
  {
1712
2055
  className: cn(
1713
2056
  "flex size-10 items-center justify-center rounded-full bg-og-surface-2",
1714
2057
  iconClass
1715
2058
  ),
1716
- children: /* @__PURE__ */ jsx11(LaptopIcon6, { className: "size-5", "aria-hidden": true })
2059
+ children: /* @__PURE__ */ jsx12(LaptopIcon6, { className: "size-5", "aria-hidden": true })
1717
2060
  }
1718
2061
  ),
1719
- /* @__PURE__ */ jsx11("h1", { className: "text-og-md font-semibold text-og-fg", children: title }),
1720
- /* @__PURE__ */ jsx11("p", { className: "max-w-sm text-og-base text-og-fg-muted", children: body })
2062
+ /* @__PURE__ */ jsx12("h1", { className: "text-og-md font-semibold text-og-fg", children: title }),
2063
+ /* @__PURE__ */ jsx12("p", { className: "max-w-sm text-og-base text-og-fg-muted", children: body })
1721
2064
  ]
1722
2065
  }
1723
2066
  );
1724
2067
  }
1725
2068
 
1726
2069
  // src/hooks/use-machines.ts
1727
- import { useCallback as useCallback2, useRef as useRef3, useState as useState4 } from "react";
2070
+ import { useCallback as useCallback3, useRef as useRef3, useState as useState6 } from "react";
1728
2071
 
1729
2072
  // src/provider.tsx
1730
2073
  import {
1731
2074
  OpenGeniApiContractMismatchError,
1732
2075
  OPENGENI_API_CONTRACT_REVISION
1733
2076
  } from "@opengeni/sdk";
1734
- import { useCallback, useEffect, useMemo, useRef as useRef2, useState as useState3 } from "react";
1735
- import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
2077
+ import { useCallback, useEffect as useEffect2, useMemo, useRef as useRef2, useState as useState4 } from "react";
2078
+ import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
1736
2079
  var CONTRACT_RELOAD_STORAGE_PREFIX = "opengeni.reloadForApiContract:";
1737
2080
  function OpenGeniProvider({
1738
2081
  client,
@@ -1741,13 +2084,13 @@ function OpenGeniProvider({
1741
2084
  onWorkspaceInteractionEvent,
1742
2085
  children
1743
2086
  }) {
1744
- const [workspaceControlEvent, setWorkspaceControlEvent] = useState3(
2087
+ const [workspaceControlEvent, setWorkspaceControlEvent] = useState4(
1745
2088
  null
1746
2089
  );
1747
- const [workspaceControlConnectionState, setWorkspaceControlConnectionState] = useState3("idle");
1748
- const [workspaceInteractionEvent, setWorkspaceInteractionEvent] = useState3(null);
1749
- const [workspaceInteractionConnectionState, setWorkspaceInteractionConnectionState] = useState3("idle");
1750
- const [contractMismatch, setContractMismatch] = useState3(
2090
+ const [workspaceControlConnectionState, setWorkspaceControlConnectionState] = useState4("idle");
2091
+ const [workspaceInteractionEvent, setWorkspaceInteractionEvent] = useState4(null);
2092
+ const [workspaceInteractionConnectionState, setWorkspaceInteractionConnectionState] = useState4("idle");
2093
+ const [contractMismatch, setContractMismatch] = useState4(
1751
2094
  null
1752
2095
  );
1753
2096
  const callbackRef = useRef2(onWorkspaceControlEvent);
@@ -1778,9 +2121,9 @@ function OpenGeniProvider({
1778
2121
  }
1779
2122
  }, [client]);
1780
2123
  const registerSessionReconciler = useMemo(
1781
- () => (sessionId, key, reconcile) => {
2124
+ () => (sessionId, key, reconcile2) => {
1782
2125
  const sessionReconcilers = reconcilersRef.current.get(sessionId) ?? /* @__PURE__ */ new Map();
1783
- sessionReconcilers.set(key, reconcile);
2126
+ sessionReconcilers.set(key, reconcile2);
1784
2127
  reconcilersRef.current.set(sessionId, sessionReconcilers);
1785
2128
  return () => {
1786
2129
  const current = reconcilersRef.current.get(sessionId);
@@ -1798,7 +2141,7 @@ function OpenGeniProvider({
1798
2141
  await withTimeout(verifyApiContract(), 1e4, "API contract reconciliation");
1799
2142
  const callbacks = [...reconcilersRef.current.get(sessionId)?.values() ?? []];
1800
2143
  await withTimeout(
1801
- Promise.all(callbacks.map((reconcile) => reconcile())).then(() => void 0),
2144
+ Promise.all(callbacks.map((reconcile2) => reconcile2())).then(() => void 0),
1802
2145
  15e3,
1803
2146
  "session reconciliation"
1804
2147
  );
@@ -1812,7 +2155,7 @@ function OpenGeniProvider({
1812
2155
  },
1813
2156
  [verifyApiContract]
1814
2157
  );
1815
- useEffect(() => {
2158
+ useEffect2(() => {
1816
2159
  if (!supportsWorkspaceLiveStream) return;
1817
2160
  const openLiveStream = client.streamWorkspaceLiveEvents;
1818
2161
  setWorkspaceControlEvent(null);
@@ -1882,7 +2225,7 @@ function OpenGeniProvider({
1882
2225
  })();
1883
2226
  return () => controller.abort();
1884
2227
  }, [client, pageLive, supportsWorkspaceLiveStream, verifyApiContract, workspaceId]);
1885
- useEffect(() => {
2228
+ useEffect2(() => {
1886
2229
  if (supportsWorkspaceLiveStream) return;
1887
2230
  if (!pageLive) {
1888
2231
  setWorkspaceControlConnectionState("idle");
@@ -1925,7 +2268,7 @@ function OpenGeniProvider({
1925
2268
  })();
1926
2269
  return () => controller.abort();
1927
2270
  }, [client, pageLive, supportsWorkspaceLiveStream, verifyApiContract, workspaceId]);
1928
- useEffect(() => {
2271
+ useEffect2(() => {
1929
2272
  if (supportsWorkspaceLiveStream) return;
1930
2273
  setWorkspaceInteractionEvent(null);
1931
2274
  if (!pageLive) {
@@ -1989,9 +2332,9 @@ function OpenGeniProvider({
1989
2332
  reconcileSession
1990
2333
  ]
1991
2334
  );
1992
- return /* @__PURE__ */ jsxs12(OpenGeniContext.Provider, { value, children: [
2335
+ return /* @__PURE__ */ jsxs13(OpenGeniContext.Provider, { value, children: [
1993
2336
  children,
1994
- contractMismatch ? /* @__PURE__ */ jsx12(ApiContractMismatchScreen, { mismatch: contractMismatch }) : null
2337
+ contractMismatch ? /* @__PURE__ */ jsx13(ApiContractMismatchScreen, { mismatch: contractMismatch }) : null
1995
2338
  ] });
1996
2339
  }
1997
2340
  function withTimeout(promise, timeoutMs, label) {
@@ -2009,23 +2352,23 @@ function withTimeout(promise, timeoutMs, label) {
2009
2352
  });
2010
2353
  }
2011
2354
  function ApiContractMismatchScreen({ mismatch }) {
2012
- return /* @__PURE__ */ jsx12(
2355
+ return /* @__PURE__ */ jsx13(
2013
2356
  "div",
2014
2357
  {
2015
2358
  className: "og-root fixed inset-0 z-[2147483647] grid place-items-center bg-og-bg/95 p-6 backdrop-blur-sm",
2016
2359
  role: "alert",
2017
2360
  "aria-live": "assertive",
2018
2361
  "data-opengeni-api-contract-mismatch": true,
2019
- children: /* @__PURE__ */ jsxs12("div", { className: "w-full max-w-md rounded-xl border border-og-border bg-og-surface p-6 shadow-2xl", children: [
2020
- /* @__PURE__ */ jsx12("p", { className: "text-og-menu font-semibold text-og-fg", children: "OpenGeni updated" }),
2021
- /* @__PURE__ */ jsx12("p", { className: "mt-2 text-og-menu leading-6 text-og-muted", children: "This tab cannot safely continue with the new server version. Reload it before sending or controlling work." }),
2022
- /* @__PURE__ */ jsxs12("p", { className: "mt-3 font-mono text-og-control text-og-subtle", children: [
2362
+ children: /* @__PURE__ */ jsxs13("div", { className: "w-full max-w-md rounded-xl border border-og-border bg-og-surface p-6 shadow-2xl", children: [
2363
+ /* @__PURE__ */ jsx13("p", { className: "text-og-menu font-semibold text-og-fg", children: "OpenGeni updated" }),
2364
+ /* @__PURE__ */ jsx13("p", { className: "mt-2 text-og-menu leading-6 text-og-muted", children: "This tab cannot safely continue with the new server version. Reload it before sending or controlling work." }),
2365
+ /* @__PURE__ */ jsxs13("p", { className: "mt-3 font-mono text-og-control text-og-subtle", children: [
2023
2366
  "Client ",
2024
2367
  mismatch.expected,
2025
2368
  " \xB7 API ",
2026
2369
  mismatch.actual
2027
2370
  ] }),
2028
- /* @__PURE__ */ jsx12(
2371
+ /* @__PURE__ */ jsx13(
2029
2372
  "button",
2030
2373
  {
2031
2374
  type: "button",
@@ -2046,8 +2389,205 @@ function reloadForContractMismatchOnce(mismatch) {
2046
2389
  window.setTimeout(() => window.location.reload(), 150);
2047
2390
  }
2048
2391
 
2392
+ // src/hooks/shared-machines-list.ts
2393
+ import { useCallback as useCallback2, useEffect as useEffect3, useState as useState5 } from "react";
2394
+ var EMPTY_MACHINES = {
2395
+ activeSandboxId: null,
2396
+ activeEpoch: 0,
2397
+ machines: []
2398
+ };
2399
+ var shares = /* @__PURE__ */ new Map();
2400
+ function emit(share) {
2401
+ for (const subscriber of share.subscribers) {
2402
+ subscriber.onChange();
2403
+ }
2404
+ }
2405
+ function effective(share) {
2406
+ let enabled = false;
2407
+ let pollIntervalMs;
2408
+ for (const subscriber of share.subscribers) {
2409
+ if (!subscriber.enabled || !subscriber.pageLive) continue;
2410
+ enabled = true;
2411
+ if (subscriber.pollIntervalMs !== void 0 && subscriber.pollIntervalMs > 0) {
2412
+ pollIntervalMs = pollIntervalMs === void 0 ? subscriber.pollIntervalMs : Math.min(pollIntervalMs, subscriber.pollIntervalMs);
2413
+ }
2414
+ }
2415
+ return { enabled, pollIntervalMs };
2416
+ }
2417
+ function latestLoad(share) {
2418
+ let load = null;
2419
+ for (const subscriber of share.subscribers) {
2420
+ if (subscriber.enabled && subscriber.pageLive) load = subscriber.load;
2421
+ }
2422
+ return load;
2423
+ }
2424
+ function stopTimer(share) {
2425
+ if (share.timer !== null) {
2426
+ clearTimeout(share.timer);
2427
+ share.timer = null;
2428
+ }
2429
+ }
2430
+ function cancelDispose(share) {
2431
+ if (share.disposeTimer !== null) {
2432
+ clearTimeout(share.disposeTimer);
2433
+ share.disposeTimer = null;
2434
+ }
2435
+ }
2436
+ function arm(share) {
2437
+ stopTimer(share);
2438
+ const { enabled, pollIntervalMs } = effective(share);
2439
+ if (!enabled || pollIntervalMs === void 0) return;
2440
+ share.timer = setTimeout(() => {
2441
+ share.timer = null;
2442
+ void run(share).finally(() => arm(share));
2443
+ }, pollIntervalMs);
2444
+ }
2445
+ async function runFresh(share) {
2446
+ const load = latestLoad(share);
2447
+ if (!load) return;
2448
+ const ticket = ++share.generation;
2449
+ const abort = new AbortController();
2450
+ share.abort = abort;
2451
+ try {
2452
+ const data = await load(abort.signal);
2453
+ if (ticket !== share.generation || abort.signal.aborted) return;
2454
+ share.snapshot = { data, loading: false, error: null };
2455
+ emit(share);
2456
+ } catch (cause) {
2457
+ if (ticket !== share.generation || abort.signal.aborted) return;
2458
+ share.snapshot = {
2459
+ data: share.snapshot.data,
2460
+ loading: false,
2461
+ error: cause instanceof Error ? cause : new Error(String(cause))
2462
+ };
2463
+ emit(share);
2464
+ } finally {
2465
+ if (share.abort === abort) share.abort = null;
2466
+ }
2467
+ }
2468
+ function run(share, force = false) {
2469
+ if (!force && share.inFlight) return share.inFlight;
2470
+ if (force && share.inFlight) {
2471
+ if (share.trailing) return share.trailing;
2472
+ let trailing;
2473
+ trailing = share.inFlight.then(async () => {
2474
+ if (!effective(share).enabled) return;
2475
+ await run(share);
2476
+ }).finally(() => {
2477
+ if (share.trailing === trailing) share.trailing = null;
2478
+ });
2479
+ share.trailing = trailing;
2480
+ return trailing;
2481
+ }
2482
+ let promise;
2483
+ promise = runFresh(share).finally(() => {
2484
+ if (share.inFlight === promise) share.inFlight = null;
2485
+ });
2486
+ share.inFlight = promise;
2487
+ return promise;
2488
+ }
2489
+ function reconcile(share) {
2490
+ const { enabled } = effective(share);
2491
+ if (!enabled) {
2492
+ share.generation += 1;
2493
+ share.abort?.abort();
2494
+ share.abort = null;
2495
+ share.inFlight = null;
2496
+ stopTimer(share);
2497
+ if (share.snapshot.loading) {
2498
+ share.snapshot = { ...share.snapshot, loading: false };
2499
+ emit(share);
2500
+ }
2501
+ return;
2502
+ }
2503
+ if (share.snapshot.data !== null || share.snapshot.error) {
2504
+ arm(share);
2505
+ return;
2506
+ }
2507
+ share.snapshot = { ...share.snapshot, loading: true };
2508
+ emit(share);
2509
+ void run(share).finally(() => arm(share));
2510
+ }
2511
+ function getOrCreate(shareKey) {
2512
+ const existing = shares.get(shareKey);
2513
+ if (existing) {
2514
+ cancelDispose(existing);
2515
+ return existing;
2516
+ }
2517
+ const created = {
2518
+ snapshot: { data: null, loading: true, error: null },
2519
+ subscribers: /* @__PURE__ */ new Set(),
2520
+ timer: null,
2521
+ disposeTimer: null,
2522
+ abort: null,
2523
+ inFlight: null,
2524
+ trailing: null,
2525
+ generation: 0
2526
+ };
2527
+ shares.set(shareKey, created);
2528
+ return created;
2529
+ }
2530
+ async function refreshSharedMachinesList(shareKey) {
2531
+ const share = shares.get(shareKey);
2532
+ if (!share) return;
2533
+ await run(share, true);
2534
+ }
2535
+ function useSharedMachinesList(shareKey, load, options = {}) {
2536
+ const enabled = options.enabled ?? true;
2537
+ const pageLive = usePageLiveActivity();
2538
+ const [, setRevision] = useState5(0);
2539
+ const live = shares.get(shareKey)?.snapshot;
2540
+ const view = live ?? { data: null, loading: enabled && pageLive, error: null };
2541
+ useEffect3(() => {
2542
+ const share = getOrCreate(shareKey);
2543
+ const subscriber = {
2544
+ load,
2545
+ pollIntervalMs: options.pollIntervalMs,
2546
+ enabled,
2547
+ pageLive,
2548
+ onChange: () => setRevision((revision) => revision + 1)
2549
+ };
2550
+ share.subscribers.add(subscriber);
2551
+ setRevision((revision) => revision + 1);
2552
+ reconcile(share);
2553
+ return () => {
2554
+ share.subscribers.delete(subscriber);
2555
+ if (share.subscribers.size > 0) {
2556
+ reconcile(share);
2557
+ return;
2558
+ }
2559
+ share.generation += 1;
2560
+ share.abort?.abort();
2561
+ share.abort = null;
2562
+ share.inFlight = null;
2563
+ share.trailing = null;
2564
+ stopTimer(share);
2565
+ cancelDispose(share);
2566
+ share.disposeTimer = setTimeout(() => {
2567
+ share.disposeTimer = null;
2568
+ if (share.subscribers.size === 0 && shares.get(shareKey) === share) {
2569
+ shares.delete(shareKey);
2570
+ }
2571
+ }, 0);
2572
+ };
2573
+ }, [shareKey, load, options.pollIntervalMs, enabled, pageLive]);
2574
+ const refresh = useCallback2(() => refreshSharedMachinesList(shareKey), [shareKey]);
2575
+ return { ...view, refresh };
2576
+ }
2577
+
2049
2578
  // src/hooks/use-machines.ts
2050
- var EMPTY = { activeSandboxId: null, activeEpoch: 0, machines: [] };
2579
+ var MACHINES_DASHBOARD_POLL_MS = 1e4;
2580
+ var MACHINES_SESSION_POLL_MS = 15e3;
2581
+ var MACHINES_COMPOSER_POLL_MS = 3e4;
2582
+ var machinesClientIds = /* @__PURE__ */ new WeakMap();
2583
+ var nextMachinesClientId = 1;
2584
+ function machinesClientId(client) {
2585
+ const existing = machinesClientIds.get(client);
2586
+ if (existing !== void 0) return existing;
2587
+ const id = nextMachinesClientId++;
2588
+ machinesClientIds.set(client, id);
2589
+ return id;
2590
+ }
2051
2591
  function swapFailureMessage(result) {
2052
2592
  if (result.reason?.trim()) return result.reason;
2053
2593
  switch (result.code) {
@@ -2069,10 +2609,10 @@ function useMachines(options = {}) {
2069
2609
  const { client, workspaceId } = useOpenGeni(options);
2070
2610
  const machinesClient = options.machinesClient ?? client;
2071
2611
  const sessionId = options.sessionId;
2072
- const identityKey = `${workspaceId}\0${sessionId ?? ""}`;
2612
+ const identityKey = `${workspaceId}\0${sessionId ?? ""}\0${machinesClientId(machinesClient)}`;
2073
2613
  const identityRef = useRef3(identityKey);
2074
2614
  identityRef.current = identityKey;
2075
- const load = useCallback2(
2615
+ const load = useCallback3(
2076
2616
  async (signal) => {
2077
2617
  return await machinesClient.listMachines(workspaceId, {
2078
2618
  ...sessionId ? { sessionId } : {},
@@ -2086,26 +2626,32 @@ function useMachines(options = {}) {
2086
2626
  loading,
2087
2627
  error,
2088
2628
  refresh
2089
- } = usePolledValue(load, {
2629
+ } = useSharedMachinesList(identityKey, load, {
2090
2630
  pollIntervalMs: options.pollIntervalMs,
2091
2631
  enabled: options.enabled
2092
2632
  });
2093
- const { run, mutating, mutationError, clearMutationError } = useMutationRunner(identityKey);
2094
- const [attachState, setAttachState] = useState4(() => ({ identity: identityKey, sandboxId: null }));
2633
+ const { run: run2, mutating, mutationError, clearMutationError } = useMutationRunner(identityKey);
2634
+ const [attachState, setAttachState] = useState6(() => ({ identity: identityKey, sandboxId: null }));
2095
2635
  const attachingSandboxId = attachState.identity === identityKey ? attachState.sandboxId : null;
2096
- const [removeState, setRemoveState] = useState4(() => ({ identity: identityKey, enrollmentId: null }));
2636
+ const [removeState, setRemoveState] = useState6(() => ({ identity: identityKey, enrollmentId: null }));
2097
2637
  const removingEnrollmentId = removeState.identity === identityKey ? removeState.enrollmentId : null;
2098
- const data = loadedData ?? EMPTY;
2638
+ const [updateState, setUpdateState] = useState6(() => ({ identity: identityKey, enrollmentId: null }));
2639
+ const updatingEnrollmentId = updateState.identity === identityKey ? updateState.enrollmentId : null;
2640
+ const [operationPolicyState, setOperationPolicyState] = useState6(() => ({ identity: identityKey, enrollmentId: null }));
2641
+ const updatingOperationPolicyEnrollmentId = operationPolicyState.identity === identityKey ? operationPolicyState.enrollmentId : null;
2642
+ const data = loadedData ?? EMPTY_MACHINES;
2099
2643
  const canAttach = sessionId !== void 0 && (typeof machinesClient.attachMachine === "function" || typeof machinesClient.swapActiveSandbox === "function");
2100
2644
  const canRemove = typeof machinesClient.removeEnrollment === "function";
2101
- const attach = useCallback2(
2645
+ const canUpdateAgent = typeof machinesClient.updateMachineAgent === "function";
2646
+ const canUpdateOperationPolicy = typeof machinesClient.updateMachineOperationPolicy === "function";
2647
+ const attach = useCallback3(
2102
2648
  async (sandboxId) => {
2103
2649
  if (sessionId === void 0) return false;
2104
2650
  const runSwap = machinesClient.attachMachine ? () => machinesClient.attachMachine(workspaceId, sessionId, sandboxId) : machinesClient.swapActiveSandbox ? () => machinesClient.swapActiveSandbox(workspaceId, sessionId, { target: sandboxId }) : null;
2105
2651
  if (!runSwap) return false;
2106
2652
  const ownedIdentity = identityKey;
2107
2653
  setAttachState({ identity: ownedIdentity, sandboxId });
2108
- const result = await run(async () => {
2654
+ const result = await run2(async () => {
2109
2655
  const response = await runSwap();
2110
2656
  if (response && typeof response === "object" && "swapped" in response && response.swapped === false) {
2111
2657
  throw new Error(swapFailureMessage(response));
@@ -2118,21 +2664,21 @@ function useMachines(options = {}) {
2118
2664
  }
2119
2665
  return result === true;
2120
2666
  },
2121
- [machinesClient, workspaceId, sessionId, identityKey, run, refresh]
2667
+ [machinesClient, workspaceId, sessionId, identityKey, run2, refresh]
2122
2668
  );
2123
- const fetchSeries = useCallback2(
2669
+ const fetchSeries = useCallback3(
2124
2670
  async (enrollmentId, window2 = "1h") => {
2125
2671
  if (!machinesClient.machineMetricsSeries) return [];
2126
2672
  return await machinesClient.machineMetricsSeries(workspaceId, enrollmentId, { window: window2 });
2127
2673
  },
2128
2674
  [machinesClient, workspaceId]
2129
2675
  );
2130
- const remove = useCallback2(
2676
+ const remove = useCallback3(
2131
2677
  async (enrollmentId, request = {}) => {
2132
2678
  if (!machinesClient.removeEnrollment) return null;
2133
2679
  const ownedIdentity = identityKey;
2134
2680
  setRemoveState({ identity: ownedIdentity, enrollmentId });
2135
- const result = await run(
2681
+ const result = await run2(
2136
2682
  () => machinesClient.removeEnrollment(workspaceId, enrollmentId, request)
2137
2683
  );
2138
2684
  if (identityRef.current === ownedIdentity) {
@@ -2143,7 +2689,37 @@ function useMachines(options = {}) {
2143
2689
  }
2144
2690
  return result;
2145
2691
  },
2146
- [machinesClient, workspaceId, identityKey, run, refresh]
2692
+ [machinesClient, workspaceId, identityKey, run2, refresh]
2693
+ );
2694
+ const updateAgent = useCallback3(
2695
+ async (enrollmentId) => {
2696
+ if (!machinesClient.updateMachineAgent) return null;
2697
+ const ownedIdentity = identityKey;
2698
+ setUpdateState({ identity: ownedIdentity, enrollmentId });
2699
+ const result = await run2(() => machinesClient.updateMachineAgent(workspaceId, enrollmentId));
2700
+ if (identityRef.current === ownedIdentity) {
2701
+ setUpdateState({ identity: ownedIdentity, enrollmentId: null });
2702
+ await refresh();
2703
+ }
2704
+ return result;
2705
+ },
2706
+ [machinesClient, workspaceId, identityKey, run2, refresh]
2707
+ );
2708
+ const updateOperationPolicy = useCallback3(
2709
+ async (enrollmentId, request) => {
2710
+ if (!machinesClient.updateMachineOperationPolicy) return null;
2711
+ const ownedIdentity = identityKey;
2712
+ setOperationPolicyState({ identity: ownedIdentity, enrollmentId });
2713
+ const result = await run2(
2714
+ () => machinesClient.updateMachineOperationPolicy(workspaceId, enrollmentId, request)
2715
+ );
2716
+ if (identityRef.current === ownedIdentity) {
2717
+ setOperationPolicyState({ identity: ownedIdentity, enrollmentId: null });
2718
+ if (result) await refresh();
2719
+ }
2720
+ return result;
2721
+ },
2722
+ [machinesClient, workspaceId, identityKey, run2, refresh]
2147
2723
  );
2148
2724
  return {
2149
2725
  machines: data.machines,
@@ -2160,6 +2736,12 @@ function useMachines(options = {}) {
2160
2736
  remove,
2161
2737
  canRemove,
2162
2738
  removingEnrollmentId,
2739
+ updateAgent,
2740
+ canUpdateAgent,
2741
+ updatingEnrollmentId,
2742
+ updateOperationPolicy,
2743
+ canUpdateOperationPolicy,
2744
+ updatingOperationPolicyEnrollmentId,
2163
2745
  mutationError,
2164
2746
  clearMutationError
2165
2747
  };
@@ -2168,6 +2750,9 @@ function useMachines(options = {}) {
2168
2750
  export {
2169
2751
  OpenGeniProvider,
2170
2752
  connectionStatusForState,
2753
+ MACHINES_DASHBOARD_POLL_MS,
2754
+ MACHINES_SESSION_POLL_MS,
2755
+ MACHINES_COMPOSER_POLL_MS,
2171
2756
  useMachines,
2172
2757
  CONNECTION_STATUS_META,
2173
2758
  MACHINE_STATE_BADGE_META,
@@ -2186,6 +2771,7 @@ export {
2186
2771
  WINDOW_LABEL,
2187
2772
  METRICS,
2188
2773
  pointsFor,
2774
+ MachineOperationPolicyEditor,
2189
2775
  MachineDetail,
2190
2776
  MachinesDashboard,
2191
2777
  MachineDockBar,
@@ -2193,4 +2779,4 @@ export {
2193
2779
  EnrollmentDeviceFlow,
2194
2780
  EnrollmentConsent
2195
2781
  };
2196
- //# sourceMappingURL=chunk-HA46CYQK.js.map
2782
+ //# sourceMappingURL=chunk-XXWH2JLC.js.map