@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
package/dist/index.js CHANGED
@@ -1,15 +1,26 @@
1
1
  import {
2
+ InteractionFrameProxyTransport,
2
3
  SlackBotProviderError,
4
+ apiRequestBindingsForTransportPeer,
5
+ cleanupScheduledTaskConnectorAuthorization,
3
6
  createAppComposition,
7
+ createLocalBrowserBoundary,
4
8
  createOpenGeniSlackBotInteractionClient,
9
+ localBrowserBoundaryResponse,
10
+ sql,
11
+ startApiMetricsListener,
5
12
  startSlackInteractionPump,
6
- startTemporalScheduleCleanupPump
7
- } from "./chunk-WCXHI3FF.js";
13
+ startTemporalScheduleCleanupPump,
14
+ withAccessGrantSessionRlsContext
15
+ } from "./chunk-6TZ5LG3O.js";
16
+ import "./chunk-OGQDXVXY.js";
8
17
 
9
18
  // src/index.ts
19
+ import { scheduledTaskKnowledgeSource } from "@opengeni/contracts";
10
20
  import {
11
21
  dbSearchPath,
12
22
  getSettings,
23
+ resolveFirstPartyDelegationSecret,
13
24
  resolveNatsCalloutConfig,
14
25
  resolveNatsControlPlaneAuth,
15
26
  retryStartupDependency,
@@ -18,15 +29,22 @@ import {
18
29
  } from "@opengeni/config";
19
30
  import {
20
31
  assertRuntimeDatabasePosture,
32
+ isRetryableRuntimeDatabaseStartupError,
21
33
  createDb,
22
34
  markSessionWorkflowWakeDelivered,
23
35
  runtimeDatabaseReadyCheck
24
36
  } from "@opengeni/db";
25
37
  import { createNatsEventBus } from "@opengeni/events";
26
- import { createObservability, logStartupDependencyRetry } from "@opengeni/observability";
38
+ import {
39
+ createObservability,
40
+ logStartupDependencyRetry
41
+ } from "@opengeni/observability";
27
42
  import { createObjectStorage } from "@opengeni/storage";
28
43
  import { isArtifactRuntimeConfigured } from "@opengeni/artifact-tool/runtime/development";
29
- import { SESSION_WORKFLOW_WAKE_DISPATCHER_SCHEDULE_ID } from "@opengeni/core";
44
+ import {
45
+ resolveCatalogSettings,
46
+ SESSION_WORKFLOW_WAKE_DISPATCHER_SCHEDULE_ID
47
+ } from "@opengeni/core";
30
48
  import {
31
49
  Connection,
32
50
  Client as TemporalClient,
@@ -36,6 +54,15 @@ import {
36
54
  } from "@temporalio/client";
37
55
 
38
56
  // src/observability.ts
57
+ import {
58
+ natsSubscriptionTerminationCounter
59
+ } from "@opengeni/events";
60
+ function observabilityEventBusOptions(observability) {
61
+ return {
62
+ logger: observabilityEventLogger(observability),
63
+ onSubscriptionTerminated: natsSubscriptionTerminationCounter(observability)
64
+ };
65
+ }
39
66
  function observabilityEventLogger(observability) {
40
67
  return {
41
68
  debug: (message, attributes) => observability.debug(message, eventAttributes(attributes)),
@@ -64,20 +91,45 @@ function eventAttributeValue(value) {
64
91
  }
65
92
 
66
93
  // src/sandbox/auth-callout.ts
94
+ import { createHash } from "crypto";
95
+ import { createLogThrottle } from "@opengeni/observability";
67
96
  import {
68
97
  resolveEnrollmentSigningSecret
69
98
  } from "@opengeni/config";
70
99
  import { verifyEnrollmentBearer } from "@opengeni/contracts";
71
- import { getEnrollment } from "@opengeni/db";
100
+ import { claimEnrollmentConnection, getEnrollment } from "@opengeni/db";
72
101
  import {
73
102
  createResponderConnection,
74
103
  decodeAuthRequest,
75
104
  mintAuthResponse,
76
105
  mintUserJwt,
106
+ parseAgentConnectionName,
77
107
  workspaceAgentPermissions
78
108
  } from "@opengeni/events";
109
+
110
+ // src/sandbox/connection-authority.ts
111
+ var AGENT_CONNECTION_LEASE_MS = 2e4;
112
+
113
+ // src/sandbox/auth-callout.ts
79
114
  var AUTH_CALLOUT_SUBJECT = "$SYS.REQ.USER.AUTH";
80
115
  var NATS_USER_JWT_TTL_SECONDS = 5 * 60;
116
+ var AUTH_CALLOUT_DENIAL_WARNING_INTERVAL_MS = 6e4;
117
+ var processDenialWarningThrottle = createLogThrottle({
118
+ intervalMs: AUTH_CALLOUT_DENIAL_WARNING_INTERVAL_MS,
119
+ maxKeys: 1024
120
+ });
121
+ function warnDenial(deps, key, reason, message, attributes = {}) {
122
+ const admission = (deps.denialWarningThrottle ?? processDenialWarningThrottle).admit(key);
123
+ if (!admission) return;
124
+ deps.observability?.warn?.(message, {
125
+ ...attributes,
126
+ reason,
127
+ ...admission.suppressedCount > 0 ? { suppressedCount: admission.suppressedCount } : {}
128
+ });
129
+ }
130
+ function rejectedBearerKey(bearer) {
131
+ return `invalid-bearer:${createHash("sha256").update(bearer).digest("hex").slice(0, 32)}`;
132
+ }
81
133
  async function handleAuthorizationRequest(deps, requestBytes) {
82
134
  const requestJwt = Buffer.from(requestBytes).toString("utf8");
83
135
  const decoded = decodeAuthRequest(requestJwt);
@@ -104,15 +156,23 @@ async function handleAuthorizationRequest(deps, requestBytes) {
104
156
  }
105
157
  const claims = await verifyEnrollmentBearer(secret, bearer);
106
158
  if (!claims) {
107
- deps.observability?.warn?.("auth-callout: rejected an invalid enrollment bearer", {});
159
+ warnDenial(
160
+ deps,
161
+ rejectedBearerKey(bearer),
162
+ "invalid_bearer",
163
+ "auth-callout: rejected an invalid enrollment bearer"
164
+ );
108
165
  return deny("invalid or expired enrollment bearer");
109
166
  }
110
167
  const enrollment = await getEnrollment(deps.db, claims.workspaceId, claims.enrollmentId);
111
168
  if (!enrollment || enrollment.status !== "active") {
112
- deps.observability?.warn?.("auth-callout: denied a revoked or unknown enrollment", {
113
- workspaceId: claims.workspaceId,
114
- agentId: claims.agentId
115
- });
169
+ warnDenial(
170
+ deps,
171
+ `inactive:${claims.workspaceId}:${claims.agentId}`,
172
+ "inactive_enrollment",
173
+ "auth-callout: denied a revoked or unknown enrollment",
174
+ { workspaceId: claims.workspaceId, agentId: claims.agentId }
175
+ );
116
176
  return deny("enrollment is not active");
117
177
  }
118
178
  if (enrollment.workspaceId !== claims.workspaceId || enrollment.id !== claims.enrollmentId || enrollment.id !== claims.agentId || claims.agentId !== claims.enrollmentId || claims.subjectPrefix !== `agent.${claims.workspaceId}.${claims.agentId}`) {
@@ -121,7 +181,32 @@ async function handleAuthorizationRequest(deps, requestBytes) {
121
181
  if (enrollment.credentialGeneration !== claims.credentialGeneration) {
122
182
  return deny("enrollment credential generation mismatch");
123
183
  }
124
- const permissions = workspaceAgentPermissions(claims.workspaceId);
184
+ const connectionInstanceId = parseAgentConnectionName(decoded.name);
185
+ if (!connectionInstanceId) {
186
+ return deny("missing or invalid agent connection instance");
187
+ }
188
+ const claim = await claimEnrollmentConnection(deps.db, {
189
+ workspaceId: claims.workspaceId,
190
+ enrollmentId: claims.enrollmentId,
191
+ credentialGeneration: claims.credentialGeneration,
192
+ connectionInstanceId,
193
+ leaseMs: AGENT_CONNECTION_LEASE_MS
194
+ });
195
+ if (!claim.claimed) {
196
+ warnDenial(
197
+ deps,
198
+ `duplicate:${claims.workspaceId}:${claims.agentId}`,
199
+ "duplicate_runner",
200
+ "auth-callout: denied a duplicate live runner",
201
+ { workspaceId: claims.workspaceId, agentId: claims.agentId }
202
+ );
203
+ return deny("another runner instance currently owns this enrollment");
204
+ }
205
+ const permissions = workspaceAgentPermissions(
206
+ claims.workspaceId,
207
+ claims.agentId,
208
+ connectionInstanceId
209
+ );
125
210
  const nowSeconds = Math.floor(Date.now() / 1e3);
126
211
  const userJwt = mintUserJwt({
127
212
  userPublicKey: decoded.userNkey,
@@ -145,7 +230,8 @@ async function handleAuthorizationRequest(deps, requestBytes) {
145
230
  });
146
231
  deps.observability?.info?.("auth-callout: granted a workspace-scoped NATS credential", {
147
232
  workspaceId: claims.workspaceId,
148
- agentId: claims.agentId
233
+ agentId: claims.agentId,
234
+ connectionGeneration: claim.connectionGeneration
149
235
  });
150
236
  return Buffer.from(response, "utf8");
151
237
  }
@@ -157,7 +243,7 @@ async function startAuthCalloutResponder(deps, natsUrl) {
157
243
  (bytes) => handleAuthorizationRequest(deps, bytes),
158
244
  {
159
245
  name: "opengeni-auth-callout",
160
- ...deps.observability ? { logger: observabilityEventLogger(deps.observability) } : {}
246
+ ...deps.observability ? observabilityEventBusOptions(deps.observability) : {}
161
247
  }
162
248
  );
163
249
  deps.observability?.info?.("OpenGeni NATS auth-callout responder started", {
@@ -170,35 +256,132 @@ async function startAuthCalloutResponder(deps, natsUrl) {
170
256
  import {
171
257
  clearEnrollmentWentOffline,
172
258
  disconnectAttachedBrowserDevices,
259
+ advanceEnrollmentAgentUpdate,
173
260
  getEnrollment as getEnrollment2,
261
+ getLiveEnrollmentConnection,
174
262
  ingestMachineMetricsSample,
175
263
  reconcileAttachedBrowserInventory,
264
+ releaseEnrollmentConnection,
265
+ renewEnrollmentConnection,
176
266
  sessionsWithActiveOpOnEnrollment,
177
267
  setEnrollmentDisplayState,
178
- setEnrollmentOpStreamState,
179
- setEnrollmentWentOffline,
180
- touchEnrollmentLastSeen
268
+ setEnrollmentAgentRuntime,
269
+ setEnrollmentOpStreamState
181
270
  } from "@opengeni/db";
182
271
  import {
183
272
  AttachedBrowserInventorySnapshot as AttachedBrowserInventoryContract
184
273
  } from "@opengeni/contracts";
185
274
  import { appendAndPublishEvents } from "@opengeni/events";
275
+ import { normalizeConnectedMachineWorkspaceRoot } from "@opengeni/runtime/sandbox";
186
276
  import {
187
277
  AgentEvent,
278
+ AgentUpdateStage,
188
279
  Arch,
189
280
  GoingOfflineReason,
190
281
  Hello,
191
282
  Os,
192
283
  goingOfflineReasonToJSON
193
284
  } from "@opengeni/agent-proto";
194
- var AGENT_EVENTS_SUBJECT = "agent.*.*.events";
195
- var AGENT_HELLO_SUBJECT = "agent.*.*.hello";
285
+ var AGENT_EVENTS_SUBJECT = "agent.*.*.connection.*.events";
286
+ var AGENT_HELLO_SUBJECT = "agent.*.*.connection.*.hello";
287
+ function payloadIsHeartbeat(payload) {
288
+ try {
289
+ return AgentEvent.decode(payload).event?.$case === "heartbeat";
290
+ } catch {
291
+ return false;
292
+ }
293
+ }
294
+ function createAgentEventIngestionScheduler(handler, options = {}) {
295
+ const maxConcurrentSubjects = options.maxConcurrentSubjects ?? 32;
296
+ if (!Number.isSafeInteger(maxConcurrentSubjects) || maxConcurrentSubjects <= 0) {
297
+ throw new RangeError("maxConcurrentSubjects must be a positive safe integer");
298
+ }
299
+ const states = /* @__PURE__ */ new Map();
300
+ const readySubjects = [];
301
+ const idleWaiters = /* @__PURE__ */ new Set();
302
+ let accepting = true;
303
+ let activeSubjects = 0;
304
+ const resolveIdleWaiters = () => {
305
+ if (states.size !== 0) return;
306
+ for (const resolve of idleWaiters) resolve();
307
+ idleWaiters.clear();
308
+ };
309
+ async function drain(subject, state) {
310
+ while (true) {
311
+ const event = state.pending.shift();
312
+ if (!event) break;
313
+ try {
314
+ await handler(event.payload, event.subject);
315
+ } catch {
316
+ }
317
+ }
318
+ activeSubjects -= 1;
319
+ state.scheduled = false;
320
+ if (state.pending.length > 0) {
321
+ schedule(subject, state);
322
+ } else {
323
+ if (states.get(subject) === state) states.delete(subject);
324
+ resolveIdleWaiters();
325
+ }
326
+ pump();
327
+ }
328
+ function pump() {
329
+ while (activeSubjects < maxConcurrentSubjects) {
330
+ const next = readySubjects.shift();
331
+ if (!next) return;
332
+ if (states.get(next.subject) !== next.state || !next.state.scheduled) continue;
333
+ activeSubjects += 1;
334
+ void drain(next.subject, next.state);
335
+ }
336
+ }
337
+ function schedule(subject, state) {
338
+ if (state.scheduled) return;
339
+ state.scheduled = true;
340
+ readySubjects.push({ subject, state });
341
+ pump();
342
+ }
343
+ return {
344
+ enqueue(payload, subject) {
345
+ if (!accepting) return;
346
+ const heartbeat = payloadIsHeartbeat(payload);
347
+ let state = states.get(subject);
348
+ if (!state) {
349
+ state = { scheduled: false, pending: [] };
350
+ states.set(subject, state);
351
+ }
352
+ const queued = { payload, subject, heartbeat };
353
+ const lastIndex = state.pending.length - 1;
354
+ if (heartbeat && lastIndex >= 0 && state.pending[lastIndex].heartbeat) {
355
+ state.pending[lastIndex] = queued;
356
+ options.onHeartbeatCoalesced?.();
357
+ } else {
358
+ state.pending.push(queued);
359
+ }
360
+ schedule(subject, state);
361
+ },
362
+ async whenIdle() {
363
+ if (states.size === 0) return;
364
+ await new Promise((resolve) => idleWaiters.add(resolve));
365
+ },
366
+ close() {
367
+ accepting = false;
368
+ for (const state of states.values()) {
369
+ state.pending.length = 0;
370
+ }
371
+ resolveIdleWaiters();
372
+ }
373
+ };
374
+ }
196
375
  function parseAgentSubject(subject, tail) {
197
376
  const parts = subject.split(".");
198
- if (parts.length !== 4 || parts[0] !== "agent" || parts[3] !== tail) {
377
+ if (parts.length !== 6 || parts[0] !== "agent" || parts[3] !== "connection" || !parts[4] || parts[5] !== tail) {
199
378
  return null;
200
379
  }
201
- return { workspaceId: parts[1], agentId: parts[2] };
380
+ return {
381
+ workspaceId: parts[1],
382
+ agentId: parts[2],
383
+ connectionInstanceId: parts[4]
384
+ };
202
385
  }
203
386
  function parseAgentEventSubject(subject) {
204
387
  return parseAgentSubject(subject, "events");
@@ -294,19 +477,10 @@ async function ingestAttachedBrowserInventory(db, input) {
294
477
  snapshot: wireAttachedBrowserInventoryToContract(input.inventory)
295
478
  });
296
479
  }
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
- }
480
+ async function ingestHeartbeatMetrics(db, input) {
302
481
  const sample = wireSampleToDbSample(input.sample);
303
- await touchEnrollmentLastSeen(db, {
304
- accountId: enrollment.accountId,
305
- workspaceId: input.workspaceId,
306
- enrollmentId: input.agentId
307
- });
308
482
  const result = await ingestMachineMetricsSample(db, {
309
- accountId: enrollment.accountId,
483
+ accountId: input.accountId,
310
484
  workspaceId: input.workspaceId,
311
485
  enrollmentId: input.agentId,
312
486
  sample
@@ -356,17 +530,19 @@ async function handleAgentEventPayload(db, observability, payload, subject, bus)
356
530
  labels: { reason }
357
531
  });
358
532
  try {
359
- const enrollment = await getEnrollment2(db, ids.workspaceId, ids.agentId);
360
- if (enrollment) {
361
- await setEnrollmentWentOffline(db, {
362
- accountId: enrollment.accountId,
533
+ const enrollment2 = await getEnrollment2(db, ids.workspaceId, ids.agentId);
534
+ if (enrollment2) {
535
+ const released = await releaseEnrollmentConnection(db, {
536
+ accountId: enrollment2.accountId,
363
537
  workspaceId: ids.workspaceId,
364
538
  enrollmentId: ids.agentId,
539
+ connectionInstanceId: ids.connectionInstanceId,
365
540
  reason
366
541
  });
542
+ if (!released.released) return;
367
543
  try {
368
544
  await disconnectAttachedBrowserDevices(db, {
369
- accountId: enrollment.accountId,
545
+ accountId: enrollment2.accountId,
370
546
  workspaceId: ids.workspaceId,
371
547
  enrollmentId: ids.agentId
372
548
  });
@@ -408,10 +584,75 @@ async function handleAgentEventPayload(db, observability, payload, subject, bus)
408
584
  }
409
585
  return;
410
586
  }
587
+ if (event.event?.$case === "agentUpdateProgress") {
588
+ const enrollment2 = await getEnrollment2(db, ids.workspaceId, ids.agentId).catch(() => null);
589
+ if (!enrollment2 || enrollment2.connectionInstanceId !== ids.connectionInstanceId) return;
590
+ const progress = event.event.agentUpdateProgress;
591
+ const status = (() => {
592
+ switch (progress.stage) {
593
+ case AgentUpdateStage.AGENT_UPDATE_STAGE_ACCEPTED:
594
+ return "accepted";
595
+ case AgentUpdateStage.AGENT_UPDATE_STAGE_WAITING_FOR_IDLE:
596
+ return "waiting_for_idle";
597
+ case AgentUpdateStage.AGENT_UPDATE_STAGE_DOWNLOADING:
598
+ return "downloading";
599
+ case AgentUpdateStage.AGENT_UPDATE_STAGE_VERIFYING:
600
+ return "verifying";
601
+ case AgentUpdateStage.AGENT_UPDATE_STAGE_APPLYING:
602
+ return "applying";
603
+ case AgentUpdateStage.AGENT_UPDATE_STAGE_RESTARTING:
604
+ return "restarting";
605
+ case AgentUpdateStage.AGENT_UPDATE_STAGE_FAILED:
606
+ return "failed";
607
+ default:
608
+ return null;
609
+ }
610
+ })();
611
+ if (!status) return;
612
+ try {
613
+ await advanceEnrollmentAgentUpdate(db, {
614
+ accountId: enrollment2.accountId,
615
+ workspaceId: ids.workspaceId,
616
+ enrollmentId: ids.agentId,
617
+ connectionInstanceId: ids.connectionInstanceId,
618
+ connectionGeneration: enrollment2.connectionGeneration,
619
+ operationId: progress.operationId,
620
+ status,
621
+ expectedBinarySha256: /^[0-9a-f]{64}$/.test(progress.expectedBinarySha256) ? progress.expectedBinarySha256 : null,
622
+ errorCode: progress.errorCode || null,
623
+ retryable: progress.retryable,
624
+ rolledBack: progress.rolledBack
625
+ });
626
+ } catch (error) {
627
+ observability?.warn?.("Failed to ingest a machine self-update progress event", {
628
+ subject,
629
+ error: error instanceof Error ? error.message : String(error)
630
+ });
631
+ }
632
+ return;
633
+ }
411
634
  if (event.event?.$case !== "heartbeat") {
412
635
  return;
413
636
  }
414
637
  const heartbeat = event.event.heartbeat;
638
+ const enrollment = await getEnrollment2(db, ids.workspaceId, ids.agentId).catch(() => null);
639
+ if (!enrollment || enrollment.connectionInstanceId !== ids.connectionInstanceId) return;
640
+ try {
641
+ const renewed = await renewEnrollmentConnection(db, {
642
+ accountId: enrollment.accountId,
643
+ workspaceId: ids.workspaceId,
644
+ enrollmentId: ids.agentId,
645
+ connectionInstanceId: ids.connectionInstanceId,
646
+ leaseMs: AGENT_CONNECTION_LEASE_MS
647
+ });
648
+ if (!renewed.renewed) return;
649
+ } catch (error) {
650
+ observability?.warn?.("Failed to renew a machine connection heartbeat", {
651
+ subject,
652
+ error: error instanceof Error ? error.message : String(error)
653
+ });
654
+ return;
655
+ }
415
656
  if (heartbeat.attachedBrowserInventory) {
416
657
  try {
417
658
  await ingestAttachedBrowserInventory(db, {
@@ -426,10 +667,33 @@ async function handleAgentEventPayload(db, observability, payload, subject, bus)
426
667
  });
427
668
  }
428
669
  }
670
+ if (heartbeat.desktopStatus) {
671
+ try {
672
+ const status = heartbeat.desktopStatus;
673
+ const available = status.available && !status.unavailableReason;
674
+ const reason = status.unavailableReason || null;
675
+ if (enrollment.hasDisplay !== available || (enrollment.desktopUnavailableReason ?? null) !== reason || enrollment.agentCapabilities?.desktop !== available) {
676
+ await refreshEnrollmentDisplay(db, {
677
+ workspaceId: ids.workspaceId,
678
+ agentId: ids.agentId,
679
+ hasDisplay: available,
680
+ desktopUnavailableReason: reason,
681
+ runtimeDesktop: available,
682
+ connectionInstanceId: ids.connectionInstanceId
683
+ });
684
+ }
685
+ } catch (error) {
686
+ observability?.warn?.("Failed to refresh a machine desktop heartbeat", {
687
+ subject,
688
+ error: error instanceof Error ? error.message : String(error)
689
+ });
690
+ }
691
+ }
429
692
  const metrics = heartbeat.metrics;
430
693
  if (!metrics) return;
431
694
  try {
432
- await ingestHeartbeat(db, {
695
+ await ingestHeartbeatMetrics(db, {
696
+ accountId: enrollment.accountId,
433
697
  workspaceId: ids.workspaceId,
434
698
  agentId: ids.agentId,
435
699
  sample: metrics
@@ -442,10 +706,22 @@ async function handleAgentEventPayload(db, observability, payload, subject, bus)
442
706
  }
443
707
  }
444
708
  function startMetricsIngestion(deps) {
445
- return deps.bus.subscribeAgentEvents(
446
- AGENT_EVENTS_SUBJECT,
447
- (payload, subject) => handleAgentEventPayload(deps.db, deps.observability, payload, subject, deps.bus)
709
+ const scheduler = createAgentEventIngestionScheduler(
710
+ (payload, subject) => handleAgentEventPayload(deps.db, deps.observability, payload, subject, deps.bus),
711
+ {
712
+ onHeartbeatCoalesced: () => deps.observability?.incrementCounter({
713
+ name: "opengeni_machine_heartbeat_coalesced_total",
714
+ help: "Connected Machine heartbeats collapsed while an exact runner event queue was busy."
715
+ })
716
+ }
448
717
  );
718
+ const unsubscribe = deps.bus.subscribeAgentEvents(AGENT_EVENTS_SUBJECT, (payload, subject) => {
719
+ scheduler.enqueue(payload, subject);
720
+ });
721
+ return () => {
722
+ unsubscribe();
723
+ scheduler.close();
724
+ };
449
725
  }
450
726
  function helloReportsDisplay(hello) {
451
727
  const caps = hello.capabilities;
@@ -464,13 +740,41 @@ function helloDesktopUnavailableReason(hello) {
464
740
  function helloReportsOpStream(hello) {
465
741
  return hello.capabilities?.opStream === true;
466
742
  }
743
+ function helloRuntimeCapabilities(hello) {
744
+ const caps = hello.capabilities;
745
+ if (!caps) return {};
746
+ return {
747
+ exec: caps.exec === true,
748
+ filesystem: caps.filesystem === true,
749
+ git: caps.git === true,
750
+ pty: caps.pty === true,
751
+ desktop: caps.desktop === true,
752
+ opStream: caps.opStream === true,
753
+ browserBridge: caps.browserBridge === true,
754
+ operationResourcePolicy: caps.operationResourcePolicy === true,
755
+ operationCpuQuota: caps.operationCpuQuota === true,
756
+ transactionalFsWrite: caps.transactionalFsWrite === true
757
+ };
758
+ }
759
+ function helloCompletedUpdate(hello) {
760
+ 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(
761
+ hello.completedUpdateOperationId
762
+ ) || !/^(?:v)?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(hello.completedUpdateTargetVersion) || !/^[0-9a-f]{64}$/.test(hello.completedUpdateBinarySha256)) {
763
+ return null;
764
+ }
765
+ return {
766
+ operationId: hello.completedUpdateOperationId,
767
+ targetVersion: hello.completedUpdateTargetVersion,
768
+ binarySha256: hello.completedUpdateBinarySha256
769
+ };
770
+ }
467
771
  async function refreshEnrollmentDisplay(db, input) {
468
772
  const desktopUnavailableReason = input.desktopUnavailableReason ?? null;
469
773
  const enrollment = await getEnrollment2(db, input.workspaceId, input.agentId);
470
774
  if (!enrollment) {
471
775
  return { updated: false };
472
776
  }
473
- if (enrollment.hasDisplay === input.hasDisplay && (enrollment.desktopUnavailableReason ?? null) === desktopUnavailableReason) {
777
+ if (enrollment.hasDisplay === input.hasDisplay && (enrollment.desktopUnavailableReason ?? null) === desktopUnavailableReason && (input.runtimeDesktop === void 0 || enrollment.agentCapabilities?.desktop === input.runtimeDesktop)) {
474
778
  return { updated: false };
475
779
  }
476
780
  return await setEnrollmentDisplayState(db, {
@@ -478,7 +782,9 @@ async function refreshEnrollmentDisplay(db, input) {
478
782
  workspaceId: input.workspaceId,
479
783
  enrollmentId: input.agentId,
480
784
  hasDisplay: input.hasDisplay,
481
- desktopUnavailableReason
785
+ desktopUnavailableReason,
786
+ ...input.runtimeDesktop !== void 0 ? { runtimeDesktop: input.runtimeDesktop } : {},
787
+ ...input.connectionInstanceId ? { connectionInstanceId: input.connectionInstanceId } : {}
482
788
  });
483
789
  }
484
790
  async function refreshEnrollmentOpStream(db, input) {
@@ -493,7 +799,8 @@ async function refreshEnrollmentOpStream(db, input) {
493
799
  accountId: enrollment.accountId,
494
800
  workspaceId: input.workspaceId,
495
801
  enrollmentId: input.agentId,
496
- opStream: input.opStream
802
+ opStream: input.opStream,
803
+ ...input.connectionInstanceId ? { connectionInstanceId: input.connectionInstanceId } : {}
497
804
  });
498
805
  }
499
806
  async function handleHelloPayload(db, observability, payload, subject, bus) {
@@ -511,17 +818,53 @@ async function handleHelloPayload(db, observability, payload, subject, bus) {
511
818
  });
512
819
  return;
513
820
  }
821
+ const authority = await getLiveEnrollmentConnection(db, ids.workspaceId, ids.agentId).catch(
822
+ () => null
823
+ );
824
+ if (!authority || authority.connectionInstanceId !== ids.connectionInstanceId) {
825
+ observability?.warn?.("Ignored a Hello from a superseded runner instance", {
826
+ workspaceId: ids.workspaceId,
827
+ agentId: ids.agentId
828
+ });
829
+ return;
830
+ }
831
+ let workspaceRoot;
832
+ if (hello.workspaceRoot.length > 0) {
833
+ try {
834
+ workspaceRoot = normalizeConnectedMachineWorkspaceRoot(hello.workspaceRoot, authority.os);
835
+ } catch (error) {
836
+ observability?.warn?.("Ignored an invalid Connected Machine workspace root", {
837
+ workspaceId: ids.workspaceId,
838
+ agentId: ids.agentId,
839
+ error: error instanceof Error ? error.message : String(error)
840
+ });
841
+ }
842
+ }
514
843
  try {
515
844
  await refreshEnrollmentDisplay(db, {
516
845
  workspaceId: ids.workspaceId,
517
846
  agentId: ids.agentId,
518
847
  hasDisplay: helloReportsDisplay(hello),
519
- desktopUnavailableReason: helloDesktopUnavailableReason(hello)
848
+ desktopUnavailableReason: helloDesktopUnavailableReason(hello),
849
+ connectionInstanceId: ids.connectionInstanceId
520
850
  });
521
851
  await refreshEnrollmentOpStream(db, {
522
852
  workspaceId: ids.workspaceId,
523
853
  agentId: ids.agentId,
524
- opStream: helloReportsOpStream(hello)
854
+ opStream: helloReportsOpStream(hello),
855
+ connectionInstanceId: ids.connectionInstanceId
856
+ });
857
+ await setEnrollmentAgentRuntime(db, {
858
+ accountId: authority.accountId,
859
+ workspaceId: ids.workspaceId,
860
+ enrollmentId: ids.agentId,
861
+ connectionInstanceId: ids.connectionInstanceId,
862
+ agentVersion: hello.agentVersion.trim() || null,
863
+ binarySha256: /^[0-9a-f]{64}$/.test(hello.binarySha256) ? hello.binarySha256 : null,
864
+ updateChannel: hello.updateChannel === "stable" || hello.updateChannel === "beta" ? hello.updateChannel : null,
865
+ capabilities: helloRuntimeCapabilities(hello),
866
+ ...workspaceRoot !== void 0 ? { workspaceRoot } : {},
867
+ completedUpdate: helloCompletedUpdate(hello)
525
868
  });
526
869
  } catch (error) {
527
870
  observability?.warn?.("Failed to refresh an enrollment's capabilities from a Hello", {
@@ -530,23 +873,21 @@ async function handleHelloPayload(db, observability, payload, subject, bus) {
530
873
  });
531
874
  }
532
875
  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
- }
876
+ const { cleared } = await clearEnrollmentWentOffline(db, {
877
+ accountId: authority.accountId,
878
+ workspaceId: ids.workspaceId,
879
+ enrollmentId: ids.agentId,
880
+ connectionInstanceId: ids.connectionInstanceId
881
+ });
882
+ if (cleared && bus) {
883
+ await fanOutMachineLinkEvents(
884
+ db,
885
+ bus,
886
+ observability,
887
+ ids.workspaceId,
888
+ ids.agentId,
889
+ (activeTurnId) => [{ type: "machine.link.restored", turnId: activeTurnId, payload: {} }]
890
+ );
550
891
  }
551
892
  } catch (error) {
552
893
  observability?.warn?.("Failed to clear a machine going-offline marker on a Hello", {
@@ -733,6 +1074,195 @@ function escapeSlackText(value) {
733
1074
  return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
734
1075
  }
735
1076
 
1077
+ // src/temporal-schedule-sync.ts
1078
+ import { getScheduledTaskIncludingDeleted } from "@opengeni/db";
1079
+ var LOCK_TIMEOUT = "5s";
1080
+ var WRITE_TIMEOUT_MS = 5e3;
1081
+ function createTemporalScheduleSynchronizer(deps) {
1082
+ const write = async (temporalScheduleId, work) => deps.db.transaction(async (tx) => {
1083
+ await tx.execute(sql`select set_config('lock_timeout', ${LOCK_TIMEOUT}, true)`);
1084
+ await tx.execute(sql`select pg_advisory_xact_lock(
1085
+ hashtextextended(${`scheduled-task-temporal:${temporalScheduleId}`}, 0)
1086
+ )`);
1087
+ return await work(tx);
1088
+ });
1089
+ return {
1090
+ async sync(task, onFailure) {
1091
+ const result = await write(task.temporalScheduleId, async (tx) => {
1092
+ try {
1093
+ await deps.withDeadline(Date.now() + WRITE_TIMEOUT_MS, async () => {
1094
+ const current = await getScheduledTaskIncludingDeleted(tx, task.workspaceId, task.id);
1095
+ if (current && current.temporalScheduleId !== task.temporalScheduleId)
1096
+ throw new Error("Scheduled task Temporal identity changed");
1097
+ if (!current || current.deletedAt !== null || current.schedule.type === "manual") {
1098
+ await deps.remove(task.temporalScheduleId);
1099
+ return;
1100
+ }
1101
+ const { deletedAt: _deletedAt, ...latest } = current;
1102
+ await deps.upsert(latest);
1103
+ });
1104
+ return { ok: true };
1105
+ } catch (error) {
1106
+ return { ok: false, error: onFailure ? await onFailure(tx, error) : error };
1107
+ }
1108
+ });
1109
+ if (!result.ok) throw result.error;
1110
+ },
1111
+ async remove(temporalScheduleId) {
1112
+ await write(
1113
+ temporalScheduleId,
1114
+ async () => deps.withDeadline(Date.now() + WRITE_TIMEOUT_MS, () => deps.remove(temporalScheduleId))
1115
+ );
1116
+ }
1117
+ };
1118
+ }
1119
+
1120
+ // src/workspace-webhook-dispatch.ts
1121
+ import { randomUUID } from "crypto";
1122
+ import { environmentsEncryptionKeyBytes } from "@opengeni/config";
1123
+ import {
1124
+ OPENGENI_DELIVERY_ID_HEADER,
1125
+ OPENGENI_EVENT_ID_HEADER,
1126
+ OPENGENI_SIGNATURE_HEADER,
1127
+ signOpenGeniPayload
1128
+ } from "@opengeni/contracts";
1129
+ import {
1130
+ claimWorkspaceWebhookDeliveries,
1131
+ claimOrganizationWebhookDeliveries,
1132
+ decryptEnvironmentValue,
1133
+ maintainWorkspaceAllowances,
1134
+ pruneOrganizationWebhookDeliveries,
1135
+ pruneWorkspaceWebhookDeliveries,
1136
+ settleOrganizationWebhookDelivery,
1137
+ settleWorkspaceWebhookDelivery
1138
+ } from "@opengeni/db";
1139
+ import { pinnedFetch } from "@opengeni/network";
1140
+ var WORKSPACE_WEBHOOK_DISPATCH_BATCH_SIZE = 32;
1141
+ var CLAIM_SECONDS = 60;
1142
+ var POLL_INTERVAL_MS = 2e3;
1143
+ var DELIVERY_TIMEOUT_MS = 1e4;
1144
+ var PRUNE_EVERY_TICKS = 300;
1145
+ function errorText(error) {
1146
+ if (error instanceof Error) {
1147
+ if (error.name === "TimeoutError" || error.name === "AbortError") return "timeout";
1148
+ return `${error.name}: ${error.message}`.slice(0, 500);
1149
+ }
1150
+ return String(error).slice(0, 500);
1151
+ }
1152
+ async function deliverOne(deps, key, claimId, delivery, organization = false) {
1153
+ let status = null;
1154
+ let error = null;
1155
+ try {
1156
+ const body = JSON.stringify(delivery.payload);
1157
+ const secret = decryptEnvironmentValue(key, delivery.secretEncrypted);
1158
+ const response = await (deps.fetch ?? pinnedFetch)(
1159
+ delivery.url,
1160
+ {
1161
+ method: "POST",
1162
+ headers: {
1163
+ "content-type": "application/json",
1164
+ "user-agent": "OpenGeni-Webhooks/1",
1165
+ [OPENGENI_SIGNATURE_HEADER]: await signOpenGeniPayload(secret, body),
1166
+ [OPENGENI_EVENT_ID_HEADER]: delivery.eventId,
1167
+ [OPENGENI_DELIVERY_ID_HEADER]: delivery.deliveryId
1168
+ },
1169
+ body,
1170
+ signal: AbortSignal.timeout(DELIVERY_TIMEOUT_MS)
1171
+ },
1172
+ deps.settings,
1173
+ {
1174
+ label: organization ? "Organization webhook" : "Workspace webhook",
1175
+ requireHttpsOutsideLocalTest: true
1176
+ }
1177
+ );
1178
+ status = response.status;
1179
+ await response.body?.cancel().catch(() => void 0);
1180
+ if (status < 200 || status >= 300) error = `HTTP ${status}`;
1181
+ } catch (caught) {
1182
+ error = errorText(caught);
1183
+ }
1184
+ await (organization ? settleOrganizationWebhookDelivery : settleWorkspaceWebhookDelivery)(
1185
+ deps.db,
1186
+ {
1187
+ deliveryId: delivery.deliveryId,
1188
+ claimId,
1189
+ status,
1190
+ error
1191
+ }
1192
+ );
1193
+ return error === null ? "delivered" : "failed";
1194
+ }
1195
+ async function drainWorkspaceWebhookDeliveries(deps) {
1196
+ await maintainWorkspaceAllowances(deps.db);
1197
+ const key = environmentsEncryptionKeyBytes(deps.settings);
1198
+ if (!key) return { claimed: 0, delivered: 0, failed: 0 };
1199
+ const claimId = randomUUID();
1200
+ const claims = await claimWorkspaceWebhookDeliveries(deps.db, {
1201
+ claimId,
1202
+ limit: WORKSPACE_WEBHOOK_DISPATCH_BATCH_SIZE,
1203
+ claimSeconds: CLAIM_SECONDS
1204
+ });
1205
+ const organizationClaims = await claimOrganizationWebhookDeliveries(deps.db, {
1206
+ claimId,
1207
+ limit: WORKSPACE_WEBHOOK_DISPATCH_BATCH_SIZE,
1208
+ claimSeconds: CLAIM_SECONDS
1209
+ });
1210
+ const outcomes = await Promise.all(
1211
+ [
1212
+ ...claims.map((delivery) => ({ delivery, organization: false })),
1213
+ ...organizationClaims.map((delivery) => ({ delivery, organization: true }))
1214
+ ].map(async ({ delivery, organization }) => {
1215
+ try {
1216
+ return await deliverOne(deps, key, claimId, delivery, organization);
1217
+ } catch (error) {
1218
+ deps.observability?.warn("Workspace webhook settlement failed", {
1219
+ deliveryId: delivery.deliveryId,
1220
+ error: errorText(error)
1221
+ });
1222
+ return "failed";
1223
+ }
1224
+ })
1225
+ );
1226
+ return {
1227
+ claimed: claims.length + organizationClaims.length,
1228
+ delivered: outcomes.filter((outcome) => outcome === "delivered").length,
1229
+ failed: outcomes.filter((outcome) => outcome === "failed").length
1230
+ };
1231
+ }
1232
+ function startWorkspaceWebhookDispatchPump(deps, options = {}) {
1233
+ const intervalMs = Math.max(100, options.intervalMs ?? POLL_INTERVAL_MS);
1234
+ const drain = options.drain ?? drainWorkspaceWebhookDeliveries;
1235
+ let stopped = false;
1236
+ let running;
1237
+ let ticks = 0;
1238
+ const tick = () => {
1239
+ if (stopped || running) return;
1240
+ ticks += 1;
1241
+ let backlog = false;
1242
+ running = (async () => {
1243
+ const result = await drain(deps);
1244
+ backlog = result.claimed >= WORKSPACE_WEBHOOK_DISPATCH_BATCH_SIZE;
1245
+ if (result.claimed > 0) deps.observability?.info("Workspace webhook batch settled", result);
1246
+ if (ticks % PRUNE_EVERY_TICKS === 0) {
1247
+ await pruneWorkspaceWebhookDeliveries(deps.db);
1248
+ await pruneOrganizationWebhookDeliveries(deps.db);
1249
+ }
1250
+ })().catch((error) => {
1251
+ deps.observability?.error("Workspace webhook dispatch failed", { error: errorText(error) });
1252
+ }).finally(() => {
1253
+ running = void 0;
1254
+ if (backlog && !stopped) queueMicrotask(tick);
1255
+ });
1256
+ };
1257
+ const timer = setInterval(tick, intervalMs);
1258
+ queueMicrotask(tick);
1259
+ return async () => {
1260
+ stopped = true;
1261
+ clearInterval(timer);
1262
+ await running;
1263
+ };
1264
+ }
1265
+
736
1266
  // src/editable-artifact-websocket.ts
737
1267
  import {
738
1268
  EditableArtifactDomainError,
@@ -741,13 +1271,14 @@ import {
741
1271
  encodeEditableArtifactLiveServerWireFrame
742
1272
  } from "@opengeni/core";
743
1273
  var EDITABLE_ARTIFACT_LIVE_WEBSOCKET_PATH = "/v1/editable-artifacts/live";
744
- var EDITABLE_ARTIFACT_LIVE_WEBSOCKET_PROTOCOL = "opengeni-artifact-v1";
1274
+ var EDITABLE_ARTIFACT_LIVE_WEBSOCKET_PROTOCOL = "opengeni-artifact-v2";
745
1275
  var EDITABLE_ARTIFACT_LIVE_WEBSOCKET_MAX_MESSAGE_BYTES = 8 * 1024 * 1024 + 64 * 1024;
746
1276
  var MAX_QUEUED_MESSAGES = 64;
747
1277
  var MAX_QUEUED_BYTES = 12 * 1024 * 1024;
748
1278
  var EditableArtifactWebSocketTransport = class {
749
- constructor(application) {
1279
+ constructor(application, authorizeSourceSession) {
750
1280
  this.application = application;
1281
+ this.authorizeSourceSession = authorizeSourceSession;
751
1282
  this.websocket = Object.freeze({
752
1283
  open: (socket) => socket.data.attach(socket),
753
1284
  message: (socket, message) => socket.data.receive(message),
@@ -771,7 +1302,10 @@ var EditableArtifactWebSocketTransport = class {
771
1302
  if (!offeredProtocol(request.headers.get("sec-websocket-protocol"))) {
772
1303
  return new Response("WebSocket protocol required", { status: 426 });
773
1304
  }
774
- const connection = new EditableArtifactWebSocketConnection(this.application);
1305
+ const connection = new EditableArtifactWebSocketConnection(
1306
+ this.application,
1307
+ this.authorizeSourceSession
1308
+ );
775
1309
  const upgraded = server.upgrade(request, {
776
1310
  data: connection,
777
1311
  headers: {
@@ -782,8 +1316,9 @@ var EditableArtifactWebSocketTransport = class {
782
1316
  }
783
1317
  };
784
1318
  var EditableArtifactWebSocketConnection = class {
785
- constructor(application) {
1319
+ constructor(application, authorizeSourceSession) {
786
1320
  this.application = application;
1321
+ this.authorizeSourceSession = authorizeSourceSession;
787
1322
  }
788
1323
  socket = null;
789
1324
  session = null;
@@ -869,7 +1404,8 @@ var EditableArtifactWebSocketConnection = class {
869
1404
  artifactId: frame.artifactId,
870
1405
  protocolVersion: frame.protocolVersion,
871
1406
  resume: frame.resume,
872
- sink: this
1407
+ sink: this,
1408
+ ...this.authorizeSourceSession ? { authorizeSourceSession: this.authorizeSourceSession } : {}
873
1409
  });
874
1410
  if (this.terminal) {
875
1411
  await session.close("transport_error");
@@ -976,14 +1512,121 @@ function closeCode(reason) {
976
1512
  return 4400;
977
1513
  }
978
1514
 
1515
+ // src/editable-artifact-source-session.ts
1516
+ import {
1517
+ hasPermission,
1518
+ requireExternalContinuationAuthority,
1519
+ requireSessionAuthorization
1520
+ } from "@opengeni/core";
1521
+ import {
1522
+ findActiveWorkspaceApiKeyById,
1523
+ getSession,
1524
+ getWorkspaceGrant,
1525
+ hasEditableArtifactSessionLink,
1526
+ lockExternalWorkspaceMembershipLifecycle,
1527
+ withRlsContext
1528
+ } from "@opengeni/db";
1529
+ function editableArtifactSourceSessionAuthorizer(deps) {
1530
+ return async (ticket, permission, transaction, signal) => {
1531
+ signal?.throwIfAborted();
1532
+ const authority = ticket.sourceSessionAuthority;
1533
+ if (!authority) return true;
1534
+ const { grant } = authority;
1535
+ const required = permission === "edit" ? "artifacts:publish" : "artifacts:read";
1536
+ if (!hasPermission(grant.permissions, required) || !hasPermission(grant.permissions, "sessions:read")) {
1537
+ return false;
1538
+ }
1539
+ if (grant.metadata?.externalActor && !authority.externalContinuation) return false;
1540
+ try {
1541
+ const scopedDeps = transaction ? { ...deps, db: transaction } : deps;
1542
+ if (transaction) {
1543
+ await lockExternalWorkspaceMembershipLifecycle(transaction, ticket.scope.accountId);
1544
+ signal?.throwIfAborted();
1545
+ await transaction.execute(sql`select pg_advisory_xact_lock_shared(
1546
+ hashtextextended(${`session-tenancy:${ticket.scope.workspaceId}`}, 0))`);
1547
+ signal?.throwIfAborted();
1548
+ }
1549
+ return await withAccessGrantSessionRlsContext(scopedDeps, grant, async () => {
1550
+ return await withRlsContext(scopedDeps.db, ticket.scope, async (tx) => {
1551
+ if (authority.externalContinuation) {
1552
+ await requireExternalContinuationAuthority(
1553
+ tx,
1554
+ authority.externalContinuation,
1555
+ { ...ticket.scope, subjectId: grant.subjectId },
1556
+ ["sessions:read", required]
1557
+ );
1558
+ signal?.throwIfAborted();
1559
+ } else if (transaction) {
1560
+ const actor = ticket.actor;
1561
+ if (actor.kind === "human" || actor.kind === "service" && actor.service === "configured_key") {
1562
+ await tx.execute(sql`select subject_id from workspace_memberships
1563
+ where account_id = ${ticket.scope.accountId}::uuid
1564
+ and workspace_id = ${ticket.scope.workspaceId}::uuid
1565
+ and subject_id = ${grant.subjectId} for share`);
1566
+ const live = await getWorkspaceGrant(tx, grant.subjectId, ticket.scope.workspaceId);
1567
+ if (!live || !hasPermission(live.permissions, "sessions:read")) return false;
1568
+ } else if (actor.kind === "service" && actor.service === "api_key") {
1569
+ await tx.execute(sql`select id from api_keys
1570
+ where account_id = ${ticket.scope.accountId}::uuid
1571
+ and id::text = ${grant.subjectId.slice("api_key:".length)}
1572
+ for share`);
1573
+ const live = await findActiveWorkspaceApiKeyById(tx, {
1574
+ ...ticket.scope,
1575
+ apiKeyId: grant.subjectId.slice("api_key:".length)
1576
+ });
1577
+ if (!live || !hasPermission(live.permissions, "sessions:read")) return false;
1578
+ } else if (actor.kind === "agent") {
1579
+ const live = await getSession(tx, ticket.scope.workspaceId, actor.sessionId);
1580
+ if (!live || live.firstPartyMcpPermissions !== null && !hasPermission(live.firstPartyMcpPermissions, "sessions:read")) {
1581
+ return false;
1582
+ }
1583
+ } else {
1584
+ return false;
1585
+ }
1586
+ signal?.throwIfAborted();
1587
+ }
1588
+ const linked = await hasEditableArtifactSessionLink(
1589
+ tx,
1590
+ ticket.scope,
1591
+ authority.sessionId,
1592
+ ticket.artifactId,
1593
+ { lockForCommit: transaction !== void 0 }
1594
+ );
1595
+ signal?.throwIfAborted();
1596
+ if (!linked) return false;
1597
+ await requireSessionAuthorization({ ...scopedDeps, db: tx }, grant, {
1598
+ sessionId: authority.sessionId,
1599
+ operation: "session.read",
1600
+ surface: "http"
1601
+ });
1602
+ signal?.throwIfAborted();
1603
+ return true;
1604
+ });
1605
+ });
1606
+ } catch {
1607
+ return false;
1608
+ }
1609
+ };
1610
+ }
1611
+
1612
+ // src/http/websocket-upgrade-dispatch.ts
1613
+ function dispatchApiWebSocketUpgrade(request, server, routes, localBrowserBoundary) {
1614
+ const route = routes.find((candidate) => candidate.handles(request));
1615
+ if (!route) return { handled: false };
1616
+ const rejection = localBrowserBoundary?.rejection(request);
1617
+ if (rejection) return { handled: true, response: localBrowserBoundaryResponse(rejection) };
1618
+ return { handled: true, response: route.upgrade(request, server) };
1619
+ }
1620
+
979
1621
  // src/editable-artifact-production.ts
980
- import { createHash as createHash4, randomUUID } from "crypto";
1622
+ import { createHash as createHash5, randomUUID as randomUUID2 } from "crypto";
981
1623
  import {
982
1624
  editableArtifactMaterializerCapabilitiesForRuntime
983
1625
  } from "@opengeni/artifact-tool/runtime/materializer";
984
1626
  import {
985
1627
  EditableArtifactDurableExportService,
986
1628
  EditableArtifactAgentApplication,
1629
+ EDITABLE_ARTIFACT_EXPORT_FORMATS,
987
1630
  EDITABLE_ARTIFACT_EXPORT_MAX_DOWNLOAD_BYTES,
988
1631
  EditableArtifactCompactionPipeline,
989
1632
  EditableArtifactGenesisPipeline,
@@ -999,7 +1642,6 @@ import {
999
1642
  editableArtifactId
1000
1643
  } from "@opengeni/core/editable-artifacts";
1001
1644
  import {
1002
- EDITABLE_ARTIFACT_LIVE_PROTOCOL_VERSION,
1003
1645
  EditableArtifactApplication,
1004
1646
  EditableArtifactCompatibilityError,
1005
1647
  EditableArtifactLiveServer,
@@ -1007,14 +1649,16 @@ import {
1007
1649
  SystemEditableArtifactLiveScheduler,
1008
1650
  WebCryptoEditableArtifactLiveTokens
1009
1651
  } from "@opengeni/core/editable-artifact-live";
1652
+ import { currentEditableArtifactCompatibility } from "@opengeni/contracts/editable-artifacts";
1010
1653
  import {
1011
1654
  PostgresEditableArtifactLiveReadStore,
1012
1655
  PostgresEditableArtifactLiveTicketStore,
1013
1656
  PostgresEditableArtifactStore,
1014
1657
  listEditableArtifactIdsForSession,
1015
1658
  touchEditableArtifactSessionLink,
1659
+ recordNativeDocumentInspection,
1016
1660
  dbSql,
1017
- withRlsContext
1661
+ withRlsContext as withRlsContext2
1018
1662
  } from "@opengeni/db";
1019
1663
  import { PostgresEditableArtifactDurableExportStore } from "@opengeni/db/editable-artifact-durable-export";
1020
1664
  import {
@@ -1078,7 +1722,7 @@ async function requireFlushBarrier(connection) {
1078
1722
  }
1079
1723
 
1080
1724
  // src/editable-artifact-native-kernel.ts
1081
- import { createHash } from "crypto";
1725
+ import { createHash as createHash2 } from "crypto";
1082
1726
  import { pathToFileURL } from "url";
1083
1727
  import {
1084
1728
  createNativeArtifactSession,
@@ -1093,6 +1737,13 @@ import {
1093
1737
  } from "@opengeni/contracts/editable-artifact-causal-frontier";
1094
1738
  import { decodeEditableArtifactSerializedCommit } from "@opengeni/contracts/editable-artifact-serialized-commit";
1095
1739
  import { decodeEditableArtifactMutationIntent } from "@opengeni/contracts/editable-artifacts";
1740
+ import {
1741
+ COMMITTED_TRANSACTION_PROTOCOL_VERSION,
1742
+ DOCUMENT_ARTIFACT_MODEL_SCHEMA_VERSION,
1743
+ PRESENTATION_ARTIFACT_MODEL_SCHEMA_VERSION,
1744
+ SPREADSHEET_ARTIFACT_MODEL_SCHEMA_VERSION,
1745
+ SPREADSHEET_COLLABORATION_SNAPSHOT_VERSION
1746
+ } from "@opengeni/contracts/editable-artifact-versions";
1096
1747
  import {
1097
1748
  MAX_EDITABLE_ARTIFACT_SNAPSHOT_BYTES,
1098
1749
  causalFrontiersEqual,
@@ -1101,9 +1752,6 @@ import {
1101
1752
  editableArtifactReplicaId,
1102
1753
  editableArtifactStateHash
1103
1754
  } from "@opengeni/core";
1104
- var MODEL_SCHEMA_VERSION = 1;
1105
- var OPERATION_PROTOCOL_VERSION = 1;
1106
- var CRDT_STATE_VERSION = 1;
1107
1755
  var SNAPSHOT_CHUNK_BYTES = 1024 * 1024;
1108
1756
  async function loadVerifiedNativeArtifactRuntimeBinding() {
1109
1757
  const location = await doctorConfiguredArtifactRuntime();
@@ -1136,7 +1784,7 @@ var NativeEditableArtifactKernelAdapter = class {
1136
1784
  modality: "spreadsheet",
1137
1785
  committedTransactionBytes,
1138
1786
  kernelVersion: this.runtime.buildIdentity,
1139
- modelSchemaVersion: MODEL_SCHEMA_VERSION
1787
+ modelSchemaVersion: SPREADSHEET_ARTIFACT_MODEL_SCHEMA_VERSION
1140
1788
  });
1141
1789
  }
1142
1790
  if (session.modality !== request.modality) {
@@ -1148,7 +1796,7 @@ var NativeEditableArtifactKernelAdapter = class {
1148
1796
  nativeReceiptBytes,
1149
1797
  resultingStateHash: editableArtifactStateHash(session.stateHash()),
1150
1798
  kernelVersion: this.runtime.buildIdentity,
1151
- modelSchemaVersion: MODEL_SCHEMA_VERSION
1799
+ modelSchemaVersion: modelSchemaVersionFor(request.modality)
1152
1800
  });
1153
1801
  } finally {
1154
1802
  session.dispose();
@@ -1198,11 +1846,11 @@ var NativeEditableArtifactKernelAdapter = class {
1198
1846
  canonicalChunks: fixedChunks(snapshot, input.signal),
1199
1847
  canonicalByteSize: snapshot.byteLength,
1200
1848
  canonicalContentHash: editableArtifactContentHash(
1201
- `sha256:${createHash("sha256").update(snapshot).digest("hex")}`
1849
+ `sha256:${createHash2("sha256").update(snapshot).digest("hex")}`
1202
1850
  ),
1203
1851
  stateHash: editableArtifactStateHash(session.stateHash()),
1204
1852
  coveredHeadSequence: 0,
1205
- modelSchemaVersion: MODEL_SCHEMA_VERSION,
1853
+ modelSchemaVersion: modelSchemaVersionFor(input.modality),
1206
1854
  kernelVersion: this.runtime.buildIdentity
1207
1855
  };
1208
1856
  if (session.modality === "spreadsheet") {
@@ -1210,8 +1858,8 @@ var NativeEditableArtifactKernelAdapter = class {
1210
1858
  ...common,
1211
1859
  modality: "spreadsheet",
1212
1860
  coveredCausalFrontier: domainFrontier(session.frontier()),
1213
- operationProtocolVersion: OPERATION_PROTOCOL_VERSION,
1214
- crdtStateVersion: CRDT_STATE_VERSION
1861
+ operationProtocolVersion: COMMITTED_TRANSACTION_PROTOCOL_VERSION,
1862
+ crdtStateVersion: SPREADSHEET_COLLABORATION_SNAPSHOT_VERSION
1215
1863
  });
1216
1864
  }
1217
1865
  return Object.freeze({
@@ -1245,11 +1893,11 @@ var NativeEditableArtifactKernelAdapter = class {
1245
1893
  canonicalChunks: fixedChunks(canonical, input.signal),
1246
1894
  canonicalByteSize: canonical.byteLength,
1247
1895
  canonicalContentHash: editableArtifactContentHash(
1248
- `sha256:${createHash("sha256").update(canonical).digest("hex")}`
1896
+ `sha256:${createHash2("sha256").update(canonical).digest("hex")}`
1249
1897
  ),
1250
1898
  stateHash: editableArtifactStateHash(session.stateHash()),
1251
1899
  coveredHeadSequence: input.state.artifact.headSequence,
1252
- modelSchemaVersion: MODEL_SCHEMA_VERSION,
1900
+ modelSchemaVersion: modelSchemaVersionFor(input.state.modality),
1253
1901
  kernelVersion: this.runtime.buildIdentity
1254
1902
  };
1255
1903
  if (session.modality === "spreadsheet") {
@@ -1258,8 +1906,8 @@ var NativeEditableArtifactKernelAdapter = class {
1258
1906
  ...common,
1259
1907
  modality: session.modality,
1260
1908
  coveredCausalFrontier: domainFrontier(session.frontier()),
1261
- operationProtocolVersion: OPERATION_PROTOCOL_VERSION,
1262
- crdtStateVersion: CRDT_STATE_VERSION
1909
+ operationProtocolVersion: COMMITTED_TRANSACTION_PROTOCOL_VERSION,
1910
+ crdtStateVersion: SPREADSHEET_COLLABORATION_SNAPSHOT_VERSION
1263
1911
  });
1264
1912
  }
1265
1913
  if (session.modality !== input.state.modality) throw new Error("Native modality mismatch");
@@ -1292,7 +1940,7 @@ var NativeEditableArtifactKernelAdapter = class {
1292
1940
  const stateHash = editableArtifactStateHash(session.stateHash());
1293
1941
  if (stateHash !== expected.stateHash) throw new Error("Snapshot state hash mismatch");
1294
1942
  const canonicalContentHash = editableArtifactContentHash(
1295
- `sha256:${createHash("sha256").update(canonical).digest("hex")}`
1943
+ `sha256:${createHash2("sha256").update(canonical).digest("hex")}`
1296
1944
  );
1297
1945
  if (session.modality === "spreadsheet") {
1298
1946
  const coveredCausalFrontier = domainFrontier(session.frontier());
@@ -1312,12 +1960,12 @@ var NativeEditableArtifactKernelAdapter = class {
1312
1960
  canonicalContentHash,
1313
1961
  coveredHeadSequence,
1314
1962
  stateHash,
1315
- modelSchemaVersion: MODEL_SCHEMA_VERSION,
1963
+ modelSchemaVersion: SPREADSHEET_ARTIFACT_MODEL_SCHEMA_VERSION,
1316
1964
  kernelVersion: this.runtime.buildIdentity,
1317
1965
  fullCrdtStateVerified: true,
1318
1966
  coveredCausalFrontier,
1319
- operationProtocolVersion: OPERATION_PROTOCOL_VERSION,
1320
- crdtStateVersion: CRDT_STATE_VERSION,
1967
+ operationProtocolVersion: COMMITTED_TRANSACTION_PROTOCOL_VERSION,
1968
+ crdtStateVersion: SPREADSHEET_COLLABORATION_SNAPSHOT_VERSION,
1321
1969
  ...replayedTarget2 ? { replayedTarget: replayedTarget2 } : {}
1322
1970
  });
1323
1971
  }
@@ -1339,7 +1987,7 @@ var NativeEditableArtifactKernelAdapter = class {
1339
1987
  canonicalContentHash,
1340
1988
  coveredHeadSequence,
1341
1989
  stateHash,
1342
- modelSchemaVersion: MODEL_SCHEMA_VERSION,
1990
+ modelSchemaVersion: modelSchemaVersionFor(session.modality),
1343
1991
  kernelVersion: this.runtime.buildIdentity,
1344
1992
  nativeRevision,
1345
1993
  ...replayedTarget ? { replayedTarget } : {}
@@ -1349,6 +1997,10 @@ var NativeEditableArtifactKernelAdapter = class {
1349
1997
  }
1350
1998
  }
1351
1999
  };
2000
+ function modelSchemaVersionFor(modality) {
2001
+ if (modality === "spreadsheet") return SPREADSHEET_ARTIFACT_MODEL_SCHEMA_VERSION;
2002
+ return modality === "document" ? DOCUMENT_ARTIFACT_MODEL_SCHEMA_VERSION : PRESENTATION_ARTIFACT_MODEL_SCHEMA_VERSION;
2003
+ }
1352
2004
  function assertRuntime(candidate, location) {
1353
2005
  if (typeof candidate !== "object" || candidate === null) {
1354
2006
  throw new Error("Verified artifact facade returned no runtime");
@@ -1504,7 +2156,7 @@ async function readVerifiedSnapshot(objects, snapshot, signal) {
1504
2156
  snapshot.byteSize,
1505
2157
  signal
1506
2158
  );
1507
- const contentHash = `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
2159
+ const contentHash = `sha256:${createHash2("sha256").update(bytes).digest("hex")}`;
1508
2160
  if (contentHash !== snapshot.contentHash) throw new Error("Snapshot content hash mismatch");
1509
2161
  await object.assertUnchanged(signal);
1510
2162
  return bytes;
@@ -1540,7 +2192,7 @@ async function* fixedChunks(bytes, signal) {
1540
2192
  }
1541
2193
  }
1542
2194
  function namespaceForArtifact(artifactId) {
1543
- const digest = createHash("sha256").update(artifactId).digest();
2195
+ const digest = createHash2("sha256").update(artifactId).digest();
1544
2196
  let value = 0n;
1545
2197
  for (let index = 0; index < 8; index += 1) value = value << 8n | BigInt(digest[index]);
1546
2198
  return value === 0n ? 1n : value;
@@ -1564,7 +2216,7 @@ function throwIfAborted(signal) {
1564
2216
  }
1565
2217
 
1566
2218
  // src/editable-artifact-office-import.ts
1567
- import { createHash as createHash2 } from "crypto";
2219
+ import { createHash as createHash3 } from "crypto";
1568
2220
  import {
1569
2221
  ArtifactOfficeSourceUnsupportedError,
1570
2222
  prepareArtifactOfficeImport
@@ -1581,7 +2233,8 @@ import {
1581
2233
  import { requireLiveAgentAttemptAuthorization } from "@opengeni/core";
1582
2234
  import { getFilesForSubject } from "@opengeni/db";
1583
2235
  import {
1584
- MAX_BOUNDED_OBJECT_CHUNK_BYTES
2236
+ MAX_BOUNDED_OBJECT_CHUNK_BYTES,
2237
+ retryWhileMissing
1585
2238
  } from "@opengeni/storage";
1586
2239
  var SNAPSHOT_MIME = "application/vnd.opengeni.editable-artifact-snapshot";
1587
2240
  var EditableArtifactOfficeImportAdapter = class {
@@ -1712,23 +2365,47 @@ function officeFilenameMatches(filename, modality) {
1712
2365
  }
1713
2366
  async function readVerifiedWorkspaceFile(storage, file, signal) {
1714
2367
  throwIfAborted2(signal);
1715
- const before = await storage.headFile(file);
2368
+ const before = await retryWhileMissing(async () => {
2369
+ try {
2370
+ return await storage.headFile(file);
2371
+ } catch (error) {
2372
+ if (typeof storage.fileExists === "function" && !await storage.fileExists(file)) {
2373
+ return null;
2374
+ }
2375
+ throw error;
2376
+ }
2377
+ });
2378
+ if (!before) {
2379
+ throw new EditableArtifactOfficeImportError("source_changed");
2380
+ }
1716
2381
  if (before.ContentLength !== file.sizeBytes || before.ContentType !== file.contentType || before.Metadata?.sha256 !== file.sha256 || typeof before.VersionToken !== "string" || before.VersionToken.length < 1) {
1717
2382
  throw new EditableArtifactOfficeImportError("source_changed");
1718
2383
  }
1719
2384
  const bytes = new Uint8Array(file.sizeBytes);
1720
- const digest = createHash2("sha256");
2385
+ const digest = createHash3("sha256");
1721
2386
  for (let start = 0; start < file.sizeBytes; start += MAX_BOUNDED_OBJECT_CHUNK_BYTES) {
1722
2387
  throwIfAborted2(signal);
1723
2388
  const end = Math.min(file.sizeBytes, start + MAX_BOUNDED_OBJECT_CHUNK_BYTES) - 1;
1724
- const chunk = await storage.getFileRange(file, { start, end });
2389
+ const chunk = await retryWhileMissing(async () => storage.getFileRange(file, { start, end }));
1725
2390
  if (!chunk || chunk.byteLength !== end - start + 1) {
1726
2391
  throw new EditableArtifactOfficeImportError("source_changed");
1727
2392
  }
1728
2393
  bytes.set(chunk, start);
1729
2394
  digest.update(chunk);
1730
2395
  }
1731
- const after = await storage.headFile(file);
2396
+ const after = await retryWhileMissing(async () => {
2397
+ try {
2398
+ return await storage.headFile(file);
2399
+ } catch (error) {
2400
+ if (typeof storage.fileExists === "function" && !await storage.fileExists(file)) {
2401
+ return null;
2402
+ }
2403
+ throw error;
2404
+ }
2405
+ });
2406
+ if (!after) {
2407
+ throw new EditableArtifactOfficeImportError("source_changed");
2408
+ }
1732
2409
  const sha256 = digest.digest("hex");
1733
2410
  if (after.ContentLength !== before.ContentLength || after.ContentType !== before.ContentType || after.Metadata?.sha256 !== before.Metadata?.sha256 || after.VersionToken !== before.VersionToken || sha256 !== file.sha256) {
1734
2411
  throw new EditableArtifactOfficeImportError("source_changed");
@@ -1766,8 +2443,9 @@ function throwIfAborted2(signal) {
1766
2443
  }
1767
2444
 
1768
2445
  // src/editable-artifact-workspace-files.ts
1769
- import { createHash as createHash3 } from "crypto";
2446
+ import { createHash as createHash4 } from "crypto";
1770
2447
  import { completeFileUpload, prepareGeneratedWorkspaceFile } from "@opengeni/db";
2448
+ import { retryWhileMissing as retryWhileMissing2 } from "@opengeni/storage";
1771
2449
  var UPLOAD_INTENT_TTL_MS = 60 * 6e4;
1772
2450
  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
2451
  var EditableArtifactWorkspaceFileAdapter = class {
@@ -1824,7 +2502,7 @@ var EditableArtifactWorkspaceFileAdapter = class {
1824
2502
  if (existing) {
1825
2503
  assertStoredExport(existing, download.byteSize, download.mimeType, sha256);
1826
2504
  } else {
1827
- await storage.putObjectStreamIfAbsent({
2505
+ const created = await storage.putObjectStreamIfAbsent({
1828
2506
  key: objectKey,
1829
2507
  contentType: download.mimeType,
1830
2508
  chunks: download.chunks(input.signal ? { signal: input.signal } : {}),
@@ -1832,18 +2510,28 @@ var EditableArtifactWorkspaceFileAdapter = class {
1832
2510
  sha256,
1833
2511
  ...input.signal ? { signal: input.signal } : {}
1834
2512
  });
1835
- const stored = await storage.headObject(objectKey);
1836
- assertStoredExport(stored, download.byteSize, download.mimeType, sha256);
2513
+ if (!created) {
2514
+ await assertVisibleExport(
2515
+ storage,
2516
+ objectKey,
2517
+ download.byteSize,
2518
+ download.mimeType,
2519
+ sha256,
2520
+ input.signal
2521
+ );
2522
+ }
1837
2523
  }
1838
2524
  await download.assertUnchanged(input.signal);
1839
2525
  throwIfAborted3(input.signal);
1840
2526
  file = await this.completeFile(this.dependencies.db, input.scope.workspaceId, uploadId);
1841
2527
  } else {
1842
- assertStoredExport(
1843
- await storage.headObject(objectKey),
2528
+ await assertVisibleExport(
2529
+ storage,
2530
+ objectKey,
1844
2531
  download.byteSize,
1845
2532
  download.mimeType,
1846
- sha256
2533
+ sha256,
2534
+ input.signal
1847
2535
  );
1848
2536
  await download.assertUnchanged(input.signal);
1849
2537
  }
@@ -1867,6 +2555,13 @@ var EditableArtifactWorkspaceFileAdapter = class {
1867
2555
  }
1868
2556
  }
1869
2557
  };
2558
+ async function assertVisibleExport(storage, objectKey, byteSize, contentType, sha256, signal) {
2559
+ const head = await retryWhileMissing2(
2560
+ async () => await storage.headObject(objectKey),
2561
+ signal ? { signal } : {}
2562
+ );
2563
+ assertStoredExport(head, byteSize, contentType, sha256);
2564
+ }
1870
2565
  function assertStoredExport(head, byteSize, contentType, sha256) {
1871
2566
  if (!head || head.ContentLength !== byteSize || head.ContentType !== contentType || head.Metadata?.sha256 !== sha256 || typeof head.VersionToken !== "string" || head.VersionToken.length < 1) {
1872
2567
  throw new Error("Workspace export object differs from its durable materialization");
@@ -1884,7 +2579,7 @@ function plainSha256(value) {
1884
2579
  return match[1];
1885
2580
  }
1886
2581
  function deterministicUuid(seed) {
1887
- const bytes = createHash3("sha256").update(seed, "utf8").digest().subarray(0, 16);
2582
+ const bytes = createHash4("sha256").update(seed, "utf8").digest().subarray(0, 16);
1888
2583
  bytes[6] = (bytes[6] ?? 0) & 15 | 80;
1889
2584
  bytes[8] = (bytes[8] ?? 0) & 63 | 128;
1890
2585
  const hex = bytes.toString("hex");
@@ -1973,6 +2668,11 @@ async function createStandaloneEditableArtifactApplication(input) {
1973
2668
  touch: async ({ scope, sessionId, artifactId }) => await touchEditableArtifactSessionLink(input.db, scope, sessionId, artifactId)
1974
2669
  }),
1975
2670
  inspector: kernel,
2671
+ recordInspection: async (inspection) => {
2672
+ if (inspection.actor.kind !== "agent")
2673
+ throw new Error("Inspection proof requires an agent attempt");
2674
+ return recordNativeDocumentInspection(input.db, { ...inspection, actor: inspection.actor });
2675
+ },
1976
2676
  officeImports,
1977
2677
  workspaceFiles: new EditableArtifactWorkspaceFileAdapter({
1978
2678
  db: input.db,
@@ -1998,7 +2698,8 @@ async function createStandaloneEditableArtifactApplication(input) {
1998
2698
  });
1999
2699
  const compatibility = Object.freeze({
2000
2700
  assertCompatible(request) {
2001
- if (request.protocolVersion !== EDITABLE_ARTIFACT_LIVE_PROTOCOL_VERSION || request.modelSchemaVersion !== 1 || request.kernelVersion !== runtime.buildIdentity) {
2701
+ const current = currentEditableArtifactCompatibility(request.artifact.modality);
2702
+ 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
2703
  throw new EditableArtifactCompatibilityError();
2003
2704
  }
2004
2705
  }
@@ -2030,7 +2731,7 @@ var PostgresEditableArtifactAuthorization = class {
2030
2731
  }
2031
2732
  async authorize(request) {
2032
2733
  const actor = request.actor;
2033
- const rows = await withRlsContext(
2734
+ const rows = await withRlsContext2(
2034
2735
  this.db,
2035
2736
  request.scope,
2036
2737
  async (tx) => rawRows(
@@ -2108,22 +2809,24 @@ var PollingEditableArtifactAuthorizationInvalidations = class {
2108
2809
  };
2109
2810
  var RandomEditableArtifactStableIds = class {
2110
2811
  next(_kind) {
2111
- return randomUUID().replaceAll("-", "");
2812
+ return randomUUID2().replaceAll("-", "");
2112
2813
  }
2113
2814
  };
2114
2815
  var RandomEditableArtifactDurableExportIds = class {
2115
2816
  next(_kind) {
2116
- return randomUUID().replaceAll("-", "");
2817
+ return randomUUID2().replaceAll("-", "");
2117
2818
  }
2118
2819
  };
2119
2820
  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)) {
2821
+ const spreadsheetCompatibility = currentEditableArtifactCompatibility("spreadsheet");
2822
+ 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
2823
  throw new Error("Verified artifact materializer profile is incompatible");
2122
2824
  }
2123
2825
  const codecVersion = capabilities.codecVersions["opengeni.xlsx"];
2124
2826
  return Object.freeze({
2827
+ supportedFormats: EDITABLE_ARTIFACT_EXPORT_FORMATS,
2125
2828
  async resolve(input) {
2126
- if (input.modality !== "spreadsheet" || input.format !== "xlsx" || Object.keys(input.options).length !== 0) {
2829
+ if (input.modality !== "spreadsheet" || input.format !== "xlsx" || !EDITABLE_ARTIFACT_EXPORT_FORMATS[input.modality].includes(input.format) || Object.keys(input.options).length !== 0) {
2127
2830
  return null;
2128
2831
  }
2129
2832
  return Object.freeze({
@@ -2152,7 +2855,7 @@ function snapshotBytesPort(objects) {
2152
2855
  throw new Error("Editable artifact snapshot content type mismatch");
2153
2856
  }
2154
2857
  const bytes = new Uint8Array(snapshot.byteSize);
2155
- const digest = createHash4("sha256");
2858
+ const digest = createHash5("sha256");
2156
2859
  let offset = 0;
2157
2860
  for await (const chunk of object.chunks({
2158
2861
  chunkBytes: MAX_BOUNDED_OBJECT_CHUNK_BYTES2
@@ -2182,6 +2885,169 @@ function rawRows(result) {
2182
2885
  throw new Error("Unsupported database execute result shape");
2183
2886
  }
2184
2887
 
2888
+ // src/fatal-process-boundary.ts
2889
+ import { randomUUID as randomUUID3 } from "crypto";
2890
+ var API_FATAL_FLUSH_TIMEOUT_MS = 1e3;
2891
+ var API_FATAL_CORRELATION_ID_PATTERN = /^[A-Za-z0-9._:-]{1,128}$/;
2892
+ var FATAL_ERROR_CODES = {
2893
+ startup_failure: "api_startup_failed",
2894
+ unhandled_rejection: "api_unhandled_rejection",
2895
+ uncaught_exception: "api_uncaught_exception"
2896
+ };
2897
+ function installApiFatalProcessBoundary(options = {}) {
2898
+ const runtimeProcess = options.process ?? defaultProcessBoundary();
2899
+ const flushTimeoutMs = options.flushTimeoutMs ?? API_FATAL_FLUSH_TIMEOUT_MS;
2900
+ const correlationId = options.correlationId ?? (() => `api-fatal.${randomUUID3()}`);
2901
+ const fallbackLog = options.fallbackLog ?? ((message) => console.error(message));
2902
+ let observability = options.observability;
2903
+ let phase = "startup";
2904
+ let reporting = false;
2905
+ const report = async (event, reason) => {
2906
+ if (reporting) return;
2907
+ reporting = true;
2908
+ const diagnostic = apiFatalDiagnostic(event, phase, reason, correlationId);
2909
+ let message = apiFatalMessage(diagnostic);
2910
+ const activeObservability = observability;
2911
+ try {
2912
+ if (activeObservability) {
2913
+ try {
2914
+ const diagnosticId = activeObservability.recordFailureDiagnostic?.({
2915
+ code: FATAL_ERROR_CODES[event],
2916
+ stage: phase,
2917
+ retryDecision: "not_retryable",
2918
+ error: reason
2919
+ });
2920
+ if (diagnosticId) {
2921
+ diagnostic.correlationId = diagnosticId;
2922
+ message = apiFatalMessage(diagnostic);
2923
+ }
2924
+ } catch {
2925
+ }
2926
+ let logged = false;
2927
+ try {
2928
+ activeObservability.error(message, diagnostic);
2929
+ logged = true;
2930
+ } catch {
2931
+ }
2932
+ if (!logged) {
2933
+ safeFallbackLog(fallbackLog, message);
2934
+ }
2935
+ try {
2936
+ const span = activeObservability.startSpan("api.process.fatal", diagnostic);
2937
+ span.end({ error: true });
2938
+ } catch {
2939
+ }
2940
+ await flushWithin(activeObservability, flushTimeoutMs);
2941
+ } else {
2942
+ safeFallbackLog(fallbackLog, message);
2943
+ }
2944
+ } finally {
2945
+ runtimeProcess.exit(1);
2946
+ }
2947
+ };
2948
+ const onUnhandledRejection = (reason) => {
2949
+ void report("unhandled_rejection", reason);
2950
+ };
2951
+ const onUncaughtException = (reason) => {
2952
+ void report("uncaught_exception", reason);
2953
+ };
2954
+ runtimeProcess.on("unhandledRejection", onUnhandledRejection);
2955
+ runtimeProcess.on("uncaughtException", onUncaughtException);
2956
+ return {
2957
+ attachObservability: (value) => {
2958
+ observability = value;
2959
+ },
2960
+ markRunning: () => {
2961
+ phase = "running";
2962
+ },
2963
+ reportStartupFailure: async (reason) => {
2964
+ await report("startup_failure", reason);
2965
+ },
2966
+ dispose: () => {
2967
+ runtimeProcess.off("unhandledRejection", onUnhandledRejection);
2968
+ runtimeProcess.off("uncaughtException", onUncaughtException);
2969
+ }
2970
+ };
2971
+ }
2972
+ function apiFatalDiagnostic(event, phase, reason, correlationId) {
2973
+ let safeCorrelationId = "api-fatal.fallback";
2974
+ try {
2975
+ const candidate = correlationId();
2976
+ if (API_FATAL_CORRELATION_ID_PATTERN.test(candidate)) {
2977
+ safeCorrelationId = candidate;
2978
+ }
2979
+ } catch {
2980
+ }
2981
+ return {
2982
+ errorClass: "ApiFatalOperationError",
2983
+ errorCode: FATAL_ERROR_CODES[event],
2984
+ origin: "api",
2985
+ phase,
2986
+ reasonKind: apiFatalReasonKind(reason),
2987
+ correlationId: safeCorrelationId
2988
+ };
2989
+ }
2990
+ function apiFatalReasonKind(reason) {
2991
+ if (reason === null) return "null";
2992
+ const kind = typeof reason;
2993
+ if (kind !== "object") return kind;
2994
+ try {
2995
+ return reason instanceof Error ? "error" : "object";
2996
+ } catch {
2997
+ return "object";
2998
+ }
2999
+ }
3000
+ function apiFatalMessage(diagnostic) {
3001
+ return `OpenGeni API fatal process failure (${diagnostic.errorCode}; phase=${diagnostic.phase}; reason_kind=${diagnostic.reasonKind}; correlation_id=${diagnostic.correlationId})`;
3002
+ }
3003
+ async function flushWithin(observability, timeoutMs) {
3004
+ let flush;
3005
+ try {
3006
+ flush = Promise.resolve(observability.flush()).catch(() => void 0);
3007
+ } catch {
3008
+ return;
3009
+ }
3010
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) return;
3011
+ let timeout;
3012
+ try {
3013
+ await Promise.race([
3014
+ flush,
3015
+ new Promise((resolve) => {
3016
+ timeout = setTimeout(resolve, timeoutMs);
3017
+ })
3018
+ ]);
3019
+ } finally {
3020
+ if (timeout !== void 0) clearTimeout(timeout);
3021
+ }
3022
+ }
3023
+ function safeFallbackLog(fallbackLog, message) {
3024
+ try {
3025
+ fallbackLog(message);
3026
+ } catch {
3027
+ }
3028
+ }
3029
+ function defaultProcessBoundary() {
3030
+ return {
3031
+ on: (event, listener) => {
3032
+ if (event === "unhandledRejection") {
3033
+ process.on("unhandledRejection", listener);
3034
+ } else {
3035
+ process.on("uncaughtException", listener);
3036
+ }
3037
+ },
3038
+ off: (event, listener) => {
3039
+ if (event === "unhandledRejection") {
3040
+ process.off("unhandledRejection", listener);
3041
+ } else {
3042
+ process.off("uncaughtException", listener);
3043
+ }
3044
+ },
3045
+ exit: (code) => {
3046
+ process.exit(code);
3047
+ }
3048
+ };
3049
+ }
3050
+
2185
3051
  // src/index.ts
2186
3052
  function isWorkflowAlreadyStarted(error) {
2187
3053
  return error instanceof WorkflowExecutionAlreadyStartedError;
@@ -2206,7 +3072,40 @@ async function createTemporalWorkflowClient(settings, db) {
2206
3072
  connection,
2207
3073
  namespace: settings.temporalNamespace
2208
3074
  });
3075
+ const scheduledTasks = createTemporalScheduleSynchronizer({
3076
+ db,
3077
+ withDeadline: (deadline, work) => temporal.withDeadline(deadline, work),
3078
+ upsert: async (task) => {
3079
+ const options = temporalScheduleOptions(task, settings.temporalTaskQueue);
3080
+ try {
3081
+ await temporal.schedule.getHandle(task.temporalScheduleId).update(() => temporalScheduleUpdateOptions(options));
3082
+ } catch (error) {
3083
+ if (!shouldCreateScheduleAfterUpdateError(error)) throw error;
3084
+ await temporal.schedule.create(options);
3085
+ }
3086
+ },
3087
+ remove: async (temporalScheduleId) => {
3088
+ try {
3089
+ await temporal.schedule.getHandle(temporalScheduleId).delete();
3090
+ } catch (error) {
3091
+ if (!(error instanceof ScheduleNotFoundError)) throw error;
3092
+ }
3093
+ }
3094
+ });
2209
3095
  const client = {
3096
+ triggerAutomationRun: async ({ accountId, workspaceId, runId }) => {
3097
+ try {
3098
+ await temporal.workflow.start("automationRunWorkflow", {
3099
+ taskQueue: settings.temporalTaskQueue,
3100
+ workflowId: `automation-run:${runId}`,
3101
+ workflowIdReusePolicy: "REJECT_DUPLICATE",
3102
+ args: [{ accountId, workspaceId, runId }]
3103
+ });
3104
+ } catch (error) {
3105
+ if (isWorkflowAlreadyStarted(error)) return;
3106
+ throw error;
3107
+ }
3108
+ },
2210
3109
  signalUserMessage: async ({ eventId, workflowId }) => {
2211
3110
  await temporal.workflow.getHandle(workflowId).signal("userMessage", eventId);
2212
3111
  },
@@ -2216,7 +3115,8 @@ async function createTemporalWorkflowClient(settings, db) {
2216
3115
  sessionId,
2217
3116
  workflowId,
2218
3117
  wakeRevision,
2219
- interruptionRequested
3118
+ interruptionRequested,
3119
+ onSignalAccepted
2220
3120
  }) => {
2221
3121
  await temporal.workflow.signalWithStart("sessionWorkflow", {
2222
3122
  taskQueue: settings.temporalTaskQueue,
@@ -2225,7 +3125,8 @@ async function createTemporalWorkflowClient(settings, db) {
2225
3125
  args: [{ accountId, workspaceId, sessionId }],
2226
3126
  signal: interruptionRequested ? "sessionControl" : "queueChanged"
2227
3127
  });
2228
- await markSessionWorkflowWakeDelivered(db, {
3128
+ onSignalAccepted?.();
3129
+ return await markSessionWorkflowWakeDelivered(db, {
2229
3130
  accountId,
2230
3131
  workspaceId,
2231
3132
  sessionId,
@@ -2284,41 +3185,14 @@ async function createTemporalWorkflowClient(settings, db) {
2284
3185
  wakeRevision: workflowWakeRevision
2285
3186
  });
2286
3187
  },
2287
- syncScheduledTask: async ({ task }) => {
2288
- const schedule = temporal.schedule.getHandle(task.temporalScheduleId);
2289
- if (task.schedule.type === "manual") {
2290
- try {
2291
- await schedule.delete();
2292
- } catch (error) {
2293
- if (!(error instanceof ScheduleNotFoundError)) throw error;
2294
- }
2295
- return;
2296
- }
2297
- const options = temporalScheduleOptions(task, settings.temporalTaskQueue);
2298
- try {
2299
- await schedule.update(() => temporalScheduleUpdateOptions(options));
2300
- } catch (error) {
2301
- if (!shouldCreateScheduleAfterUpdateError(error)) {
2302
- throw error;
2303
- }
2304
- await temporal.schedule.create(options);
2305
- }
2306
- },
2307
- deleteScheduledTaskSchedule: async ({ temporalScheduleId }) => {
2308
- try {
2309
- await temporal.withDeadline(Date.now() + 5e3, async () => {
2310
- await temporal.schedule.getHandle(temporalScheduleId).delete();
2311
- });
2312
- } catch (error) {
2313
- if (error instanceof ScheduleNotFoundError) return;
2314
- throw error;
2315
- }
2316
- },
3188
+ syncScheduledTask: async ({ task, onFailure }) => scheduledTasks.sync(task, onFailure),
3189
+ deleteScheduledTaskSchedule: async ({ temporalScheduleId }) => scheduledTasks.remove(temporalScheduleId),
2317
3190
  triggerScheduledTask: async ({
2318
3191
  task,
2319
3192
  agentRunUsageIdempotencyKey,
2320
3193
  triggerWorkflowId,
2321
3194
  initiator,
3195
+ credentialRestriction,
2322
3196
  triggerType = "manual"
2323
3197
  }) => {
2324
3198
  const workflowId = triggerWorkflowId;
@@ -2334,7 +3208,8 @@ async function createTemporalWorkflowClient(settings, db) {
2334
3208
  taskId: task.id,
2335
3209
  triggerType,
2336
3210
  agentRunUsageIdempotencyKey,
2337
- initiator
3211
+ initiator,
3212
+ ...credentialRestriction ? { credentialRestriction } : {}
2338
3213
  }
2339
3214
  ]
2340
3215
  });
@@ -2348,7 +3223,7 @@ async function createTemporalWorkflowClient(settings, db) {
2348
3223
  startRigVerification: async ({ workspaceId, changeId, versionId, workflowId }) => {
2349
3224
  const targetId = changeId ?? versionId;
2350
3225
  if (!targetId) {
2351
- throw new Error("rig verification requires changeId or versionId");
3226
+ throw new Error("sandbox environment verification requires changeId or versionId");
2352
3227
  }
2353
3228
  try {
2354
3229
  await temporal.workflow.start("rigVerificationWorkflow", {
@@ -2391,11 +3266,12 @@ async function createTemporalWorkflowClient(settings, db) {
2391
3266
  }
2392
3267
  };
2393
3268
  }
2394
- async function startApi() {
2395
- const settings = getSettings();
2396
- const observability = createObservability(settings, { component: "api" });
3269
+ async function startApi(options = {}) {
3270
+ const settings = options.settings ?? getSettings();
3271
+ const observability = options.observability ?? createObservability(settings, { component: "api" });
2397
3272
  const searchPath = dbSearchPath(settings);
2398
3273
  const dbClient = createDb(settings.databaseUrl, {
3274
+ max: settings.apiDatabasePoolMax,
2399
3275
  ...searchPath ? { searchPath } : {},
2400
3276
  rlsStrategy: settings.rlsStrategy
2401
3277
  });
@@ -2406,21 +3282,37 @@ async function startApi() {
2406
3282
  const databasePosture = {
2407
3283
  rlsStrategy: settings.rlsStrategy,
2408
3284
  expectedRole: settings.runtimeDatabaseRole,
2409
- targetSchema: settings.dbSchema.trim() || "public"
3285
+ targetSchema: settings.dbSchema.trim() || "public",
3286
+ organizationTenancyCanonicalActivationEnabled: settings.organizationTenancyCanonicalActivationEnabled
2410
3287
  };
2411
3288
  const controlPlaneAuth = resolveNatsControlPlaneAuth(settings);
2412
3289
  try {
3290
+ const { ensureModalRegistryImage } = await import("@opengeni/runtime/sandbox");
3291
+ await retryStartupDependency(
3292
+ "Modal private-registry image",
3293
+ () => ensureModalRegistryImage(settings),
3294
+ { ...retryOptions, onRetry }
3295
+ );
2413
3296
  await retryStartupDependency(
2414
3297
  "PostgreSQL runtime posture",
2415
3298
  () => assertRuntimeDatabasePosture(dbClient.db, databasePosture),
3299
+ { ...retryOptions, onRetry, shouldRetry: isRetryableRuntimeDatabaseStartupError }
3300
+ );
3301
+ const resolvedCatalog = await retryStartupDependency(
3302
+ "model catalog",
3303
+ () => resolveCatalogSettings(dbClient.db, settings),
2416
3304
  { ...retryOptions, onRetry }
2417
3305
  );
3306
+ observability.info("OpenGeni model catalog resolved", {
3307
+ catalogSource: resolvedCatalog.source,
3308
+ catalogVersion: resolvedCatalog.version
3309
+ });
2418
3310
  bus = await retryStartupDependency(
2419
3311
  "NATS",
2420
3312
  () => createNatsEventBus(
2421
3313
  settings.natsUrl,
2422
3314
  controlPlaneAuth ? { user: controlPlaneAuth.user, pass: controlPlaneAuth.password } : void 0,
2423
- { logger: observabilityEventLogger(observability) }
3315
+ observabilityEventBusOptions(observability)
2424
3316
  ),
2425
3317
  {
2426
3318
  ...retryOptions,
@@ -2481,30 +3373,54 @@ async function startApi() {
2481
3373
  behavior: "http_and_websocket_fail_closed"
2482
3374
  });
2483
3375
  }
2484
- const artifactWebSockets = new EditableArtifactWebSocketTransport(routeDeps.editableArtifacts);
3376
+ const artifactWebSockets = new EditableArtifactWebSocketTransport(
3377
+ routeDeps.editableArtifacts,
3378
+ editableArtifactSourceSessionAuthorizer(routeDeps)
3379
+ );
3380
+ const interactionFrameProxies = new InteractionFrameProxyTransport(
3381
+ resolveFirstPartyDelegationSecret(settings)
3382
+ );
3383
+ const localBrowserBoundary = createLocalBrowserBoundary(settings, {
3384
+ warn: (message, attributes) => observability.warn(message, attributes)
3385
+ });
3386
+ const webSocketUpgrades = [interactionFrameProxies, artifactWebSockets];
2485
3387
  const server = Bun.serve({
2486
3388
  hostname: settings.apiHost,
2487
3389
  port: settings.apiPort,
2488
3390
  idleTimeout: 255,
2489
3391
  fetch: (request, bunServer) => {
2490
- if (artifactWebSockets.handles(request)) {
2491
- return artifactWebSockets.upgrade(request, bunServer);
2492
- }
2493
- return app.fetch(request);
3392
+ const upgrade = dispatchApiWebSocketUpgrade(
3393
+ request,
3394
+ bunServer,
3395
+ webSocketUpgrades,
3396
+ localBrowserBoundary
3397
+ );
3398
+ if (upgrade.handled) return upgrade.response;
3399
+ return app.fetch(
3400
+ request,
3401
+ apiRequestBindingsForTransportPeer(bunServer.requestIP(request)?.address)
3402
+ );
2494
3403
  },
2495
3404
  websocket: {
2496
3405
  maxPayloadLength: EDITABLE_ARTIFACT_LIVE_WEBSOCKET_MAX_MESSAGE_BYTES,
2497
3406
  backpressureLimit: 16 * 1024 * 1024,
2498
3407
  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)
3408
+ open: (socket) => socket.data.attach(socket),
3409
+ message: (socket, message) => socket.data.receive(message),
3410
+ close: (socket) => socket.data.transportClosed()
2502
3411
  }
2503
3412
  });
3413
+ const metricsServer = startApiMetricsListener(settings, observability);
2504
3414
  const stopSlackInteractionPump = settings.slackSigningSecret ? startSlackInteractionPump(routeDeps) : void 0;
2505
3415
  const stopMemorySlackPublicationPump = startMemorySlackPublicationPump(routeDeps);
3416
+ const stopWorkspaceWebhookDispatchPump = startWorkspaceWebhookDispatchPump({
3417
+ db: dbClient.db,
3418
+ settings,
3419
+ observability
3420
+ });
2506
3421
  const stopTemporalScheduleCleanupPump = startTemporalScheduleCleanupPump({
2507
3422
  db: dbClient.db,
3423
+ cleanupConnectorAuthorization: async (claim) => await cleanupScheduledTaskConnectorAuthorization(routeDeps, claim),
2508
3424
  deleteSchedule: async (temporalScheduleId) => {
2509
3425
  await workflowClient.client.deleteScheduledTaskSchedule({ temporalScheduleId });
2510
3426
  },
@@ -2548,17 +3464,21 @@ async function startApi() {
2548
3464
  }
2549
3465
  observability.info("OpenGeni API listening", {
2550
3466
  host: settings.apiHost,
2551
- port: settings.apiPort
3467
+ port: settings.apiPort,
3468
+ ...metricsServer ? { metricsPort: settings.apiMetricsPort } : {}
2552
3469
  });
2553
3470
  return {
2554
3471
  server,
2555
3472
  close: async () => {
2556
3473
  server.stop(true);
3474
+ metricsServer?.stop(true);
2557
3475
  stopSlackInteractionPump?.();
2558
3476
  await stopMemorySlackPublicationPump();
2559
3477
  stopMetricsIngestion?.();
2560
3478
  stopHelloIngestion?.();
2561
3479
  await stopTemporalScheduleCleanupPump();
3480
+ await stopWorkspaceWebhookDispatchPump();
3481
+ await routeDeps.userPresence?.close().catch(() => void 0);
2562
3482
  await Promise.allSettled([
2563
3483
  Promise.resolve(editableArtifactComposition?.close()),
2564
3484
  authCalloutResponder?.close(),
@@ -2570,7 +3490,16 @@ async function startApi() {
2570
3490
  };
2571
3491
  }
2572
3492
  if (import.meta.main) {
2573
- await startApi();
3493
+ const fatalBoundary = installApiFatalProcessBoundary();
3494
+ try {
3495
+ const settings = getSettings();
3496
+ const observability = createObservability(settings, { component: "api" });
3497
+ fatalBoundary.attachObservability(observability);
3498
+ await startApi({ settings, observability });
3499
+ fatalBoundary.markRunning();
3500
+ } catch (error) {
3501
+ await fatalBoundary.reportStartupFailure(error);
3502
+ }
2574
3503
  }
2575
3504
  function temporalOverlapPolicy(policy) {
2576
3505
  if (policy === "skip") {
@@ -2675,7 +3604,7 @@ function temporalScheduleOptions(task, taskQueue) {
2675
3604
  }
2676
3605
  function scheduledTaskEffectivelyPaused(task) {
2677
3606
  if (task.status === "paused") return true;
2678
- if (task.action.kind !== "knowledge_source_sync") return false;
3607
+ if (!scheduledTaskKnowledgeSource(task)) return false;
2679
3608
  const control = task.metadata.knowledgeSourceSync;
2680
3609
  if (!control || typeof control !== "object") return false;
2681
3610
  const record = control;