@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
package/dist/index.js CHANGED
@@ -1,15 +1,18 @@
1
1
  import {
2
+ InteractionFrameProxyTransport,
2
3
  SlackBotProviderError,
4
+ cleanupScheduledTaskConnectorAuthorization,
3
5
  createAppComposition,
4
6
  createOpenGeniSlackBotInteractionClient,
5
7
  startSlackInteractionPump,
6
8
  startTemporalScheduleCleanupPump
7
- } from "./chunk-WCXHI3FF.js";
9
+ } from "./chunk-3TP54PPX.js";
8
10
 
9
11
  // src/index.ts
10
12
  import {
11
13
  dbSearchPath,
12
14
  getSettings,
15
+ resolveFirstPartyDelegationSecret,
13
16
  resolveNatsCalloutConfig,
14
17
  resolveNatsControlPlaneAuth,
15
18
  retryStartupDependency,
@@ -68,14 +71,20 @@ import {
68
71
  resolveEnrollmentSigningSecret
69
72
  } from "@opengeni/config";
70
73
  import { verifyEnrollmentBearer } from "@opengeni/contracts";
71
- import { getEnrollment } from "@opengeni/db";
74
+ import { claimEnrollmentConnection, getEnrollment } from "@opengeni/db";
72
75
  import {
73
76
  createResponderConnection,
74
77
  decodeAuthRequest,
75
78
  mintAuthResponse,
76
79
  mintUserJwt,
80
+ parseAgentConnectionName,
77
81
  workspaceAgentPermissions
78
82
  } from "@opengeni/events";
83
+
84
+ // src/sandbox/connection-authority.ts
85
+ var AGENT_CONNECTION_LEASE_MS = 2e4;
86
+
87
+ // src/sandbox/auth-callout.ts
79
88
  var AUTH_CALLOUT_SUBJECT = "$SYS.REQ.USER.AUTH";
80
89
  var NATS_USER_JWT_TTL_SECONDS = 5 * 60;
81
90
  async function handleAuthorizationRequest(deps, requestBytes) {
@@ -121,7 +130,29 @@ async function handleAuthorizationRequest(deps, requestBytes) {
121
130
  if (enrollment.credentialGeneration !== claims.credentialGeneration) {
122
131
  return deny("enrollment credential generation mismatch");
123
132
  }
124
- const permissions = workspaceAgentPermissions(claims.workspaceId);
133
+ const connectionInstanceId = parseAgentConnectionName(decoded.name);
134
+ if (!connectionInstanceId) {
135
+ return deny("missing or invalid agent connection instance");
136
+ }
137
+ const claim = await claimEnrollmentConnection(deps.db, {
138
+ workspaceId: claims.workspaceId,
139
+ enrollmentId: claims.enrollmentId,
140
+ credentialGeneration: claims.credentialGeneration,
141
+ connectionInstanceId,
142
+ leaseMs: AGENT_CONNECTION_LEASE_MS
143
+ });
144
+ if (!claim.claimed) {
145
+ deps.observability?.warn?.("auth-callout: denied a duplicate live runner", {
146
+ workspaceId: claims.workspaceId,
147
+ agentId: claims.agentId
148
+ });
149
+ return deny("another runner instance currently owns this enrollment");
150
+ }
151
+ const permissions = workspaceAgentPermissions(
152
+ claims.workspaceId,
153
+ claims.agentId,
154
+ connectionInstanceId
155
+ );
125
156
  const nowSeconds = Math.floor(Date.now() / 1e3);
126
157
  const userJwt = mintUserJwt({
127
158
  userPublicKey: decoded.userNkey,
@@ -145,7 +176,8 @@ async function handleAuthorizationRequest(deps, requestBytes) {
145
176
  });
146
177
  deps.observability?.info?.("auth-callout: granted a workspace-scoped NATS credential", {
147
178
  workspaceId: claims.workspaceId,
148
- agentId: claims.agentId
179
+ agentId: claims.agentId,
180
+ connectionGeneration: claim.connectionGeneration
149
181
  });
150
182
  return Buffer.from(response, "utf8");
151
183
  }
@@ -170,35 +202,44 @@ async function startAuthCalloutResponder(deps, natsUrl) {
170
202
  import {
171
203
  clearEnrollmentWentOffline,
172
204
  disconnectAttachedBrowserDevices,
205
+ advanceEnrollmentAgentUpdate,
173
206
  getEnrollment as getEnrollment2,
207
+ getLiveEnrollmentConnection,
174
208
  ingestMachineMetricsSample,
175
209
  reconcileAttachedBrowserInventory,
210
+ releaseEnrollmentConnection,
211
+ renewEnrollmentConnection,
176
212
  sessionsWithActiveOpOnEnrollment,
177
213
  setEnrollmentDisplayState,
178
- setEnrollmentOpStreamState,
179
- setEnrollmentWentOffline,
180
- touchEnrollmentLastSeen
214
+ setEnrollmentAgentRuntime,
215
+ setEnrollmentOpStreamState
181
216
  } from "@opengeni/db";
182
217
  import {
183
218
  AttachedBrowserInventorySnapshot as AttachedBrowserInventoryContract
184
219
  } from "@opengeni/contracts";
185
220
  import { appendAndPublishEvents } from "@opengeni/events";
221
+ import { normalizeConnectedMachineWorkspaceRoot } from "@opengeni/runtime/sandbox";
186
222
  import {
187
223
  AgentEvent,
224
+ AgentUpdateStage,
188
225
  Arch,
189
226
  GoingOfflineReason,
190
227
  Hello,
191
228
  Os,
192
229
  goingOfflineReasonToJSON
193
230
  } from "@opengeni/agent-proto";
194
- var AGENT_EVENTS_SUBJECT = "agent.*.*.events";
195
- var AGENT_HELLO_SUBJECT = "agent.*.*.hello";
231
+ var AGENT_EVENTS_SUBJECT = "agent.*.*.connection.*.events";
232
+ var AGENT_HELLO_SUBJECT = "agent.*.*.connection.*.hello";
196
233
  function parseAgentSubject(subject, tail) {
197
234
  const parts = subject.split(".");
198
- if (parts.length !== 4 || parts[0] !== "agent" || parts[3] !== tail) {
235
+ if (parts.length !== 6 || parts[0] !== "agent" || parts[3] !== "connection" || !parts[4] || parts[5] !== tail) {
199
236
  return null;
200
237
  }
201
- return { workspaceId: parts[1], agentId: parts[2] };
238
+ return {
239
+ workspaceId: parts[1],
240
+ agentId: parts[2],
241
+ connectionInstanceId: parts[4]
242
+ };
202
243
  }
203
244
  function parseAgentEventSubject(subject) {
204
245
  return parseAgentSubject(subject, "events");
@@ -294,19 +335,10 @@ async function ingestAttachedBrowserInventory(db, input) {
294
335
  snapshot: wireAttachedBrowserInventoryToContract(input.inventory)
295
336
  });
296
337
  }
297
- async function ingestHeartbeat(db, input) {
298
- const enrollment = await getEnrollment2(db, input.workspaceId, input.agentId);
299
- if (!enrollment) {
300
- return { ingested: false, seriesAppended: false };
301
- }
338
+ async function ingestHeartbeatMetrics(db, input) {
302
339
  const sample = wireSampleToDbSample(input.sample);
303
- await touchEnrollmentLastSeen(db, {
304
- accountId: enrollment.accountId,
305
- workspaceId: input.workspaceId,
306
- enrollmentId: input.agentId
307
- });
308
340
  const result = await ingestMachineMetricsSample(db, {
309
- accountId: enrollment.accountId,
341
+ accountId: input.accountId,
310
342
  workspaceId: input.workspaceId,
311
343
  enrollmentId: input.agentId,
312
344
  sample
@@ -356,17 +388,19 @@ async function handleAgentEventPayload(db, observability, payload, subject, bus)
356
388
  labels: { reason }
357
389
  });
358
390
  try {
359
- const enrollment = await getEnrollment2(db, ids.workspaceId, ids.agentId);
360
- if (enrollment) {
361
- await setEnrollmentWentOffline(db, {
362
- accountId: enrollment.accountId,
391
+ const enrollment2 = await getEnrollment2(db, ids.workspaceId, ids.agentId);
392
+ if (enrollment2) {
393
+ const released = await releaseEnrollmentConnection(db, {
394
+ accountId: enrollment2.accountId,
363
395
  workspaceId: ids.workspaceId,
364
396
  enrollmentId: ids.agentId,
397
+ connectionInstanceId: ids.connectionInstanceId,
365
398
  reason
366
399
  });
400
+ if (!released.released) return;
367
401
  try {
368
402
  await disconnectAttachedBrowserDevices(db, {
369
- accountId: enrollment.accountId,
403
+ accountId: enrollment2.accountId,
370
404
  workspaceId: ids.workspaceId,
371
405
  enrollmentId: ids.agentId
372
406
  });
@@ -408,10 +442,75 @@ async function handleAgentEventPayload(db, observability, payload, subject, bus)
408
442
  }
409
443
  return;
410
444
  }
445
+ if (event.event?.$case === "agentUpdateProgress") {
446
+ const enrollment2 = await getEnrollment2(db, ids.workspaceId, ids.agentId).catch(() => null);
447
+ if (!enrollment2 || enrollment2.connectionInstanceId !== ids.connectionInstanceId) return;
448
+ const progress = event.event.agentUpdateProgress;
449
+ const status = (() => {
450
+ switch (progress.stage) {
451
+ case AgentUpdateStage.AGENT_UPDATE_STAGE_ACCEPTED:
452
+ return "accepted";
453
+ case AgentUpdateStage.AGENT_UPDATE_STAGE_WAITING_FOR_IDLE:
454
+ return "waiting_for_idle";
455
+ case AgentUpdateStage.AGENT_UPDATE_STAGE_DOWNLOADING:
456
+ return "downloading";
457
+ case AgentUpdateStage.AGENT_UPDATE_STAGE_VERIFYING:
458
+ return "verifying";
459
+ case AgentUpdateStage.AGENT_UPDATE_STAGE_APPLYING:
460
+ return "applying";
461
+ case AgentUpdateStage.AGENT_UPDATE_STAGE_RESTARTING:
462
+ return "restarting";
463
+ case AgentUpdateStage.AGENT_UPDATE_STAGE_FAILED:
464
+ return "failed";
465
+ default:
466
+ return null;
467
+ }
468
+ })();
469
+ if (!status) return;
470
+ try {
471
+ await advanceEnrollmentAgentUpdate(db, {
472
+ accountId: enrollment2.accountId,
473
+ workspaceId: ids.workspaceId,
474
+ enrollmentId: ids.agentId,
475
+ connectionInstanceId: ids.connectionInstanceId,
476
+ connectionGeneration: enrollment2.connectionGeneration,
477
+ operationId: progress.operationId,
478
+ status,
479
+ expectedBinarySha256: /^[0-9a-f]{64}$/.test(progress.expectedBinarySha256) ? progress.expectedBinarySha256 : null,
480
+ errorCode: progress.errorCode || null,
481
+ retryable: progress.retryable,
482
+ rolledBack: progress.rolledBack
483
+ });
484
+ } catch (error) {
485
+ observability?.warn?.("Failed to ingest a machine self-update progress event", {
486
+ subject,
487
+ error: error instanceof Error ? error.message : String(error)
488
+ });
489
+ }
490
+ return;
491
+ }
411
492
  if (event.event?.$case !== "heartbeat") {
412
493
  return;
413
494
  }
414
495
  const heartbeat = event.event.heartbeat;
496
+ const enrollment = await getEnrollment2(db, ids.workspaceId, ids.agentId).catch(() => null);
497
+ if (!enrollment || enrollment.connectionInstanceId !== ids.connectionInstanceId) return;
498
+ try {
499
+ const renewed = await renewEnrollmentConnection(db, {
500
+ accountId: enrollment.accountId,
501
+ workspaceId: ids.workspaceId,
502
+ enrollmentId: ids.agentId,
503
+ connectionInstanceId: ids.connectionInstanceId,
504
+ leaseMs: AGENT_CONNECTION_LEASE_MS
505
+ });
506
+ if (!renewed.renewed) return;
507
+ } catch (error) {
508
+ observability?.warn?.("Failed to renew a machine connection heartbeat", {
509
+ subject,
510
+ error: error instanceof Error ? error.message : String(error)
511
+ });
512
+ return;
513
+ }
415
514
  if (heartbeat.attachedBrowserInventory) {
416
515
  try {
417
516
  await ingestAttachedBrowserInventory(db, {
@@ -429,7 +528,8 @@ async function handleAgentEventPayload(db, observability, payload, subject, bus)
429
528
  const metrics = heartbeat.metrics;
430
529
  if (!metrics) return;
431
530
  try {
432
- await ingestHeartbeat(db, {
531
+ await ingestHeartbeatMetrics(db, {
532
+ accountId: enrollment.accountId,
433
533
  workspaceId: ids.workspaceId,
434
534
  agentId: ids.agentId,
435
535
  sample: metrics
@@ -464,6 +564,33 @@ function helloDesktopUnavailableReason(hello) {
464
564
  function helloReportsOpStream(hello) {
465
565
  return hello.capabilities?.opStream === true;
466
566
  }
567
+ function helloRuntimeCapabilities(hello) {
568
+ const caps = hello.capabilities;
569
+ if (!caps) return {};
570
+ return {
571
+ exec: caps.exec === true,
572
+ filesystem: caps.filesystem === true,
573
+ git: caps.git === true,
574
+ pty: caps.pty === true,
575
+ desktop: caps.desktop === true,
576
+ opStream: caps.opStream === true,
577
+ browserBridge: caps.browserBridge === true,
578
+ operationResourcePolicy: caps.operationResourcePolicy === true,
579
+ operationCpuQuota: caps.operationCpuQuota === true
580
+ };
581
+ }
582
+ function helloCompletedUpdate(hello) {
583
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(
584
+ hello.completedUpdateOperationId
585
+ ) || !/^(?:v)?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(hello.completedUpdateTargetVersion) || !/^[0-9a-f]{64}$/.test(hello.completedUpdateBinarySha256)) {
586
+ return null;
587
+ }
588
+ return {
589
+ operationId: hello.completedUpdateOperationId,
590
+ targetVersion: hello.completedUpdateTargetVersion,
591
+ binarySha256: hello.completedUpdateBinarySha256
592
+ };
593
+ }
467
594
  async function refreshEnrollmentDisplay(db, input) {
468
595
  const desktopUnavailableReason = input.desktopUnavailableReason ?? null;
469
596
  const enrollment = await getEnrollment2(db, input.workspaceId, input.agentId);
@@ -478,7 +605,8 @@ async function refreshEnrollmentDisplay(db, input) {
478
605
  workspaceId: input.workspaceId,
479
606
  enrollmentId: input.agentId,
480
607
  hasDisplay: input.hasDisplay,
481
- desktopUnavailableReason
608
+ desktopUnavailableReason,
609
+ ...input.connectionInstanceId ? { connectionInstanceId: input.connectionInstanceId } : {}
482
610
  });
483
611
  }
484
612
  async function refreshEnrollmentOpStream(db, input) {
@@ -493,7 +621,8 @@ async function refreshEnrollmentOpStream(db, input) {
493
621
  accountId: enrollment.accountId,
494
622
  workspaceId: input.workspaceId,
495
623
  enrollmentId: input.agentId,
496
- opStream: input.opStream
624
+ opStream: input.opStream,
625
+ ...input.connectionInstanceId ? { connectionInstanceId: input.connectionInstanceId } : {}
497
626
  });
498
627
  }
499
628
  async function handleHelloPayload(db, observability, payload, subject, bus) {
@@ -511,17 +640,53 @@ async function handleHelloPayload(db, observability, payload, subject, bus) {
511
640
  });
512
641
  return;
513
642
  }
643
+ const authority = await getLiveEnrollmentConnection(db, ids.workspaceId, ids.agentId).catch(
644
+ () => null
645
+ );
646
+ if (!authority || authority.connectionInstanceId !== ids.connectionInstanceId) {
647
+ observability?.warn?.("Ignored a Hello from a superseded runner instance", {
648
+ workspaceId: ids.workspaceId,
649
+ agentId: ids.agentId
650
+ });
651
+ return;
652
+ }
653
+ let workspaceRoot;
654
+ if (hello.workspaceRoot.length > 0) {
655
+ try {
656
+ workspaceRoot = normalizeConnectedMachineWorkspaceRoot(hello.workspaceRoot, authority.os);
657
+ } catch (error) {
658
+ observability?.warn?.("Ignored an invalid Connected Machine workspace root", {
659
+ workspaceId: ids.workspaceId,
660
+ agentId: ids.agentId,
661
+ error: error instanceof Error ? error.message : String(error)
662
+ });
663
+ }
664
+ }
514
665
  try {
515
666
  await refreshEnrollmentDisplay(db, {
516
667
  workspaceId: ids.workspaceId,
517
668
  agentId: ids.agentId,
518
669
  hasDisplay: helloReportsDisplay(hello),
519
- desktopUnavailableReason: helloDesktopUnavailableReason(hello)
670
+ desktopUnavailableReason: helloDesktopUnavailableReason(hello),
671
+ connectionInstanceId: ids.connectionInstanceId
520
672
  });
521
673
  await refreshEnrollmentOpStream(db, {
522
674
  workspaceId: ids.workspaceId,
523
675
  agentId: ids.agentId,
524
- opStream: helloReportsOpStream(hello)
676
+ opStream: helloReportsOpStream(hello),
677
+ connectionInstanceId: ids.connectionInstanceId
678
+ });
679
+ await setEnrollmentAgentRuntime(db, {
680
+ accountId: authority.accountId,
681
+ workspaceId: ids.workspaceId,
682
+ enrollmentId: ids.agentId,
683
+ connectionInstanceId: ids.connectionInstanceId,
684
+ agentVersion: hello.agentVersion.trim() || null,
685
+ binarySha256: /^[0-9a-f]{64}$/.test(hello.binarySha256) ? hello.binarySha256 : null,
686
+ updateChannel: hello.updateChannel === "stable" || hello.updateChannel === "beta" ? hello.updateChannel : null,
687
+ capabilities: helloRuntimeCapabilities(hello),
688
+ ...workspaceRoot !== void 0 ? { workspaceRoot } : {},
689
+ completedUpdate: helloCompletedUpdate(hello)
525
690
  });
526
691
  } catch (error) {
527
692
  observability?.warn?.("Failed to refresh an enrollment's capabilities from a Hello", {
@@ -530,23 +695,21 @@ async function handleHelloPayload(db, observability, payload, subject, bus) {
530
695
  });
531
696
  }
532
697
  try {
533
- const enrollment = await getEnrollment2(db, ids.workspaceId, ids.agentId);
534
- if (enrollment) {
535
- const { cleared } = await clearEnrollmentWentOffline(db, {
536
- accountId: enrollment.accountId,
537
- workspaceId: ids.workspaceId,
538
- enrollmentId: ids.agentId
539
- });
540
- if (cleared && bus) {
541
- await fanOutMachineLinkEvents(
542
- db,
543
- bus,
544
- observability,
545
- ids.workspaceId,
546
- ids.agentId,
547
- (activeTurnId) => [{ type: "machine.link.restored", turnId: activeTurnId, payload: {} }]
548
- );
549
- }
698
+ const { cleared } = await clearEnrollmentWentOffline(db, {
699
+ accountId: authority.accountId,
700
+ workspaceId: ids.workspaceId,
701
+ enrollmentId: ids.agentId,
702
+ connectionInstanceId: ids.connectionInstanceId
703
+ });
704
+ if (cleared && bus) {
705
+ await fanOutMachineLinkEvents(
706
+ db,
707
+ bus,
708
+ observability,
709
+ ids.workspaceId,
710
+ ids.agentId,
711
+ (activeTurnId) => [{ type: "machine.link.restored", turnId: activeTurnId, payload: {} }]
712
+ );
550
713
  }
551
714
  } catch (error) {
552
715
  observability?.warn?.("Failed to clear a machine going-offline marker on a Hello", {
@@ -741,7 +904,7 @@ import {
741
904
  encodeEditableArtifactLiveServerWireFrame
742
905
  } from "@opengeni/core";
743
906
  var EDITABLE_ARTIFACT_LIVE_WEBSOCKET_PATH = "/v1/editable-artifacts/live";
744
- var EDITABLE_ARTIFACT_LIVE_WEBSOCKET_PROTOCOL = "opengeni-artifact-v1";
907
+ var EDITABLE_ARTIFACT_LIVE_WEBSOCKET_PROTOCOL = "opengeni-artifact-v2";
745
908
  var EDITABLE_ARTIFACT_LIVE_WEBSOCKET_MAX_MESSAGE_BYTES = 8 * 1024 * 1024 + 64 * 1024;
746
909
  var MAX_QUEUED_MESSAGES = 64;
747
910
  var MAX_QUEUED_BYTES = 12 * 1024 * 1024;
@@ -999,7 +1162,6 @@ import {
999
1162
  editableArtifactId
1000
1163
  } from "@opengeni/core/editable-artifacts";
1001
1164
  import {
1002
- EDITABLE_ARTIFACT_LIVE_PROTOCOL_VERSION,
1003
1165
  EditableArtifactApplication,
1004
1166
  EditableArtifactCompatibilityError,
1005
1167
  EditableArtifactLiveServer,
@@ -1007,6 +1169,7 @@ import {
1007
1169
  SystemEditableArtifactLiveScheduler,
1008
1170
  WebCryptoEditableArtifactLiveTokens
1009
1171
  } from "@opengeni/core/editable-artifact-live";
1172
+ import { currentEditableArtifactCompatibility } from "@opengeni/contracts/editable-artifacts";
1010
1173
  import {
1011
1174
  PostgresEditableArtifactLiveReadStore,
1012
1175
  PostgresEditableArtifactLiveTicketStore,
@@ -1093,6 +1256,13 @@ import {
1093
1256
  } from "@opengeni/contracts/editable-artifact-causal-frontier";
1094
1257
  import { decodeEditableArtifactSerializedCommit } from "@opengeni/contracts/editable-artifact-serialized-commit";
1095
1258
  import { decodeEditableArtifactMutationIntent } from "@opengeni/contracts/editable-artifacts";
1259
+ import {
1260
+ COMMITTED_TRANSACTION_PROTOCOL_VERSION,
1261
+ DOCUMENT_ARTIFACT_MODEL_SCHEMA_VERSION,
1262
+ PRESENTATION_ARTIFACT_MODEL_SCHEMA_VERSION,
1263
+ SPREADSHEET_ARTIFACT_MODEL_SCHEMA_VERSION,
1264
+ SPREADSHEET_COLLABORATION_SNAPSHOT_VERSION
1265
+ } from "@opengeni/contracts/editable-artifact-versions";
1096
1266
  import {
1097
1267
  MAX_EDITABLE_ARTIFACT_SNAPSHOT_BYTES,
1098
1268
  causalFrontiersEqual,
@@ -1101,9 +1271,6 @@ import {
1101
1271
  editableArtifactReplicaId,
1102
1272
  editableArtifactStateHash
1103
1273
  } from "@opengeni/core";
1104
- var MODEL_SCHEMA_VERSION = 1;
1105
- var OPERATION_PROTOCOL_VERSION = 1;
1106
- var CRDT_STATE_VERSION = 1;
1107
1274
  var SNAPSHOT_CHUNK_BYTES = 1024 * 1024;
1108
1275
  async function loadVerifiedNativeArtifactRuntimeBinding() {
1109
1276
  const location = await doctorConfiguredArtifactRuntime();
@@ -1136,7 +1303,7 @@ var NativeEditableArtifactKernelAdapter = class {
1136
1303
  modality: "spreadsheet",
1137
1304
  committedTransactionBytes,
1138
1305
  kernelVersion: this.runtime.buildIdentity,
1139
- modelSchemaVersion: MODEL_SCHEMA_VERSION
1306
+ modelSchemaVersion: SPREADSHEET_ARTIFACT_MODEL_SCHEMA_VERSION
1140
1307
  });
1141
1308
  }
1142
1309
  if (session.modality !== request.modality) {
@@ -1148,7 +1315,7 @@ var NativeEditableArtifactKernelAdapter = class {
1148
1315
  nativeReceiptBytes,
1149
1316
  resultingStateHash: editableArtifactStateHash(session.stateHash()),
1150
1317
  kernelVersion: this.runtime.buildIdentity,
1151
- modelSchemaVersion: MODEL_SCHEMA_VERSION
1318
+ modelSchemaVersion: modelSchemaVersionFor(request.modality)
1152
1319
  });
1153
1320
  } finally {
1154
1321
  session.dispose();
@@ -1202,7 +1369,7 @@ var NativeEditableArtifactKernelAdapter = class {
1202
1369
  ),
1203
1370
  stateHash: editableArtifactStateHash(session.stateHash()),
1204
1371
  coveredHeadSequence: 0,
1205
- modelSchemaVersion: MODEL_SCHEMA_VERSION,
1372
+ modelSchemaVersion: modelSchemaVersionFor(input.modality),
1206
1373
  kernelVersion: this.runtime.buildIdentity
1207
1374
  };
1208
1375
  if (session.modality === "spreadsheet") {
@@ -1210,8 +1377,8 @@ var NativeEditableArtifactKernelAdapter = class {
1210
1377
  ...common,
1211
1378
  modality: "spreadsheet",
1212
1379
  coveredCausalFrontier: domainFrontier(session.frontier()),
1213
- operationProtocolVersion: OPERATION_PROTOCOL_VERSION,
1214
- crdtStateVersion: CRDT_STATE_VERSION
1380
+ operationProtocolVersion: COMMITTED_TRANSACTION_PROTOCOL_VERSION,
1381
+ crdtStateVersion: SPREADSHEET_COLLABORATION_SNAPSHOT_VERSION
1215
1382
  });
1216
1383
  }
1217
1384
  return Object.freeze({
@@ -1249,7 +1416,7 @@ var NativeEditableArtifactKernelAdapter = class {
1249
1416
  ),
1250
1417
  stateHash: editableArtifactStateHash(session.stateHash()),
1251
1418
  coveredHeadSequence: input.state.artifact.headSequence,
1252
- modelSchemaVersion: MODEL_SCHEMA_VERSION,
1419
+ modelSchemaVersion: modelSchemaVersionFor(input.state.modality),
1253
1420
  kernelVersion: this.runtime.buildIdentity
1254
1421
  };
1255
1422
  if (session.modality === "spreadsheet") {
@@ -1258,8 +1425,8 @@ var NativeEditableArtifactKernelAdapter = class {
1258
1425
  ...common,
1259
1426
  modality: session.modality,
1260
1427
  coveredCausalFrontier: domainFrontier(session.frontier()),
1261
- operationProtocolVersion: OPERATION_PROTOCOL_VERSION,
1262
- crdtStateVersion: CRDT_STATE_VERSION
1428
+ operationProtocolVersion: COMMITTED_TRANSACTION_PROTOCOL_VERSION,
1429
+ crdtStateVersion: SPREADSHEET_COLLABORATION_SNAPSHOT_VERSION
1263
1430
  });
1264
1431
  }
1265
1432
  if (session.modality !== input.state.modality) throw new Error("Native modality mismatch");
@@ -1312,12 +1479,12 @@ var NativeEditableArtifactKernelAdapter = class {
1312
1479
  canonicalContentHash,
1313
1480
  coveredHeadSequence,
1314
1481
  stateHash,
1315
- modelSchemaVersion: MODEL_SCHEMA_VERSION,
1482
+ modelSchemaVersion: SPREADSHEET_ARTIFACT_MODEL_SCHEMA_VERSION,
1316
1483
  kernelVersion: this.runtime.buildIdentity,
1317
1484
  fullCrdtStateVerified: true,
1318
1485
  coveredCausalFrontier,
1319
- operationProtocolVersion: OPERATION_PROTOCOL_VERSION,
1320
- crdtStateVersion: CRDT_STATE_VERSION,
1486
+ operationProtocolVersion: COMMITTED_TRANSACTION_PROTOCOL_VERSION,
1487
+ crdtStateVersion: SPREADSHEET_COLLABORATION_SNAPSHOT_VERSION,
1321
1488
  ...replayedTarget2 ? { replayedTarget: replayedTarget2 } : {}
1322
1489
  });
1323
1490
  }
@@ -1339,7 +1506,7 @@ var NativeEditableArtifactKernelAdapter = class {
1339
1506
  canonicalContentHash,
1340
1507
  coveredHeadSequence,
1341
1508
  stateHash,
1342
- modelSchemaVersion: MODEL_SCHEMA_VERSION,
1509
+ modelSchemaVersion: modelSchemaVersionFor(session.modality),
1343
1510
  kernelVersion: this.runtime.buildIdentity,
1344
1511
  nativeRevision,
1345
1512
  ...replayedTarget ? { replayedTarget } : {}
@@ -1349,6 +1516,10 @@ var NativeEditableArtifactKernelAdapter = class {
1349
1516
  }
1350
1517
  }
1351
1518
  };
1519
+ function modelSchemaVersionFor(modality) {
1520
+ if (modality === "spreadsheet") return SPREADSHEET_ARTIFACT_MODEL_SCHEMA_VERSION;
1521
+ return modality === "document" ? DOCUMENT_ARTIFACT_MODEL_SCHEMA_VERSION : PRESENTATION_ARTIFACT_MODEL_SCHEMA_VERSION;
1522
+ }
1352
1523
  function assertRuntime(candidate, location) {
1353
1524
  if (typeof candidate !== "object" || candidate === null) {
1354
1525
  throw new Error("Verified artifact facade returned no runtime");
@@ -1581,7 +1752,8 @@ import {
1581
1752
  import { requireLiveAgentAttemptAuthorization } from "@opengeni/core";
1582
1753
  import { getFilesForSubject } from "@opengeni/db";
1583
1754
  import {
1584
- MAX_BOUNDED_OBJECT_CHUNK_BYTES
1755
+ MAX_BOUNDED_OBJECT_CHUNK_BYTES,
1756
+ retryWhileMissing
1585
1757
  } from "@opengeni/storage";
1586
1758
  var SNAPSHOT_MIME = "application/vnd.opengeni.editable-artifact-snapshot";
1587
1759
  var EditableArtifactOfficeImportAdapter = class {
@@ -1712,7 +1884,19 @@ function officeFilenameMatches(filename, modality) {
1712
1884
  }
1713
1885
  async function readVerifiedWorkspaceFile(storage, file, signal) {
1714
1886
  throwIfAborted2(signal);
1715
- const before = await storage.headFile(file);
1887
+ const before = await retryWhileMissing(async () => {
1888
+ try {
1889
+ return await storage.headFile(file);
1890
+ } catch (error) {
1891
+ if (typeof storage.fileExists === "function" && !await storage.fileExists(file)) {
1892
+ return null;
1893
+ }
1894
+ throw error;
1895
+ }
1896
+ });
1897
+ if (!before) {
1898
+ throw new EditableArtifactOfficeImportError("source_changed");
1899
+ }
1716
1900
  if (before.ContentLength !== file.sizeBytes || before.ContentType !== file.contentType || before.Metadata?.sha256 !== file.sha256 || typeof before.VersionToken !== "string" || before.VersionToken.length < 1) {
1717
1901
  throw new EditableArtifactOfficeImportError("source_changed");
1718
1902
  }
@@ -1721,14 +1905,26 @@ async function readVerifiedWorkspaceFile(storage, file, signal) {
1721
1905
  for (let start = 0; start < file.sizeBytes; start += MAX_BOUNDED_OBJECT_CHUNK_BYTES) {
1722
1906
  throwIfAborted2(signal);
1723
1907
  const end = Math.min(file.sizeBytes, start + MAX_BOUNDED_OBJECT_CHUNK_BYTES) - 1;
1724
- const chunk = await storage.getFileRange(file, { start, end });
1908
+ const chunk = await retryWhileMissing(async () => storage.getFileRange(file, { start, end }));
1725
1909
  if (!chunk || chunk.byteLength !== end - start + 1) {
1726
1910
  throw new EditableArtifactOfficeImportError("source_changed");
1727
1911
  }
1728
1912
  bytes.set(chunk, start);
1729
1913
  digest.update(chunk);
1730
1914
  }
1731
- const after = await storage.headFile(file);
1915
+ const after = await retryWhileMissing(async () => {
1916
+ try {
1917
+ return await storage.headFile(file);
1918
+ } catch (error) {
1919
+ if (typeof storage.fileExists === "function" && !await storage.fileExists(file)) {
1920
+ return null;
1921
+ }
1922
+ throw error;
1923
+ }
1924
+ });
1925
+ if (!after) {
1926
+ throw new EditableArtifactOfficeImportError("source_changed");
1927
+ }
1732
1928
  const sha256 = digest.digest("hex");
1733
1929
  if (after.ContentLength !== before.ContentLength || after.ContentType !== before.ContentType || after.Metadata?.sha256 !== before.Metadata?.sha256 || after.VersionToken !== before.VersionToken || sha256 !== file.sha256) {
1734
1930
  throw new EditableArtifactOfficeImportError("source_changed");
@@ -1768,6 +1964,7 @@ function throwIfAborted2(signal) {
1768
1964
  // src/editable-artifact-workspace-files.ts
1769
1965
  import { createHash as createHash3 } from "crypto";
1770
1966
  import { completeFileUpload, prepareGeneratedWorkspaceFile } from "@opengeni/db";
1967
+ import { retryWhileMissing as retryWhileMissing2 } from "@opengeni/storage";
1771
1968
  var UPLOAD_INTENT_TTL_MS = 60 * 6e4;
1772
1969
  var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u;
1773
1970
  var EditableArtifactWorkspaceFileAdapter = class {
@@ -1824,7 +2021,7 @@ var EditableArtifactWorkspaceFileAdapter = class {
1824
2021
  if (existing) {
1825
2022
  assertStoredExport(existing, download.byteSize, download.mimeType, sha256);
1826
2023
  } else {
1827
- await storage.putObjectStreamIfAbsent({
2024
+ const created = await storage.putObjectStreamIfAbsent({
1828
2025
  key: objectKey,
1829
2026
  contentType: download.mimeType,
1830
2027
  chunks: download.chunks(input.signal ? { signal: input.signal } : {}),
@@ -1832,18 +2029,28 @@ var EditableArtifactWorkspaceFileAdapter = class {
1832
2029
  sha256,
1833
2030
  ...input.signal ? { signal: input.signal } : {}
1834
2031
  });
1835
- const stored = await storage.headObject(objectKey);
1836
- assertStoredExport(stored, download.byteSize, download.mimeType, sha256);
2032
+ if (!created) {
2033
+ await assertVisibleExport(
2034
+ storage,
2035
+ objectKey,
2036
+ download.byteSize,
2037
+ download.mimeType,
2038
+ sha256,
2039
+ input.signal
2040
+ );
2041
+ }
1837
2042
  }
1838
2043
  await download.assertUnchanged(input.signal);
1839
2044
  throwIfAborted3(input.signal);
1840
2045
  file = await this.completeFile(this.dependencies.db, input.scope.workspaceId, uploadId);
1841
2046
  } else {
1842
- assertStoredExport(
1843
- await storage.headObject(objectKey),
2047
+ await assertVisibleExport(
2048
+ storage,
2049
+ objectKey,
1844
2050
  download.byteSize,
1845
2051
  download.mimeType,
1846
- sha256
2052
+ sha256,
2053
+ input.signal
1847
2054
  );
1848
2055
  await download.assertUnchanged(input.signal);
1849
2056
  }
@@ -1867,6 +2074,13 @@ var EditableArtifactWorkspaceFileAdapter = class {
1867
2074
  }
1868
2075
  }
1869
2076
  };
2077
+ async function assertVisibleExport(storage, objectKey, byteSize, contentType, sha256, signal) {
2078
+ const head = await retryWhileMissing2(
2079
+ async () => await storage.headObject(objectKey),
2080
+ signal ? { signal } : {}
2081
+ );
2082
+ assertStoredExport(head, byteSize, contentType, sha256);
2083
+ }
1870
2084
  function assertStoredExport(head, byteSize, contentType, sha256) {
1871
2085
  if (!head || head.ContentLength !== byteSize || head.ContentType !== contentType || head.Metadata?.sha256 !== sha256 || typeof head.VersionToken !== "string" || head.VersionToken.length < 1) {
1872
2086
  throw new Error("Workspace export object differs from its durable materialization");
@@ -1998,7 +2212,8 @@ async function createStandaloneEditableArtifactApplication(input) {
1998
2212
  });
1999
2213
  const compatibility = Object.freeze({
2000
2214
  assertCompatible(request) {
2001
- if (request.protocolVersion !== EDITABLE_ARTIFACT_LIVE_PROTOCOL_VERSION || request.modelSchemaVersion !== 1 || request.kernelVersion !== runtime.buildIdentity) {
2215
+ const current = currentEditableArtifactCompatibility(request.artifact.modality);
2216
+ if (request.modality !== request.artifact.modality || request.liveProtocolVersion !== current.liveProtocolVersion || request.modelSchemaVersion !== current.modelSchemaVersion || request.snapshotVersion !== current.snapshotVersion || request.commandProtocolVersion !== current.commandProtocolVersion || request.committedTransactionProtocolVersion !== current.committedTransactionProtocolVersion) {
2002
2217
  throw new EditableArtifactCompatibilityError();
2003
2218
  }
2004
2219
  }
@@ -2117,7 +2332,8 @@ var RandomEditableArtifactDurableExportIds = class {
2117
2332
  }
2118
2333
  };
2119
2334
  function materializationProfiles(capabilities) {
2120
- if (capabilities.protocol !== "OGAMC001" || capabilities.runtimeKind !== "native" || capabilities.maxOutputBytes !== EDITABLE_ARTIFACT_EXPORT_MAX_DOWNLOAD_BYTES || capabilities.codecVersions["opengeni.xlsx"] === void 0 || !capabilities.supportedModelSchemaVersions.includes(1) || !capabilities.supportedOperationProtocolVersions.includes(1) || !capabilities.supportedSnapshotProtocolVersions.includes(1)) {
2335
+ const spreadsheetCompatibility = currentEditableArtifactCompatibility("spreadsheet");
2336
+ if (capabilities.protocol !== "OGAMC001" || capabilities.runtimeKind !== "native" || capabilities.maxOutputBytes !== EDITABLE_ARTIFACT_EXPORT_MAX_DOWNLOAD_BYTES || capabilities.codecVersions["opengeni.xlsx"] === void 0 || capabilities.supportedModelSchemaVersions[0] !== spreadsheetCompatibility.modelSchemaVersion || capabilities.supportedOperationProtocolVersions[0] !== spreadsheetCompatibility.committedTransactionProtocolVersion || capabilities.supportedSnapshotProtocolVersions[0] !== spreadsheetCompatibility.snapshotVersion) {
2121
2337
  throw new Error("Verified artifact materializer profile is incompatible");
2122
2338
  }
2123
2339
  const codecVersion = capabilities.codecVersions["opengeni.xlsx"];
@@ -2207,6 +2423,19 @@ async function createTemporalWorkflowClient(settings, db) {
2207
2423
  namespace: settings.temporalNamespace
2208
2424
  });
2209
2425
  const client = {
2426
+ triggerAutomationRun: async ({ accountId, workspaceId, runId }) => {
2427
+ try {
2428
+ await temporal.workflow.start("automationRunWorkflow", {
2429
+ taskQueue: settings.temporalTaskQueue,
2430
+ workflowId: `automation-run:${runId}`,
2431
+ workflowIdReusePolicy: "REJECT_DUPLICATE",
2432
+ args: [{ accountId, workspaceId, runId }]
2433
+ });
2434
+ } catch (error) {
2435
+ if (isWorkflowAlreadyStarted(error)) return;
2436
+ throw error;
2437
+ }
2438
+ },
2210
2439
  signalUserMessage: async ({ eventId, workflowId }) => {
2211
2440
  await temporal.workflow.getHandle(workflowId).signal("userMessage", eventId);
2212
2441
  },
@@ -2396,6 +2625,7 @@ async function startApi() {
2396
2625
  const observability = createObservability(settings, { component: "api" });
2397
2626
  const searchPath = dbSearchPath(settings);
2398
2627
  const dbClient = createDb(settings.databaseUrl, {
2628
+ max: 32,
2399
2629
  ...searchPath ? { searchPath } : {},
2400
2630
  rlsStrategy: settings.rlsStrategy
2401
2631
  });
@@ -2406,10 +2636,17 @@ async function startApi() {
2406
2636
  const databasePosture = {
2407
2637
  rlsStrategy: settings.rlsStrategy,
2408
2638
  expectedRole: settings.runtimeDatabaseRole,
2409
- targetSchema: settings.dbSchema.trim() || "public"
2639
+ targetSchema: settings.dbSchema.trim() || "public",
2640
+ organizationTenancyCanonicalActivationEnabled: settings.organizationTenancyCanonicalActivationEnabled
2410
2641
  };
2411
2642
  const controlPlaneAuth = resolveNatsControlPlaneAuth(settings);
2412
2643
  try {
2644
+ const { ensureModalRegistryImage } = await import("@opengeni/runtime/sandbox");
2645
+ await retryStartupDependency(
2646
+ "Modal private-registry image",
2647
+ () => ensureModalRegistryImage(settings),
2648
+ { ...retryOptions, onRetry }
2649
+ );
2413
2650
  await retryStartupDependency(
2414
2651
  "PostgreSQL runtime posture",
2415
2652
  () => assertRuntimeDatabasePosture(dbClient.db, databasePosture),
@@ -2482,11 +2719,17 @@ async function startApi() {
2482
2719
  });
2483
2720
  }
2484
2721
  const artifactWebSockets = new EditableArtifactWebSocketTransport(routeDeps.editableArtifacts);
2722
+ const interactionFrameProxies = new InteractionFrameProxyTransport(
2723
+ resolveFirstPartyDelegationSecret(settings)
2724
+ );
2485
2725
  const server = Bun.serve({
2486
2726
  hostname: settings.apiHost,
2487
2727
  port: settings.apiPort,
2488
2728
  idleTimeout: 255,
2489
2729
  fetch: (request, bunServer) => {
2730
+ if (interactionFrameProxies.handles(request)) {
2731
+ return interactionFrameProxies.upgrade(request, bunServer);
2732
+ }
2490
2733
  if (artifactWebSockets.handles(request)) {
2491
2734
  return artifactWebSockets.upgrade(request, bunServer);
2492
2735
  }
@@ -2496,15 +2739,16 @@ async function startApi() {
2496
2739
  maxPayloadLength: EDITABLE_ARTIFACT_LIVE_WEBSOCKET_MAX_MESSAGE_BYTES,
2497
2740
  backpressureLimit: 16 * 1024 * 1024,
2498
2741
  closeOnBackpressureLimit: true,
2499
- open: (socket) => artifactWebSockets.websocket.open(socket),
2500
- message: (socket, message) => artifactWebSockets.websocket.message(socket, message),
2501
- close: (socket) => artifactWebSockets.websocket.close(socket)
2742
+ open: (socket) => socket.data.attach(socket),
2743
+ message: (socket, message) => socket.data.receive(message),
2744
+ close: (socket) => socket.data.transportClosed()
2502
2745
  }
2503
2746
  });
2504
2747
  const stopSlackInteractionPump = settings.slackSigningSecret ? startSlackInteractionPump(routeDeps) : void 0;
2505
2748
  const stopMemorySlackPublicationPump = startMemorySlackPublicationPump(routeDeps);
2506
2749
  const stopTemporalScheduleCleanupPump = startTemporalScheduleCleanupPump({
2507
2750
  db: dbClient.db,
2751
+ cleanupConnectorAuthorization: async (claim) => await cleanupScheduledTaskConnectorAuthorization(routeDeps, claim),
2508
2752
  deleteSchedule: async (temporalScheduleId) => {
2509
2753
  await workflowClient.client.deleteScheduledTaskSchedule({ temporalScheduleId });
2510
2754
  },