@opengeni/api-router 0.30.3 → 1.0.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 (434) hide show
  1. package/dist/access-grant-rls.d.ts +3 -0
  2. package/dist/admission-trace.d.ts +8 -0
  3. package/dist/api-websocket.d.ts +18 -0
  4. package/dist/app.d.ts +50 -2
  5. package/dist/app.js +12 -1
  6. package/dist/artifact-catalog-cursor.d.ts +27 -0
  7. package/dist/auth/managed-auth-attempt-context.d.ts +10 -0
  8. package/dist/auth/managed-auth-rate-limits.d.ts +167 -0
  9. package/dist/auth/managed-auth-session-adapter.d.ts +6 -0
  10. package/dist/auth/managed-auth.d.ts +102 -3
  11. package/dist/auth/managed-email.d.ts +29 -0
  12. package/dist/auth/managed-sign-in-notifications.d.ts +4 -0
  13. package/dist/auth/new-signups-gate.d.ts +23 -0
  14. package/dist/auth/organization-user-setup.d.ts +80 -0
  15. package/dist/auth/signup-funnel-metrics.d.ts +44 -0
  16. package/dist/browser-controller-authority.d.ts +1 -61
  17. package/dist/browser-ephemeral.d.ts +5 -0
  18. package/dist/browser-state-authority.d.ts +1 -27
  19. package/dist/chunk-6TZ5LG3O.js +123948 -0
  20. package/dist/chunk-6TZ5LG3O.js.map +1 -0
  21. package/dist/chunk-OGQDXVXY.js +18 -0
  22. package/dist/chunk-OGQDXVXY.js.map +1 -0
  23. package/dist/claude-subscription-account-conflicts.d.ts +1 -0
  24. package/dist/claude-subscription-account-usage.d.ts +39 -0
  25. package/dist/claude-subscription-oauth.d.ts +25 -0
  26. package/dist/claude-subscription-profile.d.ts +9 -0
  27. package/dist/claude-subscription-usage.d.ts +44 -0
  28. package/dist/claude-workspace-connection.d.ts +15 -0
  29. package/dist/codemode.d.ts +32 -1
  30. package/dist/company-brain-okf.d.ts +24 -0
  31. package/dist/connected-machine-computer-access.d.ts +11 -0
  32. package/dist/connection-ownership.d.ts +54 -0
  33. package/dist/controller-data-plane.d.ts +32 -0
  34. package/dist/editable-artifact-source-session.d.ts +3 -0
  35. package/dist/editable-artifact-websocket.d.ts +10 -18
  36. package/dist/editable-artifact-workspace-files.d.ts +1 -1
  37. package/dist/fatal-process-boundary.d.ts +25 -0
  38. package/dist/github-access.d.ts +31 -2
  39. package/dist/http/acting-person.d.ts +24 -0
  40. package/dist/http/api-contract-compat.d.ts +19 -0
  41. package/dist/http/api-error.d.ts +27 -1
  42. package/dist/http/cors.d.ts +17 -0
  43. package/dist/http/deprecation.d.ts +42 -0
  44. package/dist/http/interaction-control-error.d.ts +13 -0
  45. package/dist/http/keyed-admission.d.ts +12 -0
  46. package/dist/http/local-browser-boundary.d.ts +72 -0
  47. package/dist/http/metrics-listener.d.ts +22 -0
  48. package/dist/http/path-identifier.d.ts +17 -0
  49. package/dist/http/registered-route-label.d.ts +16 -0
  50. package/dist/http/request-body.d.ts +27 -0
  51. package/dist/http/request-source.d.ts +52 -0
  52. package/dist/http/sse.d.ts +17 -6
  53. package/dist/http/unmatched-route.d.ts +18 -0
  54. package/dist/http/user-content.d.ts +36 -0
  55. package/dist/http/websocket-upgrade-dispatch.d.ts +18 -0
  56. package/dist/index.d.ts +10 -3
  57. package/dist/index.js +1091 -162
  58. package/dist/index.js.map +1 -1
  59. package/dist/integration-endpoint-test.d.ts +20 -0
  60. package/dist/integrations/api-integrations.d.ts +2 -2
  61. package/dist/integrations/atlassian.d.ts +29 -3
  62. package/dist/integrations/connect-authority.d.ts +1 -0
  63. package/dist/integrations/connect-callback-return.d.ts +6 -0
  64. package/dist/integrations/fiken.d.ts +16 -2
  65. package/dist/integrations/github-app-connect.d.ts +47 -0
  66. package/dist/integrations/github-installation-proof.d.ts +3 -0
  67. package/dist/integrations/github-lens-connect.d.ts +116 -0
  68. package/dist/integrations/github-skill-source.d.ts +1 -5
  69. package/dist/integrations/google-drive.d.ts +24 -0
  70. package/dist/integrations/integration-commit-authority.d.ts +13 -0
  71. package/dist/integrations/marketplace-plugin.d.ts +2691 -0
  72. package/dist/integrations/oauth-client.d.ts +178 -18
  73. package/dist/integrations/oauth-profiles.d.ts +222 -0
  74. package/dist/integrations/oauth-return-path.d.ts +1 -0
  75. package/dist/integrations/personal-github-repositories.d.ts +84 -0
  76. package/dist/integrations/personal-github.d.ts +42 -0
  77. package/dist/integrations/pr-review-provider.d.ts +22 -0
  78. package/dist/integrations/provider-oauth.d.ts +19 -1
  79. package/dist/integrations/slack-app-home.d.ts +24 -0
  80. package/dist/integrations/slack-bot.d.ts +367 -12
  81. package/dist/integrations/slack-file-upload-flow.d.ts +62 -0
  82. package/dist/integrations/slack-install.d.ts +20 -0
  83. package/dist/integrations/slack-interactions.d.ts +107 -9
  84. package/dist/integrations/slack-mrkdwn.d.ts +41 -0
  85. package/dist/integrations/slack-routing.d.ts +120 -0
  86. package/dist/integrations/slack-session-defaults.d.ts +58 -0
  87. package/dist/integrations/slack-task-file-upload.d.ts +36 -0
  88. package/dist/integrations/social-oauth.d.ts +7 -0
  89. package/dist/interaction-agent-access.d.ts +15 -0
  90. package/dist/interaction-frame-proxy.d.ts +68 -0
  91. package/dist/interaction-metrics.d.ts +2 -0
  92. package/dist/mcp/action-catalog-types.d.ts +9 -0
  93. package/dist/mcp/action-catalog.gen.d.ts +2 -0
  94. package/dist/mcp/capability-account-readiness.d.ts +7 -0
  95. package/dist/mcp/child-read-evidence.d.ts +9 -0
  96. package/dist/mcp/command-read.d.ts +24 -0
  97. package/dist/mcp/company-profile-agent-admin.d.ts +44 -0
  98. package/dist/mcp/contract-input.d.ts +11 -0
  99. package/dist/mcp/documents.d.ts +7 -5
  100. package/dist/mcp/files.d.ts +2 -2
  101. package/dist/mcp/first-party-tool-permissions.d.ts +650 -0
  102. package/dist/mcp/knowledge-entries.d.ts +5 -0
  103. package/dist/mcp/orchestration-failure-diagnostic.d.ts +43 -0
  104. package/dist/mcp/receipts.d.ts +9 -0
  105. package/dist/mcp/request-abort.d.ts +19 -0
  106. package/dist/mcp/scheduled-task-input.d.ts +1189 -0
  107. package/dist/mcp/scheduled-task-view.d.ts +22 -4
  108. package/dist/mcp/server.d.ts +255 -3
  109. package/dist/mcp/session-event-view.d.ts +94 -0
  110. package/dist/mcp/session-view.d.ts +66 -2
  111. package/dist/mcp/session-wait.d.ts +174 -0
  112. package/dist/mcp-oauth.d.ts +52 -0
  113. package/dist/model-catalog.d.ts +11 -31
  114. package/dist/observability.d.ts +3 -1
  115. package/dist/organization-mcp.d.ts +52 -0
  116. package/dist/organization-recovery-notifications.d.ts +41 -0
  117. package/dist/routes/analytics-consent.d.ts +26 -0
  118. package/dist/routes/api-integrations.d.ts +25 -0
  119. package/dist/routes/api-keys.d.ts +17 -0
  120. package/dist/routes/artifact-catalog.d.ts +6 -0
  121. package/dist/routes/automations.d.ts +19 -0
  122. package/dist/routes/billing.d.ts +49 -0
  123. package/dist/routes/browser-sessions.d.ts +59 -2
  124. package/dist/routes/claude-subscription-accounts.d.ts +3 -0
  125. package/dist/routes/claude-subscription-oauth.d.ts +3 -0
  126. package/dist/routes/client-errors.d.ts +146 -0
  127. package/dist/routes/codex.d.ts +76 -2
  128. package/dist/routes/company-brain.d.ts +3 -0
  129. package/dist/routes/company-profile.d.ts +2 -1
  130. package/dist/routes/computer-sessions.d.ts +14 -0
  131. package/dist/routes/connect.d.ts +5 -0
  132. package/dist/routes/editable-artifacts.d.ts +2 -0
  133. package/dist/routes/external-identity-links.d.ts +6 -0
  134. package/dist/routes/feedback.d.ts +3 -0
  135. package/dist/routes/github-browser-pages.d.ts +12 -0
  136. package/dist/routes/github.d.ts +2 -0
  137. package/dist/routes/insights-response-cache.d.ts +10 -0
  138. package/dist/routes/insights-usage.d.ts +12 -0
  139. package/dist/routes/insights.d.ts +30 -1
  140. package/dist/routes/knowledge.d.ts +3 -0
  141. package/dist/routes/managed-auth-session-sets.d.ts +26 -0
  142. package/dist/routes/managed-onboarding.d.ts +29 -0
  143. package/dist/routes/managed-sign-in-methods.d.ts +6 -0
  144. package/dist/routes/model-connection-access.d.ts +3 -0
  145. package/dist/routes/organization-integration-policy.d.ts +3 -0
  146. package/dist/routes/organization-integrations.d.ts +6 -0
  147. package/dist/routes/organization-mcp-connections.d.ts +3 -0
  148. package/dist/routes/organization-model-providers.d.ts +3 -0
  149. package/dist/routes/organization-recovery.d.ts +19 -0
  150. package/dist/routes/organization-service-accounts.d.ts +3 -0
  151. package/dist/routes/organization-sessions.d.ts +26 -0
  152. package/dist/routes/personal-github-git-broker.d.ts +29 -0
  153. package/dist/routes/personal-github.d.ts +3 -0
  154. package/dist/routes/pr-review-github.d.ts +3 -0
  155. package/dist/routes/pr-review.d.ts +3 -0
  156. package/dist/routes/session-artifact-associations.d.ts +4 -0
  157. package/dist/routes/session-history-imports.d.ts +9 -0
  158. package/dist/routes/sessions.d.ts +44 -6
  159. package/dist/routes/skill-content.d.ts +4 -0
  160. package/dist/routes/skill-install-authority.d.ts +6 -0
  161. package/dist/routes/subscription-account-pools.d.ts +34 -0
  162. package/dist/routes/subscription-pool-access.d.ts +16 -0
  163. package/dist/routes/supergrok.d.ts +6 -1
  164. package/dist/routes/usage-allowances.d.ts +12 -0
  165. package/dist/routes/user-resource-authorities.d.ts +3 -0
  166. package/dist/routes/workspace-artifacts.d.ts +2 -1
  167. package/dist/routes/workspace-capture.d.ts +10 -2
  168. package/dist/routes/workspace-integrations.d.ts +35 -0
  169. package/dist/routes/workspace-learning.d.ts +3 -0
  170. package/dist/routes/workspace-model-providers.d.ts +4 -0
  171. package/dist/routes/workspaces.d.ts +29 -0
  172. package/dist/sandbox/auth-callout.d.ts +8 -0
  173. package/dist/sandbox/channel-a.d.ts +19 -1
  174. package/dist/sandbox/connection-authority.d.ts +3 -0
  175. package/dist/sandbox/enrollment.d.ts +9 -1
  176. package/dist/sandbox/machines.d.ts +5 -2
  177. package/dist/sandbox/metrics-ingestion.d.ts +28 -20
  178. package/dist/sandbox/rematerialize.d.ts +2 -0
  179. package/dist/sandbox/session-attach-variable-sets.d.ts +30 -0
  180. package/dist/sandbox/viewer.d.ts +26 -1
  181. package/dist/sandbox-file-artifacts.d.ts +50 -0
  182. package/dist/scheduled-task-deletion.d.ts +13 -0
  183. package/dist/session-create-observability.d.ts +5 -0
  184. package/dist/session-event-view-MPS2OZD3.js +292 -0
  185. package/dist/session-event-view-MPS2OZD3.js.map +1 -0
  186. package/dist/site-session-origin.d.ts +13 -0
  187. package/dist/site-uploads.d.ts +41 -0
  188. package/dist/slack-reaction-files.d.ts +1 -1
  189. package/dist/temporal-schedule-cleanup.d.ts +1 -0
  190. package/dist/temporal-schedule-sync.d.ts +26 -0
  191. package/dist/transcription/providers/xai-subscription.d.ts +1 -0
  192. package/dist/transcription/service.d.ts +2 -1
  193. package/dist/work-discovery-observability.d.ts +33 -0
  194. package/dist/workspace-artifact-content.d.ts +28 -0
  195. package/dist/workspace-artifact-provenance.d.ts +7 -0
  196. package/dist/workspace-delete-observability.d.ts +10 -0
  197. package/dist/workspace-deletion.d.ts +6 -0
  198. package/dist/workspace-state-projection.d.ts +3 -5
  199. package/dist/workspace-tool-gateway-observability.d.ts +17 -0
  200. package/dist/workspace-tool-gateway.d.ts +123 -0
  201. package/dist/workspace-webhook-dispatch.d.ts +26 -0
  202. package/package.json +23 -19
  203. package/src/access-grant-rls.ts +75 -0
  204. package/src/admission-trace.ts +14 -0
  205. package/src/api-websocket.ts +23 -0
  206. package/src/app.ts +1967 -252
  207. package/src/artifact-catalog-cursor.ts +60 -0
  208. package/src/auth/managed-auth-attempt-context.ts +70 -0
  209. package/src/auth/managed-auth-rate-limits.ts +252 -0
  210. package/src/auth/managed-auth-session-adapter.ts +222 -0
  211. package/src/auth/managed-auth.ts +536 -60
  212. package/src/auth/managed-email.ts +174 -0
  213. package/src/auth/managed-sign-in-notifications.ts +81 -0
  214. package/src/auth/new-signups-gate.ts +49 -0
  215. package/src/auth/organization-user-setup.ts +307 -0
  216. package/src/auth/signup-funnel-metrics.ts +164 -0
  217. package/src/browser-controller-authority.ts +1 -186
  218. package/src/browser-ephemeral.ts +39 -0
  219. package/src/browser-state-authority.ts +1 -236
  220. package/src/claude-subscription-account-conflicts.ts +12 -0
  221. package/src/claude-subscription-account-usage.ts +48 -0
  222. package/src/claude-subscription-oauth.ts +362 -0
  223. package/src/claude-subscription-profile.ts +73 -0
  224. package/src/claude-subscription-usage.ts +97 -0
  225. package/src/claude-workspace-connection.ts +82 -0
  226. package/src/codemode.ts +194 -30
  227. package/src/codex-realtime.ts +8 -2
  228. package/src/company-brain-okf.ts +340 -0
  229. package/src/connected-machine-computer-access.ts +33 -0
  230. package/src/connection-ownership.ts +199 -0
  231. package/src/controller-data-plane.ts +81 -0
  232. package/src/editable-artifact-native-kernel.ts +26 -15
  233. package/src/editable-artifact-office-import.ts +28 -3
  234. package/src/editable-artifact-production.ts +23 -7
  235. package/src/editable-artifact-source-session.ts +123 -0
  236. package/src/editable-artifact-websocket.ts +27 -21
  237. package/src/editable-artifact-workspace-files.ts +31 -6
  238. package/src/fatal-process-boundary.ts +246 -0
  239. package/src/gateway-realtime.ts +1 -1
  240. package/src/github-access.ts +147 -5
  241. package/src/http/acting-person.ts +71 -0
  242. package/src/http/api-contract-compat.ts +49 -0
  243. package/src/http/api-error.ts +115 -1
  244. package/src/http/auth.ts +48 -0
  245. package/src/http/cors.ts +67 -0
  246. package/src/http/deprecation.ts +118 -0
  247. package/src/http/interaction-control-error.ts +164 -0
  248. package/src/http/keyed-admission.ts +29 -0
  249. package/src/http/local-browser-boundary.ts +343 -0
  250. package/src/http/metrics-listener.ts +57 -0
  251. package/src/http/path-identifier.ts +54 -0
  252. package/src/http/registered-route-label.ts +34 -0
  253. package/src/http/request-body.ts +127 -0
  254. package/src/http/request-source.ts +178 -0
  255. package/src/http/sse.ts +486 -132
  256. package/src/http/unmatched-route.ts +60 -0
  257. package/src/http/user-content.ts +158 -0
  258. package/src/http/websocket-upgrade-dispatch.ts +29 -0
  259. package/src/index.ts +157 -52
  260. package/src/integration-endpoint-test.ts +291 -0
  261. package/src/integrations/api-integrations.ts +98 -11
  262. package/src/integrations/atlassian.ts +474 -136
  263. package/src/integrations/connect-authority.ts +2 -0
  264. package/src/integrations/connect-callback-return.ts +86 -0
  265. package/src/integrations/fiken.ts +354 -64
  266. package/src/integrations/github-app-connect.ts +435 -0
  267. package/src/integrations/github-installation-proof.ts +60 -0
  268. package/src/integrations/github-lens-connect.ts +76 -0
  269. package/src/integrations/github-skill-source.ts +2 -142
  270. package/src/integrations/google-drive.ts +717 -225
  271. package/src/integrations/integration-commit-authority.ts +69 -0
  272. package/src/integrations/marketplace-plugin.ts +48 -0
  273. package/src/integrations/oauth-client.ts +1356 -459
  274. package/src/integrations/oauth-profiles.ts +537 -0
  275. package/src/integrations/oauth-return-path.ts +18 -0
  276. package/src/integrations/personal-github-repositories.ts +674 -0
  277. package/src/integrations/personal-github.ts +825 -0
  278. package/src/integrations/pr-review-provider.ts +246 -0
  279. package/src/integrations/provider-oauth.ts +250 -22
  280. package/src/integrations/slack-app-home.ts +300 -0
  281. package/src/integrations/slack-bot.ts +963 -239
  282. package/src/integrations/slack-file-upload-flow.ts +147 -0
  283. package/src/integrations/slack-install.ts +83 -0
  284. package/src/integrations/slack-interactions.ts +3167 -474
  285. package/src/integrations/slack-mrkdwn.ts +193 -0
  286. package/src/integrations/slack-routing.ts +337 -0
  287. package/src/integrations/slack-session-defaults.ts +224 -0
  288. package/src/integrations/slack-task-file-upload.ts +323 -0
  289. package/src/integrations/social-oauth.ts +213 -15
  290. package/src/interaction-agent-access.ts +48 -0
  291. package/src/interaction-frame-proxy.ts +409 -0
  292. package/src/interaction-metrics.ts +30 -0
  293. package/src/mcp/action-catalog-types.ts +9 -0
  294. package/src/mcp/action-catalog.gen.ts +6696 -0
  295. package/src/mcp/capability-account-readiness.ts +17 -0
  296. package/src/mcp/child-read-evidence.ts +19 -0
  297. package/src/mcp/command-read.ts +109 -0
  298. package/src/mcp/company-profile-agent-admin.ts +198 -0
  299. package/src/mcp/contract-input.ts +141 -0
  300. package/src/mcp/documents.ts +65 -377
  301. package/src/mcp/editable-artifacts.ts +12 -6
  302. package/src/mcp/files.ts +80 -43
  303. package/src/mcp/first-party-tool-permissions.ts +305 -0
  304. package/src/mcp/knowledge-entries.ts +322 -0
  305. package/src/mcp/orchestration-failure-diagnostic.ts +43 -0
  306. package/src/mcp/receipts.ts +34 -0
  307. package/src/mcp/request-abort.ts +44 -0
  308. package/src/mcp/scheduled-task-input.ts +76 -0
  309. package/src/mcp/scheduled-task-view.ts +44 -3
  310. package/src/mcp/server.ts +3030 -1267
  311. package/src/mcp/session-event-view.ts +383 -0
  312. package/src/mcp/session-view.ts +84 -3
  313. package/src/mcp/session-wait.ts +665 -0
  314. package/src/mcp-oauth.ts +1123 -0
  315. package/src/model-catalog.ts +91 -333
  316. package/src/observability.ts +15 -1
  317. package/src/organization-mcp.ts +397 -0
  318. package/src/organization-recovery-notifications.ts +103 -0
  319. package/src/routes/analytics-consent.ts +123 -0
  320. package/src/routes/api-integrations.ts +364 -74
  321. package/src/routes/api-keys.ts +336 -10
  322. package/src/routes/artifact-catalog.ts +279 -0
  323. package/src/routes/automations.ts +768 -0
  324. package/src/routes/billing.ts +461 -46
  325. package/src/routes/browser-sessions.ts +780 -112
  326. package/src/routes/canonical-human-identities.ts +90 -14
  327. package/src/routes/capabilities.ts +107 -6
  328. package/src/routes/channels.ts +20 -3
  329. package/src/routes/claude-subscription-accounts.ts +334 -0
  330. package/src/routes/claude-subscription-oauth.ts +142 -0
  331. package/src/routes/client-errors.ts +362 -0
  332. package/src/routes/codex.ts +833 -116
  333. package/src/routes/company-brain.ts +322 -0
  334. package/src/routes/company-profile.ts +65 -1
  335. package/src/routes/computer-sessions.ts +557 -106
  336. package/src/routes/connect.ts +1938 -0
  337. package/src/routes/connections.ts +1039 -349
  338. package/src/routes/documents.ts +488 -287
  339. package/src/routes/editable-artifacts.ts +79 -9
  340. package/src/routes/enrollments.ts +135 -39
  341. package/src/routes/environments.ts +154 -58
  342. package/src/routes/external-identity-links.ts +201 -0
  343. package/src/routes/feedback.ts +113 -0
  344. package/src/routes/files.ts +267 -33
  345. package/src/routes/github-browser-pages.ts +162 -0
  346. package/src/routes/github.ts +446 -144
  347. package/src/routes/insights-response-cache.ts +83 -0
  348. package/src/routes/insights-usage.ts +345 -0
  349. package/src/routes/insights.ts +153 -19
  350. package/src/routes/install.ts +133 -38
  351. package/src/routes/integration-facets.ts +107 -33
  352. package/src/routes/interaction-resources.ts +30 -3
  353. package/src/routes/knowledge.ts +488 -0
  354. package/src/routes/machines.ts +313 -15
  355. package/src/routes/managed-auth-session-sets.ts +1218 -0
  356. package/src/routes/managed-onboarding.ts +337 -0
  357. package/src/routes/managed-sign-in-methods.ts +433 -0
  358. package/src/routes/memory-slack-publications.ts +3 -2
  359. package/src/routes/model-connection-access.ts +208 -0
  360. package/src/routes/organization-integration-policy.ts +116 -0
  361. package/src/routes/organization-integrations.ts +326 -0
  362. package/src/routes/organization-mcp-connections.ts +178 -0
  363. package/src/routes/organization-memberships.ts +1019 -27
  364. package/src/routes/organization-model-providers.ts +293 -0
  365. package/src/routes/organization-recovery.ts +325 -0
  366. package/src/routes/organization-service-accounts.ts +128 -0
  367. package/src/routes/organization-sessions.ts +350 -0
  368. package/src/routes/personal-github-git-broker.ts +785 -0
  369. package/src/routes/personal-github.ts +370 -0
  370. package/src/routes/plugins.ts +251 -22
  371. package/src/routes/pr-review-github.ts +881 -0
  372. package/src/routes/pr-review.ts +622 -0
  373. package/src/routes/preference-registry.ts +12 -93
  374. package/src/routes/rigs.ts +167 -50
  375. package/src/routes/scheduled-tasks.ts +346 -41
  376. package/src/routes/session-artifact-associations.ts +87 -0
  377. package/src/routes/session-history-imports.ts +280 -0
  378. package/src/routes/sessions.ts +2306 -264
  379. package/src/routes/skill-content.ts +297 -0
  380. package/src/routes/skill-install-authority.ts +64 -0
  381. package/src/routes/skills.ts +62 -9
  382. package/src/routes/social.ts +46 -18
  383. package/src/routes/subscription-account-pools.ts +436 -0
  384. package/src/routes/subscription-pool-access.ts +126 -0
  385. package/src/routes/supergrok.ts +219 -303
  386. package/src/routes/transcription-recordings.ts +38 -3
  387. package/src/routes/transcriptions.ts +4 -0
  388. package/src/routes/usage-allowances.ts +378 -0
  389. package/src/routes/user-resource-authorities.ts +138 -0
  390. package/src/routes/video-generation.ts +14 -10
  391. package/src/routes/workspace-artifacts.ts +275 -69
  392. package/src/routes/workspace-capture.ts +21 -0
  393. package/src/routes/workspace-integrations.ts +576 -0
  394. package/src/routes/workspace-learning.ts +159 -0
  395. package/src/routes/workspace-model-providers.ts +148 -0
  396. package/src/routes/workspace-state.ts +38 -25
  397. package/src/routes/workspaces.ts +1004 -127
  398. package/src/sandbox/auth-callout.ts +96 -19
  399. package/src/sandbox/channel-a.ts +323 -24
  400. package/src/sandbox/connection-authority.ts +3 -0
  401. package/src/sandbox/enrollment.ts +81 -6
  402. package/src/sandbox/machines.ts +218 -76
  403. package/src/sandbox/metrics-ingestion.ts +411 -62
  404. package/src/sandbox/rematerialize.ts +121 -19
  405. package/src/sandbox/session-attach-variable-sets.ts +131 -0
  406. package/src/sandbox/viewer.ts +280 -25
  407. package/src/sandbox-file-artifacts.ts +413 -0
  408. package/src/scheduled-task-deletion.ts +138 -0
  409. package/src/session-create-observability.ts +46 -0
  410. package/src/session-realtime-context.ts +6 -3
  411. package/src/site-session-origin.ts +48 -0
  412. package/src/site-uploads.ts +224 -0
  413. package/src/slack-reaction-files.ts +16 -5
  414. package/src/temporal-schedule-cleanup.ts +13 -0
  415. package/src/temporal-schedule-sync.ts +74 -0
  416. package/src/transcription/providers/codex-subscription.ts +2 -0
  417. package/src/transcription/providers/openai.ts +1 -0
  418. package/src/transcription/providers/xai-subscription.ts +49 -3
  419. package/src/transcription/segmenter.ts +8 -2
  420. package/src/transcription/service.ts +43 -6
  421. package/src/work-discovery-observability.ts +121 -0
  422. package/src/workspace-artifact-content.ts +144 -0
  423. package/src/workspace-artifact-provenance.ts +104 -0
  424. package/src/workspace-delete-observability.ts +75 -0
  425. package/src/workspace-deletion.ts +73 -0
  426. package/src/workspace-state-projection.ts +18 -148
  427. package/src/workspace-tool-gateway-observability.ts +100 -0
  428. package/src/workspace-tool-gateway.ts +870 -0
  429. package/src/workspace-webhook-dispatch.ts +190 -0
  430. package/src/xai-subscription-auth.ts +9 -2
  431. package/dist/chunk-WCXHI3FF.js +0 -49920
  432. package/dist/chunk-WCXHI3FF.js.map +0 -1
  433. package/dist/routes/packs.d.ts +0 -3
  434. package/src/routes/packs.ts +0 -729
@@ -1,11 +1,11 @@
1
1
  // apps/api/src/sandbox/metrics-ingestion.ts — the M10 metrics INGESTION consumer
2
2
  // + the connect-Hello DISPLAY-REFRESH consumer. The
3
3
  // enrolled agent piggybacks a `MetricsSample` on its ~5s heartbeat (an
4
- // `AgentEvent` published one-way on `agent.<ws>.<id>.events`) and publishes a
5
- // `Hello` (its live self-description) on `agent.<ws>.<id>.hello` on every connect
4
+ // `AgentEvent` published one-way on the exact process events subject) and publishes a
5
+ // `Hello` (its live self-description) on the exact process hello subject on every connect
6
6
  // /reconnect. This module owns the two agent→control-plane inbound consumers:
7
7
  //
8
- // `agent.*.*.events` (heartbeat) →
8
+ // `agent.*.*.connection.*.events` (heartbeat) →
9
9
  // 1. touchEnrollmentLastSeen — the liveness cursor (online/reconnecting/offline
10
10
  // derivation + the M3 probe disambiguation).
11
11
  // 2. ingestMachineMetricsSample — UPSERT machine_metrics_latest (the "now" row)
@@ -13,7 +13,7 @@
13
13
  // A GOING-OFFLINE event is not a metrics point — liveness flips via the lease/
14
14
  // probe path; we skip it here (no-op).
15
15
  //
16
- // `agent.*.*.hello` (connect) →
16
+ // `agent.*.*.connection.*.hello` (connect) →
17
17
  // refreshEnrollmentDisplay — reconcile `enrollments.has_display` to the LIVE
18
18
  // capability the Hello reports. `has_display` was previously FROZEN at the
19
19
  // enroll-time offer snapshot; a machine that GAINS a display later (a Mac that
@@ -22,25 +22,32 @@
22
22
  // `has_display` track reality (both directions), which the desktop-capability
23
23
  // gate (packages/runtime capabilities.ts) keys off.
24
24
  // refreshEnrollmentOpStream — reconcile `enrollments.op_stream` to the LIVE
25
- // runner capability the Hello reports, leaving legacy request/reply exec as the
26
- // fallback unless the runner advertises the streaming engine.
25
+ // runner capability the Hello reports. Exec is unavailable unless the runner
26
+ // advertises the streaming engine.
27
27
  //
28
28
  // Both consumers are BEST-EFFORT and fail-soft: a decode/DB error for one message
29
29
  // is logged + swallowed (the bus subscription already swallows handler throws) so
30
30
  // a metrics blip / a display-refresh write failure never tears down the consumer,
31
- // back-pressures the agent, or breaks its connect.
31
+ // back-pressures the agent, or breaks its connect. Event ingestion drains the NATS
32
+ // subscription immediately into exact-subject queues: different runner connections
33
+ // progress concurrently, one runner's ordering is preserved, and consecutive queued
34
+ // heartbeats collapse latest-wins. A delayed heartbeat backlog therefore cannot keep
35
+ // renewing a dead runner's short connection lease one stale message at a time.
32
36
 
33
37
  import {
34
38
  clearEnrollmentWentOffline,
35
39
  disconnectAttachedBrowserDevices,
40
+ advanceEnrollmentAgentUpdate,
36
41
  getEnrollment,
42
+ getLiveEnrollmentConnection,
37
43
  ingestMachineMetricsSample,
38
44
  reconcileAttachedBrowserInventory,
45
+ releaseEnrollmentConnection,
46
+ renewEnrollmentConnection,
39
47
  sessionsWithActiveOpOnEnrollment,
40
48
  setEnrollmentDisplayState,
49
+ setEnrollmentAgentRuntime,
41
50
  setEnrollmentOpStreamState,
42
- setEnrollmentWentOffline,
43
- touchEnrollmentLastSeen,
44
51
  type AppendEventInput,
45
52
  type Database,
46
53
  type MachineMetricsSample,
@@ -51,8 +58,10 @@ import {
51
58
  } from "@opengeni/contracts";
52
59
  import { appendAndPublishEvents, type EventBus } from "@opengeni/events";
53
60
  import type { Observability } from "@opengeni/observability";
61
+ import { normalizeConnectedMachineWorkspaceRoot } from "@opengeni/runtime/sandbox";
54
62
  import {
55
63
  AgentEvent,
64
+ AgentUpdateStage,
56
65
  Arch,
57
66
  GoingOfflineReason,
58
67
  Hello,
@@ -61,40 +70,186 @@ import {
61
70
  type AttachedBrowserInventorySnapshot as WireAttachedBrowserInventorySnapshot,
62
71
  type MetricsSample,
63
72
  } from "@opengeni/agent-proto";
73
+ import { AGENT_CONNECTION_LEASE_MS } from "./connection-authority";
64
74
 
65
75
  /** The wildcard subject the agent event plane publishes heartbeats on. */
66
- export const AGENT_EVENTS_SUBJECT = "agent.*.*.events";
76
+ export const AGENT_EVENTS_SUBJECT = "agent.*.*.connection.*.events";
67
77
 
68
78
  /** The wildcard subject the agent publishes its connect Hello on. */
69
- export const AGENT_HELLO_SUBJECT = "agent.*.*.hello";
79
+ export const AGENT_HELLO_SUBJECT = "agent.*.*.connection.*.hello";
80
+
81
+ type QueuedAgentEvent = {
82
+ payload: Uint8Array;
83
+ subject: string;
84
+ heartbeat: boolean;
85
+ };
86
+
87
+ type AgentEventQueueState = {
88
+ scheduled: boolean;
89
+ pending: QueuedAgentEvent[];
90
+ };
91
+
92
+ type ScheduledAgentEventQueue = {
93
+ subject: string;
94
+ state: AgentEventQueueState;
95
+ };
96
+
97
+ export type AgentEventIngestionScheduler = {
98
+ enqueue: (payload: Uint8Array, subject: string) => void;
99
+ whenIdle: () => Promise<void>;
100
+ close: () => void;
101
+ };
102
+
103
+ function payloadIsHeartbeat(payload: Uint8Array): boolean {
104
+ try {
105
+ return AgentEvent.decode(payload).event?.$case === "heartbeat";
106
+ } catch {
107
+ // Preserve the normal decoder/logging path for malformed events. They are
108
+ // barriers rather than heartbeat-coalescing candidates.
109
+ return false;
110
+ }
111
+ }
70
112
 
71
113
  /**
72
- * Parse `agent.<ws>.<id>.<tail>` → `{ workspaceId, agentId }`, requiring the
114
+ * Drain agent events without making every Connected Machine wait behind one
115
+ * deployment-wide serial DB queue. Each exact process subject retains FIFO order;
116
+ * separate subjects drain concurrently. Consecutive pending heartbeats for one
117
+ * subject are latest-wins because they are point-in-time liveness/metrics samples,
118
+ * while GoingOffline and update-progress events remain ordered barriers.
119
+ */
120
+ export function createAgentEventIngestionScheduler(
121
+ handler: (payload: Uint8Array, subject: string) => void | Promise<void>,
122
+ options: { maxConcurrentSubjects?: number; onHeartbeatCoalesced?: () => void } = {},
123
+ ): AgentEventIngestionScheduler {
124
+ const maxConcurrentSubjects = options.maxConcurrentSubjects ?? 32;
125
+ if (!Number.isSafeInteger(maxConcurrentSubjects) || maxConcurrentSubjects <= 0) {
126
+ throw new RangeError("maxConcurrentSubjects must be a positive safe integer");
127
+ }
128
+
129
+ const states = new Map<string, AgentEventQueueState>();
130
+ const readySubjects: ScheduledAgentEventQueue[] = [];
131
+ const idleWaiters = new Set<() => void>();
132
+ let accepting = true;
133
+ let activeSubjects = 0;
134
+
135
+ const resolveIdleWaiters = (): void => {
136
+ if (states.size !== 0) return;
137
+ for (const resolve of idleWaiters) resolve();
138
+ idleWaiters.clear();
139
+ };
140
+
141
+ async function drain(subject: string, state: AgentEventQueueState): Promise<void> {
142
+ while (true) {
143
+ const event = state.pending.shift();
144
+ if (!event) break;
145
+ try {
146
+ await handler(event.payload, event.subject);
147
+ } catch {
148
+ // Agent-event ingestion is best-effort. One bad event must not strand the
149
+ // exact-subject queue or prevent later heartbeats from restoring liveness.
150
+ }
151
+ }
152
+
153
+ activeSubjects -= 1;
154
+ state.scheduled = false;
155
+ if (state.pending.length > 0) {
156
+ schedule(subject, state);
157
+ } else {
158
+ if (states.get(subject) === state) states.delete(subject);
159
+ resolveIdleWaiters();
160
+ }
161
+ pump();
162
+ }
163
+
164
+ function pump(): void {
165
+ while (activeSubjects < maxConcurrentSubjects) {
166
+ const next = readySubjects.shift();
167
+ if (!next) return;
168
+ if (states.get(next.subject) !== next.state || !next.state.scheduled) continue;
169
+ activeSubjects += 1;
170
+ void drain(next.subject, next.state);
171
+ }
172
+ }
173
+
174
+ function schedule(subject: string, state: AgentEventQueueState): void {
175
+ if (state.scheduled) return;
176
+ state.scheduled = true;
177
+ readySubjects.push({ subject, state });
178
+ pump();
179
+ }
180
+
181
+ return {
182
+ enqueue(payload, subject) {
183
+ if (!accepting) return;
184
+ const heartbeat = payloadIsHeartbeat(payload);
185
+ let state = states.get(subject);
186
+ if (!state) {
187
+ state = { scheduled: false, pending: [] };
188
+ states.set(subject, state);
189
+ }
190
+
191
+ const queued = { payload, subject, heartbeat };
192
+ const lastIndex = state.pending.length - 1;
193
+ if (heartbeat && lastIndex >= 0 && state.pending[lastIndex]!.heartbeat) {
194
+ state.pending[lastIndex] = queued;
195
+ options.onHeartbeatCoalesced?.();
196
+ } else {
197
+ state.pending.push(queued);
198
+ }
199
+
200
+ schedule(subject, state);
201
+ },
202
+ async whenIdle() {
203
+ if (states.size === 0) return;
204
+ await new Promise<void>((resolve) => idleWaiters.add(resolve));
205
+ },
206
+ close() {
207
+ accepting = false;
208
+ for (const state of states.values()) {
209
+ state.pending.length = 0;
210
+ }
211
+ resolveIdleWaiters();
212
+ },
213
+ };
214
+ }
215
+
216
+ /**
217
+ * Parse `agent.<ws>.<id>.connection.<instance>.<tail>` into its exact authority,
73
218
  * expected tail token. Returns null for a subject that does not match the shape
74
219
  * (defensive — the subscription pattern already constrains it).
75
220
  */
76
221
  function parseAgentSubject(
77
222
  subject: string,
78
223
  tail: "events" | "hello",
79
- ): { workspaceId: string; agentId: string } | null {
224
+ ): { workspaceId: string; agentId: string; connectionInstanceId: string } | null {
80
225
  const parts = subject.split(".");
81
- if (parts.length !== 4 || parts[0] !== "agent" || parts[3] !== tail) {
226
+ if (
227
+ parts.length !== 6 ||
228
+ parts[0] !== "agent" ||
229
+ parts[3] !== "connection" ||
230
+ !parts[4] ||
231
+ parts[5] !== tail
232
+ ) {
82
233
  return null;
83
234
  }
84
- return { workspaceId: parts[1]!, agentId: parts[2]! };
235
+ return {
236
+ workspaceId: parts[1]!,
237
+ agentId: parts[2]!,
238
+ connectionInstanceId: parts[4]!,
239
+ };
85
240
  }
86
241
 
87
- /** Parse `agent.<ws>.<id>.events` → `{ workspaceId, agentId }` (heartbeat plane). */
242
+ /** Parse the exact process events subject (heartbeat plane). */
88
243
  export function parseAgentEventSubject(
89
244
  subject: string,
90
- ): { workspaceId: string; agentId: string } | null {
245
+ ): { workspaceId: string; agentId: string; connectionInstanceId: string } | null {
91
246
  return parseAgentSubject(subject, "events");
92
247
  }
93
248
 
94
- /** Parse `agent.<ws>.<id>.hello` → `{ workspaceId, agentId }` (connect plane). */
249
+ /** Parse the exact process hello subject (connect plane). */
95
250
  export function parseAgentHelloSubject(
96
251
  subject: string,
97
- ): { workspaceId: string; agentId: string } | null {
252
+ ): { workspaceId: string; agentId: string; connectionInstanceId: string } | null {
98
253
  return parseAgentSubject(subject, "hello");
99
254
  }
100
255
 
@@ -218,30 +373,23 @@ async function ingestAttachedBrowserInventory(
218
373
  }
219
374
 
220
375
  /**
221
- * Ingest ONE decoded heartbeat for an enrolled machine. Resolves the enrollment's
222
- * accountId (needed for the RLS-scoped writes) from the enrollment row; an
223
- * unknown/cross-workspace agentId is ignored (no row → no write). Touches
224
- * last-seen + upserts latest + downsamples the series.
376
+ * Ingest the OPTIONAL metrics sample carried by an already-authorized heartbeat.
377
+ * Connection renewal is deliberately owned by the outer event handler and runs
378
+ * before optional telemetry/inventory work, so telemetry absence or failure can
379
+ * never make a healthy runner lose authority.
225
380
  */
226
- export async function ingestHeartbeat(
381
+ async function ingestHeartbeatMetrics(
227
382
  db: Database,
228
- input: { workspaceId: string; agentId: string; sample: MetricsSample },
383
+ input: {
384
+ accountId: string;
385
+ workspaceId: string;
386
+ agentId: string;
387
+ sample: MetricsSample;
388
+ },
229
389
  ): Promise<{ ingested: boolean; seriesAppended: boolean }> {
230
- // The enrollment row is the source of the accountId (the RLS principal) and the
231
- // existence check. A revoked machine still reports its accountId, so we ingest
232
- // (the dashboard shows its last sample); a truly unknown id is a no-op.
233
- const enrollment = await getEnrollment(db, input.workspaceId, input.agentId);
234
- if (!enrollment) {
235
- return { ingested: false, seriesAppended: false };
236
- }
237
390
  const sample = wireSampleToDbSample(input.sample);
238
- await touchEnrollmentLastSeen(db, {
239
- accountId: enrollment.accountId,
240
- workspaceId: input.workspaceId,
241
- enrollmentId: input.agentId,
242
- });
243
391
  const result = await ingestMachineMetricsSample(db, {
244
- accountId: enrollment.accountId,
392
+ accountId: input.accountId,
245
393
  workspaceId: input.workspaceId,
246
394
  enrollmentId: input.agentId,
247
395
  sample,
@@ -340,12 +488,16 @@ export async function handleAgentEventPayload(
340
488
  try {
341
489
  const enrollment = await getEnrollment(db, ids.workspaceId, ids.agentId);
342
490
  if (enrollment) {
343
- await setEnrollmentWentOffline(db, {
491
+ const released = await releaseEnrollmentConnection(db, {
344
492
  accountId: enrollment.accountId,
345
493
  workspaceId: ids.workspaceId,
346
494
  enrollmentId: ids.agentId,
495
+ connectionInstanceId: ids.connectionInstanceId,
347
496
  reason,
348
497
  });
498
+ // A late goodbye from a superseded process is not the current machine
499
+ // going offline. It must not disconnect browsers or fan out link loss.
500
+ if (!released.released) return;
349
501
  try {
350
502
  await disconnectAttachedBrowserDevices(db, {
351
503
  accountId: enrollment.accountId,
@@ -396,10 +548,77 @@ export async function handleAgentEventPayload(
396
548
  }
397
549
  return;
398
550
  }
551
+ if (event.event?.$case === "agentUpdateProgress") {
552
+ const enrollment = await getEnrollment(db, ids.workspaceId, ids.agentId).catch(() => null);
553
+ if (!enrollment || enrollment.connectionInstanceId !== ids.connectionInstanceId) return;
554
+ const progress = event.event.agentUpdateProgress;
555
+ const status = (() => {
556
+ switch (progress.stage) {
557
+ case AgentUpdateStage.AGENT_UPDATE_STAGE_ACCEPTED:
558
+ return "accepted" as const;
559
+ case AgentUpdateStage.AGENT_UPDATE_STAGE_WAITING_FOR_IDLE:
560
+ return "waiting_for_idle" as const;
561
+ case AgentUpdateStage.AGENT_UPDATE_STAGE_DOWNLOADING:
562
+ return "downloading" as const;
563
+ case AgentUpdateStage.AGENT_UPDATE_STAGE_VERIFYING:
564
+ return "verifying" as const;
565
+ case AgentUpdateStage.AGENT_UPDATE_STAGE_APPLYING:
566
+ return "applying" as const;
567
+ case AgentUpdateStage.AGENT_UPDATE_STAGE_RESTARTING:
568
+ return "restarting" as const;
569
+ case AgentUpdateStage.AGENT_UPDATE_STAGE_FAILED:
570
+ return "failed" as const;
571
+ default:
572
+ return null;
573
+ }
574
+ })();
575
+ if (!status) return;
576
+ try {
577
+ await advanceEnrollmentAgentUpdate(db, {
578
+ accountId: enrollment.accountId,
579
+ workspaceId: ids.workspaceId,
580
+ enrollmentId: ids.agentId,
581
+ connectionInstanceId: ids.connectionInstanceId,
582
+ connectionGeneration: enrollment.connectionGeneration,
583
+ operationId: progress.operationId,
584
+ status,
585
+ expectedBinarySha256: /^[0-9a-f]{64}$/.test(progress.expectedBinarySha256)
586
+ ? progress.expectedBinarySha256
587
+ : null,
588
+ errorCode: progress.errorCode || null,
589
+ retryable: progress.retryable,
590
+ rolledBack: progress.rolledBack,
591
+ });
592
+ } catch (error) {
593
+ observability?.warn?.("Failed to ingest a machine self-update progress event", {
594
+ subject,
595
+ error: error instanceof Error ? error.message : String(error),
596
+ });
597
+ }
598
+ return;
599
+ }
399
600
  if (event.event?.$case !== "heartbeat") {
400
601
  return; // an unknown event kind → not a metrics point.
401
602
  }
402
603
  const heartbeat = event.event.heartbeat;
604
+ const enrollment = await getEnrollment(db, ids.workspaceId, ids.agentId).catch(() => null);
605
+ if (!enrollment || enrollment.connectionInstanceId !== ids.connectionInstanceId) return;
606
+ try {
607
+ const renewed = await renewEnrollmentConnection(db, {
608
+ accountId: enrollment.accountId,
609
+ workspaceId: ids.workspaceId,
610
+ enrollmentId: ids.agentId,
611
+ connectionInstanceId: ids.connectionInstanceId,
612
+ leaseMs: AGENT_CONNECTION_LEASE_MS,
613
+ });
614
+ if (!renewed.renewed) return;
615
+ } catch (error) {
616
+ observability?.warn?.("Failed to renew a machine connection heartbeat", {
617
+ subject,
618
+ error: error instanceof Error ? error.message : String(error),
619
+ });
620
+ return;
621
+ }
403
622
  if (heartbeat.attachedBrowserInventory) {
404
623
  try {
405
624
  await ingestAttachedBrowserInventory(db, {
@@ -414,10 +633,39 @@ export async function handleAgentEventPayload(
414
633
  });
415
634
  }
416
635
  }
636
+ if (heartbeat.desktopStatus) {
637
+ try {
638
+ const status = heartbeat.desktopStatus;
639
+ const available = status.available && !status.unavailableReason;
640
+ const reason = status.unavailableReason || null;
641
+ // The heartbeat already read the exact enrollment. Steady state adds no
642
+ // extra query; the change path re-reads and fences its eventual write.
643
+ if (
644
+ enrollment.hasDisplay !== available ||
645
+ (enrollment.desktopUnavailableReason ?? null) !== reason ||
646
+ enrollment.agentCapabilities?.desktop !== available
647
+ ) {
648
+ await refreshEnrollmentDisplay(db, {
649
+ workspaceId: ids.workspaceId,
650
+ agentId: ids.agentId,
651
+ hasDisplay: available,
652
+ desktopUnavailableReason: reason,
653
+ runtimeDesktop: available,
654
+ connectionInstanceId: ids.connectionInstanceId,
655
+ });
656
+ }
657
+ } catch (error) {
658
+ observability?.warn?.("Failed to refresh a machine desktop heartbeat", {
659
+ subject,
660
+ error: error instanceof Error ? error.message : String(error),
661
+ });
662
+ }
663
+ }
417
664
  const metrics = heartbeat.metrics;
418
665
  if (!metrics) return;
419
666
  try {
420
- await ingestHeartbeat(db, {
667
+ await ingestHeartbeatMetrics(db, {
668
+ accountId: enrollment.accountId,
421
669
  workspaceId: ids.workspaceId,
422
670
  agentId: ids.agentId,
423
671
  sample: metrics,
@@ -431,7 +679,7 @@ export async function handleAgentEventPayload(
431
679
  }
432
680
 
433
681
  /**
434
- * Start the metrics-ingestion consumer: subscribe `agent.*.*.events` and ingest
682
+ * Start the metrics-ingestion consumer: subscribe exact process events and ingest
435
683
  * every heartbeat. Gated by sandboxSelfhostedEnabled (the caller checks the flag;
436
684
  * a disabled deployment never starts the consumer). Returns the unsubscribe fn.
437
685
  */
@@ -440,9 +688,24 @@ export function startMetricsIngestion(deps: {
440
688
  bus: EventBus;
441
689
  observability?: Observability;
442
690
  }): () => void {
443
- return deps.bus.subscribeAgentEvents(AGENT_EVENTS_SUBJECT, (payload, subject) =>
444
- handleAgentEventPayload(deps.db, deps.observability, payload, subject, deps.bus),
691
+ const scheduler = createAgentEventIngestionScheduler(
692
+ (payload, subject) =>
693
+ handleAgentEventPayload(deps.db, deps.observability, payload, subject, deps.bus),
694
+ {
695
+ onHeartbeatCoalesced: () =>
696
+ deps.observability?.incrementCounter({
697
+ name: "opengeni_machine_heartbeat_coalesced_total",
698
+ help: "Connected Machine heartbeats collapsed while an exact runner event queue was busy.",
699
+ }),
700
+ },
445
701
  );
702
+ const unsubscribe = deps.bus.subscribeAgentEvents(AGENT_EVENTS_SUBJECT, (payload, subject) => {
703
+ scheduler.enqueue(payload, subject);
704
+ });
705
+ return () => {
706
+ unsubscribe();
707
+ scheduler.close();
708
+ };
446
709
  }
447
710
 
448
711
  // ── Connect-Hello display refresh ─────────────────────────────────────────────
@@ -490,6 +753,47 @@ export function helloReportsOpStream(hello: Hello): boolean {
490
753
  return hello.capabilities?.opStream === true;
491
754
  }
492
755
 
756
+ export function helloRuntimeCapabilities(hello: Hello): Record<string, boolean> {
757
+ const caps = hello.capabilities;
758
+ // Absence is an older-agent/unknown signal, not a fabricated set of false claims.
759
+ // Keep the durable cursor empty so reconnects from legacy agents remain a
760
+ // no-op and future capability additions do not get silently fabricated.
761
+ if (!caps) return {};
762
+ return {
763
+ exec: caps.exec === true,
764
+ filesystem: caps.filesystem === true,
765
+ git: caps.git === true,
766
+ pty: caps.pty === true,
767
+ desktop: caps.desktop === true,
768
+ opStream: caps.opStream === true,
769
+ browserBridge: caps.browserBridge === true,
770
+ operationResourcePolicy: caps.operationResourcePolicy === true,
771
+ operationCpuQuota: caps.operationCpuQuota === true,
772
+ transactionalFsWrite: caps.transactionalFsWrite === true,
773
+ };
774
+ }
775
+
776
+ function helloCompletedUpdate(hello: Hello): {
777
+ operationId: string;
778
+ targetVersion: string;
779
+ binarySha256: string;
780
+ } | null {
781
+ if (
782
+ !/^[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(
783
+ hello.completedUpdateOperationId,
784
+ ) ||
785
+ !/^(?:v)?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(hello.completedUpdateTargetVersion) ||
786
+ !/^[0-9a-f]{64}$/.test(hello.completedUpdateBinarySha256)
787
+ ) {
788
+ return null;
789
+ }
790
+ return {
791
+ operationId: hello.completedUpdateOperationId,
792
+ targetVersion: hello.completedUpdateTargetVersion,
793
+ binarySha256: hello.completedUpdateBinarySha256,
794
+ };
795
+ }
796
+
493
797
  /**
494
798
  * Reconcile `enrollments.has_display` (+ the capture-blocked reason) to what a Hello
495
799
  * reports. Resolves the enrollment (the accountId is the RLS principal + the
@@ -505,6 +809,8 @@ export async function refreshEnrollmentDisplay(
505
809
  agentId: string;
506
810
  hasDisplay: boolean;
507
811
  desktopUnavailableReason?: string | null;
812
+ runtimeDesktop?: boolean;
813
+ connectionInstanceId?: string;
508
814
  },
509
815
  ): Promise<{ updated: boolean }> {
510
816
  const desktopUnavailableReason = input.desktopUnavailableReason ?? null;
@@ -514,7 +820,9 @@ export async function refreshEnrollmentDisplay(
514
820
  }
515
821
  if (
516
822
  enrollment.hasDisplay === input.hasDisplay &&
517
- (enrollment.desktopUnavailableReason ?? null) === desktopUnavailableReason
823
+ (enrollment.desktopUnavailableReason ?? null) === desktopUnavailableReason &&
824
+ (input.runtimeDesktop === undefined ||
825
+ enrollment.agentCapabilities?.desktop === input.runtimeDesktop)
518
826
  ) {
519
827
  // Both fields unchanged — do not even issue the UPDATE (no churn on a
520
828
  // steady-state Hello).
@@ -526,6 +834,8 @@ export async function refreshEnrollmentDisplay(
526
834
  enrollmentId: input.agentId,
527
835
  hasDisplay: input.hasDisplay,
528
836
  desktopUnavailableReason,
837
+ ...(input.runtimeDesktop !== undefined ? { runtimeDesktop: input.runtimeDesktop } : {}),
838
+ ...(input.connectionInstanceId ? { connectionInstanceId: input.connectionInstanceId } : {}),
529
839
  });
530
840
  }
531
841
 
@@ -541,6 +851,7 @@ export async function refreshEnrollmentOpStream(
541
851
  workspaceId: string;
542
852
  agentId: string;
543
853
  opStream: boolean;
854
+ connectionInstanceId?: string;
544
855
  },
545
856
  ): Promise<{ updated: boolean }> {
546
857
  const enrollment = await getEnrollment(db, input.workspaceId, input.agentId);
@@ -557,6 +868,7 @@ export async function refreshEnrollmentOpStream(
557
868
  workspaceId: input.workspaceId,
558
869
  enrollmentId: input.agentId,
559
870
  opStream: input.opStream,
871
+ ...(input.connectionInstanceId ? { connectionInstanceId: input.connectionInstanceId } : {}),
560
872
  });
561
873
  }
562
874
 
@@ -589,17 +901,56 @@ export async function handleHelloPayload(
589
901
  });
590
902
  return;
591
903
  }
904
+ const authority = await getLiveEnrollmentConnection(db, ids.workspaceId, ids.agentId).catch(
905
+ () => null,
906
+ );
907
+ if (!authority || authority.connectionInstanceId !== ids.connectionInstanceId) {
908
+ observability?.warn?.("Ignored a Hello from a superseded runner instance", {
909
+ workspaceId: ids.workspaceId,
910
+ agentId: ids.agentId,
911
+ });
912
+ return;
913
+ }
914
+ let workspaceRoot: string | undefined;
915
+ if (hello.workspaceRoot.length > 0) {
916
+ try {
917
+ workspaceRoot = normalizeConnectedMachineWorkspaceRoot(hello.workspaceRoot, authority.os);
918
+ } catch (error) {
919
+ observability?.warn?.("Ignored an invalid Connected Machine workspace root", {
920
+ workspaceId: ids.workspaceId,
921
+ agentId: ids.agentId,
922
+ error: error instanceof Error ? error.message : String(error),
923
+ });
924
+ }
925
+ }
592
926
  try {
593
927
  await refreshEnrollmentDisplay(db, {
594
928
  workspaceId: ids.workspaceId,
595
929
  agentId: ids.agentId,
596
930
  hasDisplay: helloReportsDisplay(hello),
597
931
  desktopUnavailableReason: helloDesktopUnavailableReason(hello),
932
+ connectionInstanceId: ids.connectionInstanceId,
598
933
  });
599
934
  await refreshEnrollmentOpStream(db, {
600
935
  workspaceId: ids.workspaceId,
601
936
  agentId: ids.agentId,
602
937
  opStream: helloReportsOpStream(hello),
938
+ connectionInstanceId: ids.connectionInstanceId,
939
+ });
940
+ await setEnrollmentAgentRuntime(db, {
941
+ accountId: authority.accountId,
942
+ workspaceId: ids.workspaceId,
943
+ enrollmentId: ids.agentId,
944
+ connectionInstanceId: ids.connectionInstanceId,
945
+ agentVersion: hello.agentVersion.trim() || null,
946
+ binarySha256: /^[0-9a-f]{64}$/.test(hello.binarySha256) ? hello.binarySha256 : null,
947
+ updateChannel:
948
+ hello.updateChannel === "stable" || hello.updateChannel === "beta"
949
+ ? hello.updateChannel
950
+ : null,
951
+ capabilities: helloRuntimeCapabilities(hello),
952
+ ...(workspaceRoot !== undefined ? { workspaceRoot } : {}),
953
+ completedUpdate: helloCompletedUpdate(hello),
603
954
  });
604
955
  } catch (error) {
605
956
  observability?.warn?.("Failed to refresh an enrollment's capabilities from a Hello", {
@@ -616,23 +967,21 @@ export async function handleHelloPayload(
616
967
  // sessions with an active op on it — a restored only ever pairs a prior lost, so
617
968
  // a routine connect Hello (no marker) emits nothing.
618
969
  try {
619
- const enrollment = await getEnrollment(db, ids.workspaceId, ids.agentId);
620
- if (enrollment) {
621
- const { cleared } = await clearEnrollmentWentOffline(db, {
622
- accountId: enrollment.accountId,
623
- workspaceId: ids.workspaceId,
624
- enrollmentId: ids.agentId,
625
- });
626
- if (cleared && bus) {
627
- await fanOutMachineLinkEvents(
628
- db,
629
- bus,
630
- observability,
631
- ids.workspaceId,
632
- ids.agentId,
633
- (activeTurnId) => [{ type: "machine.link.restored", turnId: activeTurnId, payload: {} }],
634
- );
635
- }
970
+ const { cleared } = await clearEnrollmentWentOffline(db, {
971
+ accountId: authority.accountId,
972
+ workspaceId: ids.workspaceId,
973
+ enrollmentId: ids.agentId,
974
+ connectionInstanceId: ids.connectionInstanceId,
975
+ });
976
+ if (cleared && bus) {
977
+ await fanOutMachineLinkEvents(
978
+ db,
979
+ bus,
980
+ observability,
981
+ ids.workspaceId,
982
+ ids.agentId,
983
+ (activeTurnId) => [{ type: "machine.link.restored", turnId: activeTurnId, payload: {} }],
984
+ );
636
985
  }
637
986
  } catch (error) {
638
987
  observability?.warn?.("Failed to clear a machine going-offline marker on a Hello", {
@@ -643,7 +992,7 @@ export async function handleHelloPayload(
643
992
  }
644
993
 
645
994
  /**
646
- * Start the Hello display-refresh consumer: subscribe `agent.*.*.hello` and
995
+ * Start the Hello display-refresh consumer: subscribe exact process hellos and
647
996
  * reconcile `has_display` to the live capability the agent reports on every
648
997
  * connect. Gated by sandboxSelfhostedEnabled (the caller checks the flag). Returns
649
998
  * the unsubscribe fn.