@opengeni/api-router 0.30.3 → 2.2.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 (165) hide show
  1. package/dist/api-websocket.d.ts +18 -0
  2. package/dist/app.js +1 -1
  3. package/dist/auth/managed-auth.d.ts +6 -0
  4. package/dist/{chunk-WCXHI3FF.js → chunk-3TP54PPX.js} +26692 -15389
  5. package/dist/chunk-3TP54PPX.js.map +1 -0
  6. package/dist/codemode.d.ts +8 -1
  7. package/dist/company-brain-okf.d.ts +24 -0
  8. package/dist/connected-machine-computer-access.d.ts +11 -0
  9. package/dist/connection-authority-owner.d.ts +59 -0
  10. package/dist/connection-ownership.d.ts +47 -0
  11. package/dist/controller-data-plane.d.ts +16 -0
  12. package/dist/editable-artifact-websocket.d.ts +5 -15
  13. package/dist/editable-artifact-workspace-files.d.ts +1 -1
  14. package/dist/http/api-error.d.ts +10 -0
  15. package/dist/http/cors.d.ts +5 -0
  16. package/dist/http/interaction-control-error.d.ts +13 -0
  17. package/dist/http/sse.d.ts +2 -3
  18. package/dist/index.d.ts +5 -2
  19. package/dist/index.js +326 -82
  20. package/dist/index.js.map +1 -1
  21. package/dist/integrations/atlassian.d.ts +17 -0
  22. package/dist/integrations/fiken.d.ts +7 -2
  23. package/dist/integrations/google-drive.d.ts +18 -0
  24. package/dist/integrations/oauth-client.d.ts +22 -3
  25. package/dist/integrations/oauth-profiles.d.ts +195 -0
  26. package/dist/integrations/personal-github-repositories.d.ts +45 -0
  27. package/dist/integrations/personal-github.d.ts +40 -0
  28. package/dist/integrations/pr-review-provider.d.ts +22 -0
  29. package/dist/integrations/provider-oauth.d.ts +8 -0
  30. package/dist/integrations/slack-app-home.d.ts +24 -0
  31. package/dist/integrations/slack-bot.d.ts +157 -9
  32. package/dist/integrations/slack-interactions.d.ts +39 -5
  33. package/dist/integrations/slack-routing.d.ts +120 -0
  34. package/dist/integrations/social-oauth.d.ts +5 -0
  35. package/dist/interaction-frame-proxy.d.ts +68 -0
  36. package/dist/mcp/company-brain-governed-writes.d.ts +26 -0
  37. package/dist/mcp/company-profile-agent-admin.d.ts +44 -0
  38. package/dist/mcp/documents.d.ts +1 -0
  39. package/dist/mcp/remember.d.ts +28 -0
  40. package/dist/mcp/request-abort.d.ts +19 -0
  41. package/dist/mcp/scheduled-task-view.d.ts +4 -4
  42. package/dist/mcp/server.d.ts +15 -2
  43. package/dist/mcp/session-view.d.ts +4 -0
  44. package/dist/mcp/session-wait.d.ts +137 -0
  45. package/dist/routes/automations.d.ts +4 -0
  46. package/dist/routes/billing.d.ts +5 -0
  47. package/dist/routes/browser-sessions.d.ts +4 -1
  48. package/dist/routes/company-brain.d.ts +3 -0
  49. package/dist/routes/company-profile.d.ts +2 -1
  50. package/dist/routes/connection-authorities.d.ts +3 -0
  51. package/dist/routes/personal-github-git-broker.d.ts +29 -0
  52. package/dist/routes/personal-github.d.ts +3 -0
  53. package/dist/routes/pr-review.d.ts +3 -0
  54. package/dist/routes/sessions.d.ts +1 -0
  55. package/dist/routes/user-resource-authorities.d.ts +3 -0
  56. package/dist/routes/workspace-capture.d.ts +10 -2
  57. package/dist/routes/workspace-learning.d.ts +3 -0
  58. package/dist/routes/workspaces.d.ts +19 -0
  59. package/dist/sandbox/channel-a.d.ts +11 -1
  60. package/dist/sandbox/connection-authority.d.ts +3 -0
  61. package/dist/sandbox/enrollment.d.ts +2 -0
  62. package/dist/sandbox/machines.d.ts +2 -2
  63. package/dist/sandbox/metrics-ingestion.d.ts +10 -20
  64. package/dist/sandbox/viewer.d.ts +24 -1
  65. package/dist/sandbox-file-artifacts.d.ts +11 -0
  66. package/dist/scheduled-task-deletion.d.ts +14 -0
  67. package/dist/slack-reaction-files.d.ts +1 -1
  68. package/dist/temporal-schedule-cleanup.d.ts +1 -0
  69. package/package.json +20 -18
  70. package/src/api-websocket.ts +23 -0
  71. package/src/app.ts +161 -23
  72. package/src/auth/managed-auth.ts +25 -3
  73. package/src/codemode.ts +53 -23
  74. package/src/codex-realtime.ts +8 -2
  75. package/src/company-brain-okf.ts +340 -0
  76. package/src/connected-machine-computer-access.ts +33 -0
  77. package/src/connection-authority-owner.ts +61 -0
  78. package/src/connection-ownership.ts +180 -0
  79. package/src/controller-data-plane.ts +47 -0
  80. package/src/editable-artifact-native-kernel.ts +26 -15
  81. package/src/editable-artifact-office-import.ts +28 -3
  82. package/src/editable-artifact-production.ts +13 -7
  83. package/src/editable-artifact-websocket.ts +11 -18
  84. package/src/editable-artifact-workspace-files.ts +31 -6
  85. package/src/http/api-error.ts +28 -0
  86. package/src/http/auth.ts +4 -0
  87. package/src/http/cors.ts +35 -0
  88. package/src/http/interaction-control-error.ts +164 -0
  89. package/src/http/sse.ts +112 -66
  90. package/src/index.ts +46 -6
  91. package/src/integrations/atlassian.ts +146 -35
  92. package/src/integrations/fiken.ts +102 -22
  93. package/src/integrations/google-drive.ts +301 -92
  94. package/src/integrations/oauth-client.ts +272 -143
  95. package/src/integrations/oauth-profiles.ts +477 -0
  96. package/src/integrations/personal-github-repositories.ts +445 -0
  97. package/src/integrations/personal-github.ts +705 -0
  98. package/src/integrations/pr-review-provider.ts +246 -0
  99. package/src/integrations/provider-oauth.ts +121 -5
  100. package/src/integrations/slack-app-home.ts +300 -0
  101. package/src/integrations/slack-bot.ts +342 -57
  102. package/src/integrations/slack-interactions.ts +1898 -259
  103. package/src/integrations/slack-routing.ts +337 -0
  104. package/src/integrations/social-oauth.ts +25 -0
  105. package/src/interaction-frame-proxy.ts +409 -0
  106. package/src/mcp/company-brain-governed-writes.ts +262 -0
  107. package/src/mcp/company-profile-agent-admin.ts +205 -0
  108. package/src/mcp/documents.ts +37 -6
  109. package/src/mcp/files.ts +16 -1
  110. package/src/mcp/remember.ts +181 -0
  111. package/src/mcp/request-abort.ts +44 -0
  112. package/src/mcp/scheduled-task-view.ts +1 -0
  113. package/src/mcp/server.ts +719 -369
  114. package/src/mcp/session-view.ts +54 -0
  115. package/src/mcp/session-wait.ts +554 -0
  116. package/src/model-catalog.ts +20 -12
  117. package/src/routes/api-integrations.ts +4 -0
  118. package/src/routes/api-keys.ts +1 -0
  119. package/src/routes/automations.ts +534 -0
  120. package/src/routes/billing.ts +57 -1
  121. package/src/routes/browser-sessions.ts +329 -79
  122. package/src/routes/channels.ts +17 -1
  123. package/src/routes/codex.ts +50 -0
  124. package/src/routes/company-brain.ts +367 -0
  125. package/src/routes/company-profile.ts +1 -1
  126. package/src/routes/computer-sessions.ts +320 -93
  127. package/src/routes/connection-authorities.ts +139 -0
  128. package/src/routes/connections.ts +144 -34
  129. package/src/routes/documents.ts +353 -4
  130. package/src/routes/editable-artifacts.ts +11 -3
  131. package/src/routes/enrollments.ts +116 -26
  132. package/src/routes/environments.ts +127 -55
  133. package/src/routes/files.ts +72 -11
  134. package/src/routes/install.ts +82 -28
  135. package/src/routes/integration-facets.ts +29 -0
  136. package/src/routes/interaction-resources.ts +20 -2
  137. package/src/routes/machines.ts +275 -13
  138. package/src/routes/organization-memberships.ts +717 -28
  139. package/src/routes/packs.ts +4 -3
  140. package/src/routes/personal-github-git-broker.ts +785 -0
  141. package/src/routes/personal-github.ts +319 -0
  142. package/src/routes/pr-review.ts +531 -0
  143. package/src/routes/rigs.ts +104 -39
  144. package/src/routes/scheduled-tasks.ts +19 -21
  145. package/src/routes/sessions.ts +775 -38
  146. package/src/routes/social.ts +14 -2
  147. package/src/routes/supergrok.ts +23 -4
  148. package/src/routes/transcription-recordings.ts +8 -2
  149. package/src/routes/user-resource-authorities.ts +138 -0
  150. package/src/routes/workspace-artifacts.ts +4 -1
  151. package/src/routes/workspace-capture.ts +21 -0
  152. package/src/routes/workspace-learning.ts +228 -0
  153. package/src/routes/workspaces.ts +59 -6
  154. package/src/sandbox/auth-callout.ts +38 -12
  155. package/src/sandbox/channel-a.ts +242 -19
  156. package/src/sandbox/connection-authority.ts +3 -0
  157. package/src/sandbox/enrollment.ts +15 -3
  158. package/src/sandbox/machines.ts +186 -64
  159. package/src/sandbox/metrics-ingestion.ts +220 -58
  160. package/src/sandbox/viewer.ts +243 -19
  161. package/src/sandbox-file-artifacts.ts +329 -0
  162. package/src/scheduled-task-deletion.ts +127 -0
  163. package/src/slack-reaction-files.ts +16 -5
  164. package/src/temporal-schedule-cleanup.ts +13 -0
  165. package/dist/chunk-WCXHI3FF.js.map +0 -1
@@ -1,10 +1,10 @@
1
1
  // apps/api/src/sandbox/machines.ts — the M10 Machines-DASHBOARD service (design
2
2
  // §10.7). Builds the `MachinesResponse` the dashboard renders: the workspace's
3
3
  // enrolled selfhosted machines, each enriched with
4
- // * STATE — the M3 liveness (online/reconnecting/offline) overlaid with the
5
- // enrollment-derived consent/display reasons (consent_required /
6
- // display_unavailable) — a real ControlRpc ping (the subject IS the registry),
7
- // reusing the M7 fleet probe;
4
+ // * STATE — heartbeat + goodbye liveness (online/offline) overlaid with the
5
+ // enrollment-derived display reason (display_unavailable). This list does
6
+ // not ControlRpc-ping; attach, capability negotiation, and fleet tools
7
+ // still probe when they need a live responder.
8
8
  // * METRICS — the latest machine_metrics_latest row (or null before a first
9
9
  // heartbeat), projected to the contract's MetricSample;
10
10
  // * sharedSessionCount — the lease refcount (how many sessions share this one
@@ -21,6 +21,7 @@
21
21
  import type { Settings } from "@opengeni/config";
22
22
  import {
23
23
  getSession,
24
+ getLiveEnrollmentConnection,
24
25
  listEnrollments,
25
26
  listSandboxes,
26
27
  readActiveSandbox,
@@ -30,32 +31,116 @@ import {
30
31
  type EnrollmentRecord,
31
32
  type MachineMetricsRow,
32
33
  } from "@opengeni/db";
33
- import type { EventBus } from "@opengeni/events";
34
34
  import { MachineView, MetricSample, type MachinesResponse } from "@opengeni/contracts";
35
- import {
36
- NatsControlRpc,
37
- selfhostedLiveness,
38
- SelfhostedSession,
39
- type ControlRpc,
40
- type NatsRequestConnection,
41
- } from "@opengeni/runtime/sandbox";
42
- import { relayConfigFromSettings } from "@opengeni/core";
35
+ import { selfhostedHeartbeatLiveness } from "@opengeni/runtime/sandbox";
43
36
 
44
37
  export type MachinesServices = {
45
38
  db: Database;
46
39
  settings: Settings;
47
- bus?: EventBus;
48
40
  };
49
41
 
50
- const PROBE_TIMEOUT_MS = 5_000;
42
+ const ACTIVE_UPDATE_STATUSES = new Set([
43
+ "requested",
44
+ "accepted",
45
+ "waiting_for_idle",
46
+ "downloading",
47
+ "verifying",
48
+ "applying",
49
+ "restarting",
50
+ ]);
51
51
 
52
- function controlRpc(bus: EventBus | undefined): ControlRpc {
53
- return new NatsControlRpc(async (): Promise<NatsRequestConnection | null> => {
54
- if (!bus) {
55
- return null;
56
- }
57
- return bus.getRequestConnection();
58
- });
52
+ function compareAgentVersions(left: string, right: string): number | null {
53
+ const parse = (value: string): { core: number[]; prerelease: string | null } | null => {
54
+ const match = /^(?:v)?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/.exec(value.trim());
55
+ if (!match) return null;
56
+ return {
57
+ core: [Number(match[1]), Number(match[2]), Number(match[3])],
58
+ prerelease: match[4] ?? null,
59
+ };
60
+ };
61
+ const a = parse(left);
62
+ const b = parse(right);
63
+ if (!a || !b) return null;
64
+ for (let index = 0; index < 3; index += 1) {
65
+ const delta = a.core[index]! - b.core[index]!;
66
+ if (delta !== 0) return Math.sign(delta);
67
+ }
68
+ if (a.prerelease === b.prerelease) return 0;
69
+ if (a.prerelease === null) return 1;
70
+ if (b.prerelease === null) return -1;
71
+ return a.prerelease.localeCompare(b.prerelease);
72
+ }
73
+
74
+ function runtimeFor(settings: Settings, enrollment: EnrollmentRecord): MachineView["runtime"] {
75
+ const desiredVersion =
76
+ enrollment.agentUpdateChannel === "beta"
77
+ ? (settings.agentBetaVersion ?? settings.agentStableVersion)
78
+ : settings.agentStableVersion;
79
+ const update = enrollment.agentUpdate;
80
+ const activeUpdate = update ? ACTIVE_UPDATE_STATUSES.has(update.status) : false;
81
+ const order =
82
+ enrollment.agentVersion && desiredVersion
83
+ ? compareAgentVersions(enrollment.agentVersion, desiredVersion)
84
+ : null;
85
+ const versionState = activeUpdate
86
+ ? "updating"
87
+ : update?.status === "failed"
88
+ ? "update_failed"
89
+ : order === null
90
+ ? "unknown"
91
+ : order === 0
92
+ ? "current"
93
+ : order < 0
94
+ ? "outdated"
95
+ : "ahead";
96
+ const capability = (name: string): boolean => enrollment.agentCapabilities[name] === true;
97
+ return {
98
+ installedVersion: enrollment.agentVersion,
99
+ binarySha256: enrollment.agentBinarySha256,
100
+ updateChannel: enrollment.agentUpdateChannel,
101
+ desiredVersion,
102
+ versionState,
103
+ capabilities: {
104
+ exec: capability("exec"),
105
+ filesystem: capability("filesystem"),
106
+ git: capability("git"),
107
+ pty: capability("pty"),
108
+ desktop: capability("desktop"),
109
+ opStream: capability("opStream"),
110
+ browserBridge: capability("browserBridge"),
111
+ operationResourcePolicy: capability("operationResourcePolicy"),
112
+ operationCpuQuota: capability("operationCpuQuota"),
113
+ },
114
+ update,
115
+ };
116
+ }
117
+
118
+ function connectionAuthorityFor(
119
+ enrollment: EnrollmentRecord | null,
120
+ liveConnection: EnrollmentRecord | null,
121
+ ): MachineView["connectionAuthority"] {
122
+ if (!enrollment) {
123
+ return {
124
+ state: "not_applicable",
125
+ generation: 0,
126
+ supersededCount: 0,
127
+ leaseExpiresAt: null,
128
+ duplicateRunnerDeniedCount: 0,
129
+ duplicateRunnerDeniedAt: null,
130
+ };
131
+ }
132
+ return {
133
+ state: liveConnection
134
+ ? "active"
135
+ : enrollment.connectionInstanceId && enrollment.connectionLeaseExpiresAt
136
+ ? "expired"
137
+ : "unclaimed",
138
+ generation: enrollment.connectionGeneration,
139
+ supersededCount: Math.max(0, enrollment.connectionGeneration - 1),
140
+ leaseExpiresAt: enrollment.connectionLeaseExpiresAt,
141
+ duplicateRunnerDeniedCount: enrollment.connectionDuplicateDeniedCount,
142
+ duplicateRunnerDeniedAt: enrollment.connectionDuplicateDeniedAt,
143
+ };
59
144
  }
60
145
 
61
146
  /**
@@ -83,36 +168,8 @@ export function metricRowToSample(row: MachineMetricsRow): MetricSample {
83
168
  });
84
169
  }
85
170
 
86
- /** Probe an enrolled machine's liveness — a real ControlRpc ping mapped through
87
- * `selfhostedLiveness` (the enrollment status/consent/display + lastSeenAt
88
- * disambiguate a probe-miss into reconnecting vs offline). Mirrors the M7 fleet
89
- * probe. A non-active enrollment is offline without a probe. */
90
- async function probeEnrollment(
91
- services: MachinesServices,
92
- workspaceId: string,
93
- enrollment: EnrollmentRecord,
94
- ): Promise<{
95
- state: "online" | "reconnecting" | "offline";
96
- consented: boolean;
97
- hasDisplay: boolean;
98
- }> {
99
- const { settings, bus } = services;
100
- let probeResponded = false;
101
- if (enrollment.status === "active") {
102
- const session = new SelfhostedSession({
103
- workspaceId,
104
- agentId: enrollment.id,
105
- controlRpc: controlRpc(bus),
106
- relay: relayConfigFromSettings(settings),
107
- timeoutMs: PROBE_TIMEOUT_MS,
108
- });
109
- try {
110
- probeResponded = await session.ping();
111
- } catch {
112
- probeResponded = false;
113
- }
114
- }
115
- const derived = selfhostedLiveness({
171
+ function enrollmentLiveness(enrollment: EnrollmentRecord) {
172
+ return selfhostedHeartbeatLiveness({
116
173
  enrollment: {
117
174
  status: enrollment.status,
118
175
  exposure: enrollment.exposure,
@@ -122,16 +179,15 @@ async function probeEnrollment(
122
179
  wentOfflineAt: enrollment.wentOfflineAt,
123
180
  wentOfflineReason: enrollment.wentOfflineReason,
124
181
  },
125
- probeResponded,
126
182
  });
127
- return { state: derived.state, consented: derived.consented, hasDisplay: derived.hasDisplay };
128
183
  }
129
184
 
130
185
  /**
131
186
  * Resolve the dashboard STATE of a machine. State reflects REACHABILITY + the
132
187
  * VIEW plane only: an online machine with no display → `display_unavailable` (no
133
188
  * desktop stream, but compute — exec/fs/git/terminal — still works); otherwise
134
- * the liveness state (online/reconnecting/offline). It deliberately does NOT fold
189
+ * the liveness state (online/offline; list heartbeat never yields reconnecting).
190
+ * It deliberately does NOT fold
135
191
  * in screen-control consent: a displayed machine can be VIEWED (read-only) and
136
192
  * used for compute regardless of `allowScreenControl` — only INPUT (ComputerUse /
137
193
  * an interactive stream) needs that consent, which is a per-capability concern
@@ -163,7 +219,12 @@ function machineStateFor(
163
219
  */
164
220
  export async function listMachines(
165
221
  services: MachinesServices,
166
- input: { workspaceId: string; sessionId?: string | null },
222
+ input: {
223
+ accountId?: string;
224
+ workspaceId: string;
225
+ subjectId?: string;
226
+ sessionId?: string | null;
227
+ },
167
228
  ): Promise<MachinesResponse> {
168
229
  const { db } = services;
169
230
  const { workspaceId } = input;
@@ -210,18 +271,59 @@ export async function listMachines(
210
271
  allowScreenControl: false,
211
272
  sharedSessionCount: 1,
212
273
  lastSeenAt: null,
274
+ connectionAuthority: connectionAuthorityFor(null, null),
275
+ runtime: null,
276
+ operationPolicy: null,
213
277
  metrics: null,
214
278
  }),
215
279
  );
216
280
  }
217
281
 
218
282
  // The workspace's enrolled selfhosted machines. One bulk metrics read joined
219
- // onto the machines (no N+1). Each machine is probed for liveness.
220
- const [sandboxes, enrollments, metricsByEnrollment] = await Promise.all([
283
+ // onto the machines (no N+1). Liveness is the durable heartbeat cursor.
284
+ const [legacySandboxes, enrollments] = await Promise.all([
221
285
  listSandboxes(db, workspaceId),
222
- listEnrollments(db, workspaceId, { status: "active" }),
223
- readMachineMetricsLatestForWorkspace(db, workspaceId),
286
+ listEnrollments(
287
+ db,
288
+ input.accountId && input.subjectId
289
+ ? {
290
+ accountId: input.accountId,
291
+ workspaceId,
292
+ subjectId: input.subjectId,
293
+ }
294
+ : workspaceId,
295
+ { status: "active" },
296
+ ),
224
297
  ]);
298
+ const metricsByEnrollment = new Map<string, MachineMetricsRow>();
299
+ for (const originWorkspaceId of new Set(enrollments.map((entry) => entry.workspaceId))) {
300
+ const originMetrics = await readMachineMetricsLatestForWorkspace(db, originWorkspaceId);
301
+ for (const [enrollmentId, metrics] of originMetrics) {
302
+ metricsByEnrollment.set(enrollmentId, metrics);
303
+ }
304
+ }
305
+ const scopedSandboxes = enrollments.flatMap((enrollment) =>
306
+ enrollment.sandboxId
307
+ ? [
308
+ {
309
+ id: enrollment.sandboxId,
310
+ accountId: enrollment.accountId,
311
+ workspaceId: enrollment.workspaceId,
312
+ kind: "selfhosted" as const,
313
+ name: enrollment.sandboxName ?? `${enrollment.os} machine`,
314
+ enrollmentId: enrollment.id,
315
+ createdAt: enrollment.createdAt,
316
+ updatedAt: enrollment.updatedAt,
317
+ },
318
+ ]
319
+ : [],
320
+ );
321
+ const sandboxes = [
322
+ ...legacySandboxes.filter(
323
+ (sandbox) => !scopedSandboxes.some((scoped) => scoped.id === sandbox.id),
324
+ ),
325
+ ...scopedSandboxes,
326
+ ];
225
327
  const enrollmentById = new Map(enrollments.map((e) => [e.id, e]));
226
328
 
227
329
  const machineViews = await Promise.all(
@@ -233,11 +335,26 @@ export async function listMachines(
233
335
  if (!enrollment) {
234
336
  return null;
235
337
  }
236
- const [probe, lease] = await Promise.all([
237
- probeEnrollment(services, workspaceId, enrollment),
238
- readLease(db, workspaceId, sandbox.id),
338
+ const [liveConnection, lease] = await Promise.all([
339
+ enrollment.connectionInstanceId &&
340
+ enrollment.connectionLeaseExpiresAt &&
341
+ Date.parse(enrollment.connectionLeaseExpiresAt) > Date.now()
342
+ ? Promise.resolve(enrollment)
343
+ : getLiveEnrollmentConnection(
344
+ db,
345
+ input.accountId && input.subjectId
346
+ ? {
347
+ accountId: input.accountId,
348
+ workspaceId,
349
+ subjectId: input.subjectId,
350
+ }
351
+ : enrollment.workspaceId,
352
+ enrollment.id,
353
+ ),
354
+ readLease(db, enrollment.workspaceId, sandbox.id),
239
355
  ]);
240
- const state = machineStateFor(probe.state, probe.hasDisplay);
356
+ const liveness = enrollmentLiveness(enrollment);
357
+ const state = machineStateFor(liveness.state, liveness.hasDisplay);
241
358
 
242
359
  // sharedSessionCount = the lease refcount for this machine's group. The
243
360
  // selfhosted sandbox id IS the lease group key (maxSandboxes:1, N sessions
@@ -248,6 +365,8 @@ export async function listMachines(
248
365
  return MachineView.parse({
249
366
  sandboxId: sandbox.id,
250
367
  enrollmentId: enrollment.id,
368
+ scope: enrollment.scope,
369
+ generation: enrollment.generation,
251
370
  name: sandbox.name,
252
371
  kind: "selfhosted",
253
372
  state,
@@ -263,6 +382,9 @@ export async function listMachines(
263
382
  allowScreenControl: enrollment.allowScreenControl,
264
383
  sharedSessionCount,
265
384
  lastSeenAt: enrollment.lastSeenAt,
385
+ connectionAuthority: connectionAuthorityFor(enrollment, liveConnection),
386
+ runtime: runtimeFor(services.settings, enrollment),
387
+ operationPolicy: enrollment.operationPolicy,
266
388
  metrics: metricsRow ? metricRowToSample(metricsRow) : null,
267
389
  });
268
390
  }),