@opengeni/api-router 3.0.1 → 3.0.2-canary.38894290271001

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 (546) hide show
  1. package/dist/access-grant-rls.d.ts +8 -1
  2. package/dist/admission-trace.d.ts +8 -0
  3. package/dist/app.d.ts +50 -3
  4. package/dist/app.js +10 -1
  5. package/dist/artifact-catalog-cursor.d.ts +1 -0
  6. package/dist/attached-browser-viewer.d.ts +11 -0
  7. package/dist/auth/managed-auth-attempt-context.d.ts +2 -0
  8. package/dist/auth/managed-auth-rate-limits.d.ts +167 -0
  9. package/dist/auth/managed-auth-session-adapter.d.ts +9 -1
  10. package/dist/auth/managed-auth.d.ts +72 -5
  11. package/dist/auth/managed-sign-in-notifications.d.ts +4 -0
  12. package/dist/auth/new-signups-gate.d.ts +23 -0
  13. package/dist/auth/signup-funnel-metrics.d.ts +44 -0
  14. package/dist/azure-live.d.ts +28 -0
  15. package/dist/browser-action-fences.d.ts +13 -0
  16. package/dist/browser-controller-authority.d.ts +1 -61
  17. package/dist/browser-ephemeral.d.ts +5 -0
  18. package/dist/browser-lost-runtime-end.d.ts +24 -0
  19. package/dist/browser-state-authority.d.ts +1 -27
  20. package/dist/{chunk-AJ62T5QN.js → chunk-GBUJ2DYZ.js} +80507 -39878
  21. package/dist/chunk-GBUJ2DYZ.js.map +1 -0
  22. package/dist/chunk-OGQDXVXY.js +18 -0
  23. package/dist/chunk-OGQDXVXY.js.map +1 -0
  24. package/dist/claude-default-models.d.ts +19 -0
  25. package/dist/claude-subscription-core.d.ts +50 -0
  26. package/dist/claude-subscription-oauth.d.ts +36 -0
  27. package/dist/claude-subscription-profile.d.ts +9 -0
  28. package/dist/claude-subscription-usage.d.ts +6 -0
  29. package/dist/claude-workspace-connection.d.ts +15 -0
  30. package/dist/codemode.d.ts +40 -5
  31. package/dist/codex-realtime.d.ts +29 -3
  32. package/dist/codex-session-pointers.d.ts +19 -0
  33. package/dist/connection-ownership.d.ts +6 -3
  34. package/dist/controller-data-plane.d.ts +16 -0
  35. package/dist/credit-promotion-snapshot.d.ts +6 -0
  36. package/dist/editable-artifact-source-session.d.ts +3 -0
  37. package/dist/editable-artifact-websocket.d.ts +5 -3
  38. package/dist/fatal-process-boundary.d.ts +9 -1
  39. package/dist/github-access.d.ts +7 -1
  40. package/dist/github-browser-flow.d.ts +1 -1
  41. package/dist/http/acting-person.d.ts +36 -0
  42. package/dist/http/api-contract-compat.d.ts +19 -0
  43. package/dist/http/api-error.d.ts +50 -1
  44. package/dist/http/auth.d.ts +8 -1
  45. package/dist/http/cors.d.ts +12 -0
  46. package/dist/http/deprecation.d.ts +42 -0
  47. package/dist/http/keyed-admission.d.ts +12 -0
  48. package/dist/http/local-browser-boundary.d.ts +72 -0
  49. package/dist/http/metrics-listener.d.ts +22 -0
  50. package/dist/http/path-identifier.d.ts +17 -0
  51. package/dist/http/registered-route-label.d.ts +16 -0
  52. package/dist/http/rejection-telemetry.d.ts +34 -0
  53. package/dist/http/request-body.d.ts +27 -0
  54. package/dist/http/request-source.d.ts +41 -3
  55. package/dist/http/sse.d.ts +12 -1
  56. package/dist/http/unmatched-route.d.ts +18 -0
  57. package/dist/http/user-content.d.ts +36 -0
  58. package/dist/http/variable-sets-removed.d.ts +41 -0
  59. package/dist/http/websocket-upgrade-dispatch.d.ts +18 -0
  60. package/dist/index.js +1279 -108
  61. package/dist/index.js.map +1 -1
  62. package/dist/integration-connect-metrics.d.ts +35 -0
  63. package/dist/integration-endpoint-test.d.ts +20 -0
  64. package/dist/integrations/api-integrations.d.ts +2 -2
  65. package/dist/integrations/atlassian.d.ts +8 -3
  66. package/dist/integrations/fiken.d.ts +3 -1
  67. package/dist/integrations/github-lens-connect.d.ts +37 -66
  68. package/dist/integrations/google-drive.d.ts +2 -0
  69. package/dist/integrations/integration-commit-authority.d.ts +13 -0
  70. package/dist/integrations/marketplace-plugin.d.ts +43 -43
  71. package/dist/integrations/oauth-client.d.ts +171 -12
  72. package/dist/integrations/oauth-profiles.d.ts +42 -13
  73. package/dist/integrations/onepassword-vault-provider.d.ts +9 -0
  74. package/dist/integrations/slack-bot.d.ts +285 -7
  75. package/dist/integrations/slack-file-upload-flow.d.ts +62 -0
  76. package/dist/integrations/slack-interactions.d.ts +89 -10
  77. package/dist/integrations/slack-mrkdwn.d.ts +41 -0
  78. package/dist/integrations/slack-routing.d.ts +1 -1
  79. package/dist/integrations/slack-session-defaults.d.ts +58 -0
  80. package/dist/integrations/slack-task-file-upload.d.ts +36 -0
  81. package/dist/integrations/slack-tool-review.d.ts +5 -0
  82. package/dist/interaction-agent-access.d.ts +13 -8
  83. package/dist/interaction-agent-version.d.ts +21 -0
  84. package/dist/interaction-holder-heartbeat.d.ts +5 -0
  85. package/dist/interaction-metrics.d.ts +27 -2
  86. package/dist/interaction-route-stages.d.ts +25 -0
  87. package/dist/interaction-secrets-vault.d.ts +20 -0
  88. package/dist/interaction-secrets.d.ts +103 -0
  89. package/dist/mcp/action-catalog-types.d.ts +14 -0
  90. package/dist/mcp/action-catalog.gen.d.ts +2 -0
  91. package/dist/mcp/action-descriptions.d.ts +4 -0
  92. package/dist/mcp/action-schema-imports.gen.d.ts +1 -0
  93. package/dist/mcp/capability-account-readiness.d.ts +7 -0
  94. package/dist/mcp/child-read-evidence.d.ts +9 -0
  95. package/dist/mcp/company-profile-agent-admin.d.ts +12 -0
  96. package/dist/mcp/contract-input.d.ts +13 -0
  97. package/dist/mcp/documents.d.ts +2 -0
  98. package/dist/mcp/first-party-tool-permissions.d.ts +3 -606
  99. package/dist/mcp/notification-tools.d.ts +26 -0
  100. package/dist/mcp/orchestration-failure-diagnostic.d.ts +43 -0
  101. package/dist/mcp/scheduled-task-input.d.ts +1309 -0
  102. package/dist/mcp/scheduled-task-view.d.ts +217 -3
  103. package/dist/mcp/secret-tools.d.ts +14 -0
  104. package/dist/mcp/server.d.ts +108 -5
  105. package/dist/mcp/session-admin-access-tools.d.ts +33 -0
  106. package/dist/mcp/session-event-errors.d.ts +28 -0
  107. package/dist/mcp/session-event-view.d.ts +62 -0
  108. package/dist/mcp/session-view.d.ts +15 -0
  109. package/dist/mcp/session-wait.d.ts +18 -0
  110. package/dist/mcp/site-agent-clamp-tools.d.ts +38 -0
  111. package/dist/mcp/site-data-tools.d.ts +22 -0
  112. package/dist/mcp/site-publication-tools.d.ts +19 -0
  113. package/dist/mcp/site-sql-tools.d.ts +27 -0
  114. package/dist/mcp-oauth.d.ts +37 -4
  115. package/dist/model-calls/chat-completions.d.ts +87 -0
  116. package/dist/model-calls/pending-usage-settlements.d.ts +57 -0
  117. package/dist/model-calls/service.d.ts +36 -0
  118. package/dist/model-catalog.d.ts +9 -3
  119. package/dist/native-push-dispatch.d.ts +69 -0
  120. package/dist/observability.d.ts +3 -1
  121. package/dist/organization-mcp.d.ts +82 -0
  122. package/dist/prepared-mcp-actor.d.ts +77 -0
  123. package/dist/prepared-mcp-permissions.d.ts +8 -0
  124. package/dist/routes/analytics-consent.d.ts +26 -0
  125. package/dist/routes/api-integrations.d.ts +11 -0
  126. package/dist/routes/api-keys.d.ts +7 -7
  127. package/dist/routes/automations.d.ts +2 -0
  128. package/dist/routes/billing.d.ts +59 -0
  129. package/dist/routes/browser-sessions.d.ts +65 -2
  130. package/dist/routes/chat-completions.d.ts +11 -0
  131. package/dist/routes/claude-core-usage.d.ts +20 -0
  132. package/dist/routes/claude-subscription-accounts.d.ts +3 -0
  133. package/dist/routes/claude-subscription-oauth.d.ts +3 -0
  134. package/dist/routes/client-errors.d.ts +182 -0
  135. package/dist/routes/codex-core.d.ts +401 -0
  136. package/dist/routes/codex.d.ts +64 -2
  137. package/dist/routes/computer-sessions.d.ts +6 -1
  138. package/dist/routes/desktop-distribution.d.ts +32 -0
  139. package/dist/routes/github-browser-fonts.gen.d.ts +2 -0
  140. package/dist/routes/github-browser-pages.d.ts +27 -0
  141. package/dist/routes/inbox.d.ts +13 -0
  142. package/dist/routes/insights-response-cache.d.ts +10 -0
  143. package/dist/routes/insights-usage.d.ts +12 -0
  144. package/dist/routes/insights.d.ts +28 -0
  145. package/dist/routes/interaction-placement-message.d.ts +7 -0
  146. package/dist/routes/managed-sign-in-methods.d.ts +6 -0
  147. package/dist/routes/model-account-choice.d.ts +13 -0
  148. package/dist/routes/model-account-settings.d.ts +19 -0
  149. package/dist/routes/native-app-auth.d.ts +12 -0
  150. package/dist/routes/organization-integration-policy.d.ts +8 -0
  151. package/dist/routes/organization-integrations.d.ts +6 -0
  152. package/dist/routes/organization-mcp-connections.d.ts +3 -0
  153. package/dist/routes/organization-model-defaults.d.ts +9 -0
  154. package/dist/routes/organization-service-accounts.d.ts +3 -0
  155. package/dist/routes/personal-api-keys.d.ts +3 -0
  156. package/dist/routes/session-admin-access.d.ts +3 -0
  157. package/dist/routes/session-artifact-associations.d.ts +4 -0
  158. package/dist/routes/session-history-imports.d.ts +20 -0
  159. package/dist/routes/sessions.d.ts +38 -2
  160. package/dist/routes/site-data.d.ts +18 -0
  161. package/dist/routes/site-publishing.d.ts +5 -0
  162. package/dist/routes/site-runtime.d.ts +31 -0
  163. package/dist/routes/site-sql.d.ts +16 -0
  164. package/dist/routes/slack-bot-access.d.ts +3 -0
  165. package/dist/routes/subscription-account-pools.d.ts +21 -0
  166. package/dist/routes/subscription-core-http.d.ts +5 -0
  167. package/dist/routes/subscription-pool-access.d.ts +37 -0
  168. package/dist/routes/subscription-pool-core.d.ts +153 -0
  169. package/dist/routes/supergrok.d.ts +6 -6
  170. package/dist/routes/usage-allowances.d.ts +12 -0
  171. package/dist/routes/variable-sets-removed.d.ts +2 -0
  172. package/dist/routes/vault-providers.d.ts +34 -0
  173. package/dist/routes/vaults.d.ts +3 -0
  174. package/dist/routes/workspace-integrations.d.ts +35 -0
  175. package/dist/routes/workspace-model-providers.d.ts +4 -0
  176. package/dist/routes/workspaces.d.ts +11 -2
  177. package/dist/sandbox/agent-update.d.ts +64 -0
  178. package/dist/sandbox/auth-callout.d.ts +8 -0
  179. package/dist/sandbox/channel-a.d.ts +15 -1
  180. package/dist/sandbox/enrollment.d.ts +26 -4
  181. package/dist/sandbox/machine-audience.d.ts +29 -0
  182. package/dist/sandbox/machines.d.ts +15 -2
  183. package/dist/sandbox/metrics-ingestion.d.ts +6 -2
  184. package/dist/sandbox/screen-control.d.ts +85 -0
  185. package/dist/sandbox/viewer.d.ts +5 -1
  186. package/dist/sandbox-file-artifacts.d.ts +17 -1
  187. package/dist/scheduled-task-blockers.d.ts +39 -0
  188. package/dist/scheduled-task-deletion.d.ts +3 -4
  189. package/dist/scheduled-task-lifecycle.d.ts +14 -0
  190. package/dist/secret-answers.d.ts +40 -0
  191. package/dist/session-create-observability.d.ts +5 -0
  192. package/dist/session-event-view-36GO3IL7.js +528 -0
  193. package/dist/session-event-view-36GO3IL7.js.map +1 -0
  194. package/dist/signed-state-secret.d.ts +18 -0
  195. package/dist/site-agent-guard.d.ts +53 -0
  196. package/dist/site-data/operations.d.ts +103 -0
  197. package/dist/site-data/service.d.ts +203 -0
  198. package/dist/site-frame-guard.d.ts +32 -0
  199. package/dist/site-publishing/agent-data-tools.d.ts +84 -0
  200. package/dist/site-publishing/agent-model.d.ts +58 -0
  201. package/dist/site-publishing/agent-session.d.ts +16 -0
  202. package/dist/site-publishing/content-scanner.d.ts +31 -0
  203. package/dist/site-publishing/edge-sync.d.ts +48 -0
  204. package/dist/site-publishing/gateway-signature.d.ts +56 -0
  205. package/dist/site-publishing/limits.d.ts +63 -0
  206. package/dist/site-publishing/page-tool-authority.d.ts +30 -0
  207. package/dist/site-publishing/platform-usage.d.ts +113 -0
  208. package/dist/site-publishing/platform.d.ts +21 -0
  209. package/dist/site-publishing/readiness.d.ts +62 -0
  210. package/dist/site-publishing/runtime.d.ts +179 -0
  211. package/dist/site-publishing/service.d.ts +264 -0
  212. package/dist/site-publishing/session-events.d.ts +22 -0
  213. package/dist/site-publishing/session-views.d.ts +36 -0
  214. package/dist/site-publishing/viewer.d.ts +40 -0
  215. package/dist/site-session-origin.d.ts +31 -6
  216. package/dist/site-sql/format.d.ts +47 -0
  217. package/dist/site-sql/gateway.d.ts +50 -0
  218. package/dist/site-sql/service.d.ts +213 -0
  219. package/dist/temporal-schedule-sync.d.ts +26 -0
  220. package/dist/transcription/billing-refusal.d.ts +8 -0
  221. package/dist/transcription/normalize.d.ts +23 -0
  222. package/dist/transcription/providers/azure-mai.d.ts +12 -0
  223. package/dist/transcription/providers/azure-openai.d.ts +4 -0
  224. package/dist/transcription/providers/openai.d.ts +16 -0
  225. package/dist/transcription/providers/subscription-core.d.ts +39 -0
  226. package/dist/transcription/service.d.ts +10 -1
  227. package/dist/transcription/wav-duration.d.ts +8 -0
  228. package/dist/workspace-tool-gateway-attestations.d.ts +37 -0
  229. package/dist/workspace-tool-gateway-observability.d.ts +16 -0
  230. package/dist/workspace-tool-gateway-secrets.d.ts +27 -0
  231. package/dist/workspace-tool-gateway.d.ts +102 -6
  232. package/dist/workspace-tool-target-fence.d.ts +116 -0
  233. package/dist/workspace-tool-target.d.ts +225 -0
  234. package/dist/workspace-webhook-dispatch.d.ts +26 -0
  235. package/dist/xai-subscription-core.d.ts +60 -0
  236. package/package.json +25 -20
  237. package/src/access-grant-rls.ts +64 -14
  238. package/src/admission-trace.ts +14 -0
  239. package/src/app.ts +1684 -252
  240. package/src/artifact-catalog-cursor.ts +1 -0
  241. package/src/attached-browser-viewer.ts +26 -0
  242. package/src/auth/managed-auth-attempt-context.ts +9 -0
  243. package/src/auth/managed-auth-rate-limits.ts +252 -0
  244. package/src/auth/managed-auth-session-adapter.ts +64 -29
  245. package/src/auth/managed-auth.ts +303 -35
  246. package/src/auth/managed-email.ts +18 -1
  247. package/src/auth/managed-sign-in-notifications.ts +81 -0
  248. package/src/auth/new-signups-gate.ts +49 -0
  249. package/src/auth/organization-user-setup.ts +3 -3
  250. package/src/auth/signup-funnel-metrics.ts +164 -0
  251. package/src/azure-live.ts +103 -0
  252. package/src/browser-action-fences.ts +45 -0
  253. package/src/browser-controller-authority.ts +1 -186
  254. package/src/browser-ephemeral.ts +39 -0
  255. package/src/browser-lost-runtime-end.ts +71 -0
  256. package/src/browser-state-authority.ts +1 -236
  257. package/src/claude-default-models.ts +37 -0
  258. package/src/claude-subscription-core.ts +154 -0
  259. package/src/claude-subscription-oauth.ts +352 -0
  260. package/src/claude-subscription-profile.ts +73 -0
  261. package/src/claude-subscription-usage.ts +51 -0
  262. package/src/claude-workspace-connection.ts +82 -0
  263. package/src/codemode.ts +236 -39
  264. package/src/codex-realtime.ts +181 -53
  265. package/src/codex-session-pointers.ts +80 -0
  266. package/src/company-brain-okf.ts +1 -1
  267. package/src/connected-machine-computer-access.ts +2 -1
  268. package/src/connection-ownership.ts +19 -13
  269. package/src/controller-data-plane.ts +34 -0
  270. package/src/credit-promotion-snapshot.ts +36 -0
  271. package/src/editable-artifact-production.ts +10 -0
  272. package/src/editable-artifact-source-session.ts +123 -0
  273. package/src/editable-artifact-websocket.ts +16 -3
  274. package/src/fatal-process-boundary.ts +67 -3
  275. package/src/gateway-realtime.ts +12 -8
  276. package/src/github-access.ts +31 -5
  277. package/src/github-browser-flow.ts +1 -1
  278. package/src/http/acting-person.ts +105 -0
  279. package/src/http/api-contract-compat.ts +49 -0
  280. package/src/http/api-error.ts +210 -2
  281. package/src/http/auth.ts +68 -4
  282. package/src/http/cors.ts +37 -5
  283. package/src/http/deprecation.ts +118 -0
  284. package/src/http/keyed-admission.ts +29 -0
  285. package/src/http/local-browser-boundary.ts +343 -0
  286. package/src/http/metrics-listener.ts +57 -0
  287. package/src/http/path-identifier.ts +54 -0
  288. package/src/http/registered-route-label.ts +34 -0
  289. package/src/http/rejection-telemetry.ts +120 -0
  290. package/src/http/request-body.ts +127 -0
  291. package/src/http/request-source.ts +154 -13
  292. package/src/http/sse.ts +88 -28
  293. package/src/http/unmatched-route.ts +60 -0
  294. package/src/http/user-content.ts +158 -0
  295. package/src/http/variable-sets-removed.ts +122 -0
  296. package/src/http/websocket-upgrade-dispatch.ts +29 -0
  297. package/src/index.ts +107 -57
  298. package/src/integration-connect-metrics.ts +228 -0
  299. package/src/integration-endpoint-test.ts +291 -0
  300. package/src/integrations/api-integrations.ts +81 -7
  301. package/src/integrations/atlassian.ts +202 -83
  302. package/src/integrations/fiken.ts +66 -38
  303. package/src/integrations/github-app-connect.ts +64 -18
  304. package/src/integrations/github-lens-connect.ts +8 -29
  305. package/src/integrations/google-drive.ts +448 -152
  306. package/src/integrations/integration-commit-authority.ts +69 -0
  307. package/src/integrations/marketplace-plugin.ts +1 -1
  308. package/src/integrations/oauth-client.ts +733 -193
  309. package/src/integrations/oauth-profiles.ts +116 -45
  310. package/src/integrations/onepassword-vault-provider.ts +16 -0
  311. package/src/integrations/personal-github-repositories.ts +1 -1
  312. package/src/integrations/personal-github.ts +61 -35
  313. package/src/integrations/provider-oauth.ts +67 -85
  314. package/src/integrations/slack-app-home.ts +5 -5
  315. package/src/integrations/slack-bot.ts +893 -201
  316. package/src/integrations/slack-file-upload-flow.ts +147 -0
  317. package/src/integrations/slack-install.ts +7 -7
  318. package/src/integrations/slack-interactions.ts +1589 -299
  319. package/src/integrations/slack-mrkdwn.ts +193 -0
  320. package/src/integrations/slack-routing.ts +2 -2
  321. package/src/integrations/slack-session-defaults.ts +224 -0
  322. package/src/integrations/slack-task-file-upload.ts +323 -0
  323. package/src/integrations/slack-tool-review.ts +65 -0
  324. package/src/integrations/social-oauth.ts +90 -58
  325. package/src/interaction-agent-access.ts +40 -20
  326. package/src/interaction-agent-version.ts +85 -0
  327. package/src/interaction-holder-heartbeat.ts +24 -8
  328. package/src/interaction-metrics.ts +164 -2
  329. package/src/interaction-route-stages.ts +94 -0
  330. package/src/interaction-secrets-vault.ts +213 -0
  331. package/src/interaction-secrets.ts +426 -0
  332. package/src/mcp/action-catalog-types.ts +14 -0
  333. package/src/mcp/action-catalog.gen.ts +8014 -0
  334. package/src/mcp/action-descriptions.ts +299 -0
  335. package/src/mcp/action-schema-imports.gen.ts +304 -0
  336. package/src/mcp/capability-account-readiness.ts +17 -0
  337. package/src/mcp/child-read-evidence.ts +19 -0
  338. package/src/mcp/company-profile-agent-admin.ts +13 -0
  339. package/src/mcp/contract-input.ts +165 -0
  340. package/src/mcp/documents.ts +9 -2
  341. package/src/mcp/editable-artifacts.ts +29 -11
  342. package/src/mcp/files.ts +5 -1
  343. package/src/mcp/first-party-tool-permissions.ts +7 -285
  344. package/src/mcp/knowledge-entries.ts +108 -34
  345. package/src/mcp/notification-tools.ts +480 -0
  346. package/src/mcp/orchestration-failure-diagnostic.ts +43 -0
  347. package/src/mcp/scheduled-task-input.ts +89 -0
  348. package/src/mcp/scheduled-task-view.ts +63 -3
  349. package/src/mcp/secret-tools.ts +185 -0
  350. package/src/mcp/server.ts +2213 -1116
  351. package/src/mcp/session-admin-access-tools.ts +163 -0
  352. package/src/mcp/session-event-errors.ts +123 -0
  353. package/src/mcp/session-event-view.ts +395 -13
  354. package/src/mcp/session-view.ts +3 -0
  355. package/src/mcp/session-wait.ts +67 -7
  356. package/src/mcp/site-agent-clamp-tools.ts +140 -0
  357. package/src/mcp/site-data-tools.ts +339 -0
  358. package/src/mcp/site-publication-tools.ts +201 -0
  359. package/src/mcp/site-sql-tools.ts +427 -0
  360. package/src/mcp-oauth.ts +635 -45
  361. package/src/model-calls/chat-completions.ts +352 -0
  362. package/src/model-calls/pending-usage-settlements.ts +125 -0
  363. package/src/model-calls/service.ts +694 -0
  364. package/src/model-catalog.ts +68 -17
  365. package/src/native-push-dispatch.ts +457 -0
  366. package/src/observability.ts +15 -1
  367. package/src/organization-mcp.ts +485 -0
  368. package/src/prepared-mcp-actor.ts +154 -0
  369. package/src/prepared-mcp-permissions.ts +38 -0
  370. package/src/routes/analytics-consent.ts +123 -0
  371. package/src/routes/api-integrations.ts +268 -9
  372. package/src/routes/api-keys.ts +164 -25
  373. package/src/routes/artifact-catalog.ts +111 -0
  374. package/src/routes/automations.ts +163 -90
  375. package/src/routes/billing.ts +535 -59
  376. package/src/routes/browser-sessions.ts +966 -342
  377. package/src/routes/canonical-human-identities.ts +61 -0
  378. package/src/routes/capabilities.ts +112 -5
  379. package/src/routes/channels.ts +4 -3
  380. package/src/routes/chat-completions.ts +235 -0
  381. package/src/routes/claude-core-usage.ts +147 -0
  382. package/src/routes/claude-subscription-accounts.ts +233 -0
  383. package/src/routes/claude-subscription-oauth.ts +175 -0
  384. package/src/routes/client-errors.ts +394 -0
  385. package/src/routes/codex-core.ts +687 -0
  386. package/src/routes/codex.ts +1277 -1119
  387. package/src/routes/company-brain.ts +4 -0
  388. package/src/routes/computer-sessions.ts +319 -85
  389. package/src/routes/connect.ts +277 -175
  390. package/src/routes/connections.ts +690 -363
  391. package/src/routes/desktop-distribution.ts +217 -0
  392. package/src/routes/documents.ts +13 -7
  393. package/src/routes/editable-artifacts.ts +74 -4
  394. package/src/routes/enrollments.ts +110 -16
  395. package/src/routes/external-identity-links.ts +13 -12
  396. package/src/routes/files.ts +41 -16
  397. package/src/routes/github-browser-fonts.gen.ts +12 -0
  398. package/src/routes/github-browser-pages.ts +258 -0
  399. package/src/routes/github.ts +200 -75
  400. package/src/routes/inbox.ts +353 -0
  401. package/src/routes/insights-response-cache.ts +83 -0
  402. package/src/routes/insights-usage.ts +345 -0
  403. package/src/routes/insights.ts +103 -8
  404. package/src/routes/install.ts +51 -10
  405. package/src/routes/integration-facets.ts +70 -28
  406. package/src/routes/interaction-placement-message.ts +12 -0
  407. package/src/routes/knowledge.ts +136 -27
  408. package/src/routes/machines.ts +189 -135
  409. package/src/routes/managed-auth-session-sets.ts +43 -10
  410. package/src/routes/managed-onboarding.ts +39 -19
  411. package/src/routes/managed-sign-in-methods.ts +433 -0
  412. package/src/routes/memory-slack-publications.ts +3 -2
  413. package/src/routes/model-account-choice.ts +194 -0
  414. package/src/routes/model-account-settings.ts +235 -0
  415. package/src/routes/model-connection-access.ts +210 -48
  416. package/src/routes/native-app-auth.ts +296 -0
  417. package/src/routes/organization-integration-policy.ts +116 -0
  418. package/src/routes/organization-integrations.ts +326 -0
  419. package/src/routes/organization-mcp-connections.ts +183 -0
  420. package/src/routes/organization-memberships.ts +205 -19
  421. package/src/routes/organization-model-defaults.ts +110 -0
  422. package/src/routes/organization-model-providers.ts +74 -3
  423. package/src/routes/organization-service-accounts.ts +128 -0
  424. package/src/routes/organization-sessions.ts +21 -5
  425. package/src/routes/personal-api-keys.ts +184 -0
  426. package/src/routes/personal-github-git-broker.ts +2 -2
  427. package/src/routes/personal-github.ts +9 -4
  428. package/src/routes/plugins.ts +46 -7
  429. package/src/routes/pr-review-github.ts +105 -118
  430. package/src/routes/pr-review.ts +88 -82
  431. package/src/routes/preference-registry.ts +18 -0
  432. package/src/routes/rigs.ts +48 -13
  433. package/src/routes/scheduled-tasks.ts +347 -27
  434. package/src/routes/session-admin-access.ts +271 -0
  435. package/src/routes/session-artifact-associations.ts +164 -0
  436. package/src/routes/session-history-imports.ts +305 -0
  437. package/src/routes/sessions.ts +1682 -363
  438. package/src/routes/site-data.ts +340 -0
  439. package/src/routes/site-publishing.ts +584 -0
  440. package/src/routes/site-runtime.ts +1944 -0
  441. package/src/routes/site-sql.ts +263 -0
  442. package/src/routes/skill-content.ts +72 -7
  443. package/src/routes/skills.ts +5 -4
  444. package/src/routes/slack-bot-access.ts +108 -0
  445. package/src/routes/social.ts +27 -15
  446. package/src/routes/subscription-account-pools.ts +209 -0
  447. package/src/routes/subscription-core-http.ts +18 -0
  448. package/src/routes/subscription-pool-access.ts +180 -0
  449. package/src/routes/subscription-pool-core.ts +552 -0
  450. package/src/routes/supergrok.ts +174 -655
  451. package/src/routes/transcription-recordings.ts +71 -4
  452. package/src/routes/transcriptions.ts +26 -2
  453. package/src/routes/usage-allowances.ts +381 -0
  454. package/src/routes/variable-sets-removed.ts +19 -0
  455. package/src/routes/vault-providers.ts +782 -0
  456. package/src/routes/vaults.ts +421 -0
  457. package/src/routes/video-generation.ts +30 -12
  458. package/src/routes/workspace-artifacts.ts +165 -53
  459. package/src/routes/workspace-integrations.ts +576 -0
  460. package/src/routes/workspace-model-providers.ts +148 -0
  461. package/src/routes/workspace-state.ts +4 -0
  462. package/src/routes/workspaces.ts +625 -117
  463. package/src/sandbox/agent-update.ts +315 -0
  464. package/src/sandbox/auth-callout.ts +104 -21
  465. package/src/sandbox/channel-a.ts +150 -49
  466. package/src/sandbox/enrollment.ts +131 -22
  467. package/src/sandbox/machine-audience.ts +192 -0
  468. package/src/sandbox/machines.ts +75 -31
  469. package/src/sandbox/metrics-ingestion.ts +133 -2
  470. package/src/sandbox/rematerialize.ts +51 -21
  471. package/src/sandbox/screen-control.ts +289 -0
  472. package/src/sandbox/viewer.ts +33 -58
  473. package/src/sandbox-file-artifacts.ts +53 -27
  474. package/src/scheduled-task-blockers.ts +293 -0
  475. package/src/scheduled-task-deletion.ts +13 -3
  476. package/src/scheduled-task-lifecycle.ts +84 -0
  477. package/src/secret-answers.ts +119 -0
  478. package/src/session-create-observability.ts +46 -0
  479. package/src/session-realtime-context.ts +26 -4
  480. package/src/signed-state-secret.ts +58 -0
  481. package/src/site-agent-guard.ts +261 -0
  482. package/src/site-data/operations.ts +777 -0
  483. package/src/site-data/service.ts +1043 -0
  484. package/src/site-frame-guard.ts +107 -0
  485. package/src/site-publishing/agent-data-tools.ts +250 -0
  486. package/src/site-publishing/agent-model.ts +127 -0
  487. package/src/site-publishing/agent-session.ts +148 -0
  488. package/src/site-publishing/content-scanner.ts +93 -0
  489. package/src/site-publishing/edge-sync.ts +148 -0
  490. package/src/site-publishing/gateway-signature.ts +198 -0
  491. package/src/site-publishing/limits.ts +315 -0
  492. package/src/site-publishing/page-tool-authority.ts +111 -0
  493. package/src/site-publishing/platform-usage.ts +479 -0
  494. package/src/site-publishing/platform.ts +98 -0
  495. package/src/site-publishing/readiness.ts +330 -0
  496. package/src/site-publishing/runtime.ts +831 -0
  497. package/src/site-publishing/service.ts +1282 -0
  498. package/src/site-publishing/session-events.ts +262 -0
  499. package/src/site-publishing/session-views.ts +262 -0
  500. package/src/site-publishing/viewer.ts +75 -0
  501. package/src/site-session-origin.ts +91 -6
  502. package/src/site-sql/format.ts +134 -0
  503. package/src/site-sql/gateway.ts +168 -0
  504. package/src/site-sql/service.ts +1636 -0
  505. package/src/site-uploads.ts +27 -5
  506. package/src/temporal-schedule-sync.ts +74 -0
  507. package/src/transcription/billing-refusal.ts +14 -0
  508. package/src/transcription/normalize.ts +77 -0
  509. package/src/transcription/providers/azure-mai.ts +98 -0
  510. package/src/transcription/providers/azure-openai.ts +16 -7
  511. package/src/transcription/providers/codex-subscription.ts +190 -67
  512. package/src/transcription/providers/openai.ts +79 -5
  513. package/src/transcription/providers/subscription-core.ts +125 -0
  514. package/src/transcription/providers/xai-subscription.ts +81 -83
  515. package/src/transcription/segmenter.ts +7 -1
  516. package/src/transcription/service.ts +239 -10
  517. package/src/transcription/wav-duration.ts +39 -0
  518. package/src/workspace-deletion.ts +25 -1
  519. package/src/workspace-tool-gateway-attestations.ts +133 -0
  520. package/src/workspace-tool-gateway-observability.ts +64 -0
  521. package/src/workspace-tool-gateway-secrets.ts +104 -0
  522. package/src/workspace-tool-gateway.ts +921 -103
  523. package/src/workspace-tool-target-fence.ts +380 -0
  524. package/src/workspace-tool-target.ts +554 -0
  525. package/src/workspace-webhook-dispatch.ts +225 -0
  526. package/src/xai-realtime.ts +85 -81
  527. package/src/xai-subscription-core.ts +172 -0
  528. package/dist/browser-auth-broker.d.ts +0 -20
  529. package/dist/chunk-AJ62T5QN.js.map +0 -1
  530. package/dist/connection-authority-owner.d.ts +0 -59
  531. package/dist/routes/connection-authorities.d.ts +0 -3
  532. package/dist/routes/environments.d.ts +0 -5
  533. package/dist/routes/host-mcp-bindings.d.ts +0 -5
  534. package/dist/routes/host-mcp-resolvers.d.ts +0 -3
  535. package/dist/routes/packs.d.ts +0 -3
  536. package/dist/session-event-view-MU3HMNV3.js +0 -264
  537. package/dist/session-event-view-MU3HMNV3.js.map +0 -1
  538. package/dist/xai-subscription-auth.d.ts +0 -23
  539. package/src/browser-auth-broker.ts +0 -155
  540. package/src/connection-authority-owner.ts +0 -61
  541. package/src/routes/connection-authorities.ts +0 -139
  542. package/src/routes/environments.ts +0 -388
  543. package/src/routes/host-mcp-bindings.ts +0 -122
  544. package/src/routes/host-mcp-resolvers.ts +0 -41
  545. package/src/routes/packs.ts +0 -784
  546. package/src/xai-subscription-auth.ts +0 -139
package/dist/index.js CHANGED
@@ -4,10 +4,18 @@ import {
4
4
  apiRequestBindingsForTransportPeer,
5
5
  cleanupScheduledTaskConnectorAuthorization,
6
6
  createAppComposition,
7
+ createAutomaticAgentUpdater,
8
+ createLocalBrowserBoundary,
7
9
  createOpenGeniSlackBotInteractionClient,
10
+ localBrowserBoundaryResponse,
11
+ reconcileScreenControlOnHello,
12
+ sql,
13
+ startApiMetricsListener,
8
14
  startSlackInteractionPump,
9
- startTemporalScheduleCleanupPump
10
- } from "./chunk-AJ62T5QN.js";
15
+ startTemporalScheduleCleanupPump,
16
+ withAccessGrantSessionRlsContext
17
+ } from "./chunk-GBUJ2DYZ.js";
18
+ import "./chunk-OGQDXVXY.js";
11
19
 
12
20
  // src/index.ts
13
21
  import { scheduledTaskKnowledgeSource } from "@opengeni/contracts";
@@ -23,6 +31,7 @@ import {
23
31
  } from "@opengeni/config";
24
32
  import {
25
33
  assertRuntimeDatabasePosture,
34
+ isRetryableRuntimeDatabaseStartupError,
26
35
  createDb,
27
36
  markSessionWorkflowWakeDelivered,
28
37
  runtimeDatabaseReadyCheck
@@ -33,11 +42,12 @@ import {
33
42
  logStartupDependencyRetry
34
43
  } from "@opengeni/observability";
35
44
  import { createObjectStorage } from "@opengeni/storage";
36
- import { createNativeRemoteMcpCredentialsPort } from "@opengeni/core/remote-mcp-credentials";
37
45
  import { isArtifactRuntimeConfigured } from "@opengeni/artifact-tool/runtime/development";
38
46
  import {
39
47
  resolveCatalogSettings,
40
- SESSION_WORKFLOW_WAKE_DISPATCHER_SCHEDULE_ID
48
+ SESSION_WORKFLOW_WAKE_DISPATCH_REQUESTED_SIGNAL,
49
+ SESSION_WORKFLOW_WAKE_DISPATCHER_WORKFLOW_ID,
50
+ SESSION_WORKFLOW_WAKE_DISPATCHER_WORKFLOW_TYPE
41
51
  } from "@opengeni/core";
42
52
  import {
43
53
  Connection,
@@ -48,6 +58,15 @@ import {
48
58
  } from "@temporalio/client";
49
59
 
50
60
  // src/observability.ts
61
+ import {
62
+ natsSubscriptionTerminationCounter
63
+ } from "@opengeni/events";
64
+ function observabilityEventBusOptions(observability) {
65
+ return {
66
+ logger: observabilityEventLogger(observability),
67
+ onSubscriptionTerminated: natsSubscriptionTerminationCounter(observability)
68
+ };
69
+ }
51
70
  function observabilityEventLogger(observability) {
52
71
  return {
53
72
  debug: (message, attributes) => observability.debug(message, eventAttributes(attributes)),
@@ -76,6 +95,8 @@ function eventAttributeValue(value) {
76
95
  }
77
96
 
78
97
  // src/sandbox/auth-callout.ts
98
+ import { createHash } from "crypto";
99
+ import { createLogThrottle } from "@opengeni/observability";
79
100
  import {
80
101
  resolveEnrollmentSigningSecret
81
102
  } from "@opengeni/config";
@@ -96,14 +117,43 @@ var AGENT_CONNECTION_LEASE_MS = 2e4;
96
117
  // src/sandbox/auth-callout.ts
97
118
  var AUTH_CALLOUT_SUBJECT = "$SYS.REQ.USER.AUTH";
98
119
  var NATS_USER_JWT_TTL_SECONDS = 5 * 60;
120
+ var AUTH_CALLOUT_DENIAL_WARNING_INTERVAL_MS = 6e4;
121
+ var processDenialWarningThrottle = createLogThrottle({
122
+ intervalMs: AUTH_CALLOUT_DENIAL_WARNING_INTERVAL_MS,
123
+ maxKeys: 1024
124
+ });
125
+ function warnDenial(deps, key, reason, message, attributes = {}) {
126
+ const admission = (deps.denialWarningThrottle ?? processDenialWarningThrottle).admit(key);
127
+ if (!admission) return;
128
+ deps.observability?.warn?.(message, {
129
+ ...attributes,
130
+ reason,
131
+ ...admission.suppressedCount > 0 ? { suppressedCount: admission.suppressedCount } : {}
132
+ });
133
+ }
134
+ function recordMachineConnect(deps, outcome, reason) {
135
+ try {
136
+ deps.observability?.incrementCounter({
137
+ name: "opengeni_machine_connect_total",
138
+ help: "Connected Machine agent NATS connect decisions (auth callout) by outcome and closed denial reason.",
139
+ labels: { outcome, reason }
140
+ });
141
+ } catch {
142
+ }
143
+ }
144
+ function rejectedBearerKey(bearer) {
145
+ return `invalid-bearer:${createHash("sha256").update(bearer).digest("hex").slice(0, 32)}`;
146
+ }
99
147
  async function handleAuthorizationRequest(deps, requestBytes) {
100
148
  const requestJwt = Buffer.from(requestBytes).toString("utf8");
101
149
  const decoded = decodeAuthRequest(requestJwt);
102
150
  if (!decoded) {
103
151
  deps.observability?.warn?.("auth-callout: undecodable authorization request", {});
152
+ recordMachineConnect(deps, "denied", "undecodable");
104
153
  throw new Error("undecodable authorization request");
105
154
  }
106
- const deny = (reason) => {
155
+ const deny = (code, reason) => {
156
+ recordMachineConnect(deps, "denied", code);
107
157
  const response2 = mintAuthResponse({
108
158
  userPublicKey: decoded.userNkey,
109
159
  serverId: decoded.serverId,
@@ -114,34 +164,42 @@ async function handleAuthorizationRequest(deps, requestBytes) {
114
164
  };
115
165
  const bearer = decoded.authToken;
116
166
  if (!bearer) {
117
- return deny("missing enrollment bearer");
167
+ return deny("missing_bearer", "missing enrollment bearer");
118
168
  }
119
169
  const secret = resolveEnrollmentSigningSecret(deps.settings);
120
170
  if (!secret) {
121
- return deny("enrollment credential plane disabled");
171
+ return deny("plane_disabled", "enrollment credential plane disabled");
122
172
  }
123
173
  const claims = await verifyEnrollmentBearer(secret, bearer);
124
174
  if (!claims) {
125
- deps.observability?.warn?.("auth-callout: rejected an invalid enrollment bearer", {});
126
- return deny("invalid or expired enrollment bearer");
175
+ warnDenial(
176
+ deps,
177
+ rejectedBearerKey(bearer),
178
+ "invalid_bearer",
179
+ "auth-callout: rejected an invalid enrollment bearer"
180
+ );
181
+ return deny("invalid_bearer", "invalid or expired enrollment bearer");
127
182
  }
128
183
  const enrollment = await getEnrollment(deps.db, claims.workspaceId, claims.enrollmentId);
129
184
  if (!enrollment || enrollment.status !== "active") {
130
- deps.observability?.warn?.("auth-callout: denied a revoked or unknown enrollment", {
131
- workspaceId: claims.workspaceId,
132
- agentId: claims.agentId
133
- });
134
- return deny("enrollment is not active");
185
+ warnDenial(
186
+ deps,
187
+ `inactive:${claims.workspaceId}:${claims.agentId}`,
188
+ "inactive_enrollment",
189
+ "auth-callout: denied a revoked or unknown enrollment",
190
+ { workspaceId: claims.workspaceId, agentId: claims.agentId }
191
+ );
192
+ return deny("inactive_enrollment", "enrollment is not active");
135
193
  }
136
194
  if (enrollment.workspaceId !== claims.workspaceId || enrollment.id !== claims.enrollmentId || enrollment.id !== claims.agentId || claims.agentId !== claims.enrollmentId || claims.subjectPrefix !== `agent.${claims.workspaceId}.${claims.agentId}`) {
137
- return deny("enrollment identity mismatch");
195
+ return deny("identity_mismatch", "enrollment identity mismatch");
138
196
  }
139
197
  if (enrollment.credentialGeneration !== claims.credentialGeneration) {
140
- return deny("enrollment credential generation mismatch");
198
+ return deny("generation_mismatch", "enrollment credential generation mismatch");
141
199
  }
142
200
  const connectionInstanceId = parseAgentConnectionName(decoded.name);
143
201
  if (!connectionInstanceId) {
144
- return deny("missing or invalid agent connection instance");
202
+ return deny("invalid_instance", "missing or invalid agent connection instance");
145
203
  }
146
204
  const claim = await claimEnrollmentConnection(deps.db, {
147
205
  workspaceId: claims.workspaceId,
@@ -151,11 +209,14 @@ async function handleAuthorizationRequest(deps, requestBytes) {
151
209
  leaseMs: AGENT_CONNECTION_LEASE_MS
152
210
  });
153
211
  if (!claim.claimed) {
154
- deps.observability?.warn?.("auth-callout: denied a duplicate live runner", {
155
- workspaceId: claims.workspaceId,
156
- agentId: claims.agentId
157
- });
158
- return deny("another runner instance currently owns this enrollment");
212
+ warnDenial(
213
+ deps,
214
+ `duplicate:${claims.workspaceId}:${claims.agentId}`,
215
+ "duplicate_runner",
216
+ "auth-callout: denied a duplicate live runner",
217
+ { workspaceId: claims.workspaceId, agentId: claims.agentId }
218
+ );
219
+ return deny("duplicate_runner", "another runner instance currently owns this enrollment");
159
220
  }
160
221
  const permissions = workspaceAgentPermissions(
161
222
  claims.workspaceId,
@@ -183,6 +244,7 @@ async function handleAuthorizationRequest(deps, requestBytes) {
183
244
  accountSeed: deps.callout.accountSeed,
184
245
  userJwt
185
246
  });
247
+ recordMachineConnect(deps, "granted", "none");
186
248
  deps.observability?.info?.("auth-callout: granted a workspace-scoped NATS credential", {
187
249
  workspaceId: claims.workspaceId,
188
250
  agentId: claims.agentId,
@@ -198,10 +260,10 @@ async function startAuthCalloutResponder(deps, natsUrl) {
198
260
  (bytes) => handleAuthorizationRequest(deps, bytes),
199
261
  {
200
262
  name: "opengeni-auth-callout",
201
- ...deps.observability ? { logger: observabilityEventLogger(deps.observability) } : {}
263
+ ...deps.observability ? observabilityEventBusOptions(deps.observability) : {}
202
264
  }
203
265
  );
204
- deps.observability?.info?.("OpenGeni NATS auth-callout responder started", {
266
+ deps.observability?.info?.("Opengeni NATS auth-callout responder started", {
205
267
  subject: AUTH_CALLOUT_SUBJECT
206
268
  });
207
269
  return connection;
@@ -212,6 +274,8 @@ import {
212
274
  clearEnrollmentWentOffline,
213
275
  disconnectAttachedBrowserDevices,
214
276
  advanceEnrollmentAgentUpdate,
277
+ enrollmentMacPermissionCapabilities,
278
+ enrollmentMacPermissions,
215
279
  getEnrollment as getEnrollment2,
216
280
  getLiveEnrollmentConnection,
217
281
  ingestMachineMetricsSample,
@@ -224,6 +288,7 @@ import {
224
288
  setEnrollmentOpStreamState
225
289
  } from "@opengeni/db";
226
290
  import {
291
+ AttachedBrowserConnectionEnd as AttachedBrowserConnectionEndContract,
227
292
  AttachedBrowserInventorySnapshot as AttachedBrowserInventoryContract
228
293
  } from "@opengeni/contracts";
229
294
  import { appendAndPublishEvents } from "@opengeni/events";
@@ -232,6 +297,7 @@ import {
232
297
  AgentEvent,
233
298
  AgentUpdateStage,
234
299
  Arch,
300
+ AttachedBrowserConnectionEndReason as WireConnectionEndReason,
235
301
  GoingOfflineReason,
236
302
  Hello,
237
303
  Os,
@@ -385,8 +451,42 @@ function wireAttachedBrowserInventoryToContract(wire) {
385
451
  ),
386
452
  tabCount: safeWireInteger(device.tabCount, "attached browser tab count"),
387
453
  capabilities: device.capabilities
388
- }))
454
+ })),
455
+ connectionEnds: (wire.connectionEnds ?? []).flatMap(wireConnectionEndToContract)
456
+ });
457
+ }
458
+ var WIRE_CONNECTION_END_REASONS = /* @__PURE__ */ new Map([
459
+ [
460
+ WireConnectionEndReason.ATTACHED_BROWSER_CONNECTION_END_REASON_BROWSER_CLOSED_PORT,
461
+ "browser_closed_port"
462
+ ],
463
+ [
464
+ WireConnectionEndReason.ATTACHED_BROWSER_CONNECTION_END_REASON_BROWSER_MESSAGE_INVALID,
465
+ "browser_message_invalid"
466
+ ],
467
+ [
468
+ WireConnectionEndReason.ATTACHED_BROWSER_CONNECTION_END_REASON_BROWSER_OUTPUT_FAILED,
469
+ "browser_output_failed"
470
+ ],
471
+ [
472
+ WireConnectionEndReason.ATTACHED_BROWSER_CONNECTION_END_REASON_NATIVE_HOST_LOST,
473
+ "native_host_lost"
474
+ ],
475
+ [WireConnectionEndReason.ATTACHED_BROWSER_CONNECTION_END_REASON_AGENT_REJECTED, "agent_rejected"]
476
+ ]);
477
+ function wireConnectionEndToContract(wire) {
478
+ const reason = WIRE_CONNECTION_END_REASONS.get(wire.reason);
479
+ const endedAtMs = Number(wire.endedAtUnixMs);
480
+ if (!reason || !Number.isSafeInteger(endedAtMs) || endedAtMs <= 0) return [];
481
+ const parsed = AttachedBrowserConnectionEndContract.safeParse({
482
+ deviceId: wire.deviceId,
483
+ connectionGeneration: wire.connectionGeneration,
484
+ reason,
485
+ detail: wire.detail.slice(0, 512) || null,
486
+ endedAt: new Date(endedAtMs).toISOString(),
487
+ connectedMs: Math.min(Number(wire.connectedMs) || 0, Number.MAX_SAFE_INTEGER)
389
488
  });
489
+ return parsed.success ? [parsed.data] : [];
390
490
  }
391
491
  function safeWireInteger(value, label) {
392
492
  let parsed;
@@ -462,7 +562,7 @@ async function fanOutMachineLinkEvents(db, bus, observability, workspaceId, enro
462
562
  }
463
563
  }
464
564
  }
465
- async function handleAgentEventPayload(db, observability, payload, subject, bus) {
565
+ async function handleAgentEventPayload(db, observability, payload, subject, bus, agentUpdates) {
466
566
  const ids = parseAgentEventSubject(subject);
467
567
  if (!ids) {
468
568
  return;
@@ -608,6 +708,7 @@ async function handleAgentEventPayload(db, observability, payload, subject, bus)
608
708
  });
609
709
  return;
610
710
  }
711
+ agentUpdates?.consider(enrollment);
611
712
  if (heartbeat.attachedBrowserInventory) {
612
713
  try {
613
714
  await ingestAttachedBrowserInventory(db, {
@@ -622,6 +723,31 @@ async function handleAgentEventPayload(db, observability, payload, subject, bus)
622
723
  });
623
724
  }
624
725
  }
726
+ if (heartbeat.desktopStatus) {
727
+ try {
728
+ const status = heartbeat.desktopStatus;
729
+ const available = status.available && !status.unavailableReason;
730
+ const reason = status.unavailableReason || null;
731
+ const macPermissions = status.macPermissions;
732
+ const stored = enrollmentMacPermissions(enrollment.agentCapabilities);
733
+ if (enrollment.hasDisplay !== available || (enrollment.desktopUnavailableReason ?? null) !== reason || enrollment.agentCapabilities?.desktop !== available || macPermissions !== void 0 && (stored?.screenRecording !== macPermissions.screenRecording || stored?.accessibility !== macPermissions.accessibility || stored?.inputMonitoring !== macPermissions.inputMonitoring)) {
734
+ await refreshEnrollmentDisplay(db, {
735
+ workspaceId: ids.workspaceId,
736
+ agentId: ids.agentId,
737
+ hasDisplay: available,
738
+ desktopUnavailableReason: reason,
739
+ runtimeDesktop: available,
740
+ ...macPermissions ? { macPermissions } : {},
741
+ connectionInstanceId: ids.connectionInstanceId
742
+ });
743
+ }
744
+ } catch (error) {
745
+ observability?.warn?.("Failed to refresh a machine desktop heartbeat", {
746
+ subject,
747
+ error: error instanceof Error ? error.message : String(error)
748
+ });
749
+ }
750
+ }
625
751
  const metrics = heartbeat.metrics;
626
752
  if (!metrics) return;
627
753
  try {
@@ -640,7 +766,14 @@ async function handleAgentEventPayload(db, observability, payload, subject, bus)
640
766
  }
641
767
  function startMetricsIngestion(deps) {
642
768
  const scheduler = createAgentEventIngestionScheduler(
643
- (payload, subject) => handleAgentEventPayload(deps.db, deps.observability, payload, subject, deps.bus),
769
+ (payload, subject) => handleAgentEventPayload(
770
+ deps.db,
771
+ deps.observability,
772
+ payload,
773
+ subject,
774
+ deps.bus,
775
+ deps.agentUpdates
776
+ ),
644
777
  {
645
778
  onHeartbeatCoalesced: () => deps.observability?.incrementCounter({
646
779
  name: "opengeni_machine_heartbeat_coalesced_total",
@@ -686,7 +819,10 @@ function helloRuntimeCapabilities(hello) {
686
819
  browserBridge: caps.browserBridge === true,
687
820
  operationResourcePolicy: caps.operationResourcePolicy === true,
688
821
  operationCpuQuota: caps.operationCpuQuota === true,
689
- transactionalFsWrite: caps.transactionalFsWrite === true
822
+ transactionalFsWrite: caps.transactionalFsWrite === true,
823
+ credentialRenew: caps.credentialRenew === true,
824
+ screenControl: caps.consentedScreenControl === true,
825
+ ...caps.macPermissions ? enrollmentMacPermissionCapabilities(caps.macPermissions) : {}
690
826
  };
691
827
  }
692
828
  function helloCompletedUpdate(hello) {
@@ -707,7 +843,11 @@ async function refreshEnrollmentDisplay(db, input) {
707
843
  if (!enrollment) {
708
844
  return { updated: false };
709
845
  }
710
- if (enrollment.hasDisplay === input.hasDisplay && (enrollment.desktopUnavailableReason ?? null) === desktopUnavailableReason) {
846
+ if (enrollment.hasDisplay === input.hasDisplay && (enrollment.desktopUnavailableReason ?? null) === desktopUnavailableReason && (input.runtimeDesktop === void 0 || enrollment.agentCapabilities?.desktop === input.runtimeDesktop) && (input.macPermissions === void 0 || JSON.stringify(enrollmentMacPermissions(enrollment.agentCapabilities)) === JSON.stringify({
847
+ screenRecording: input.macPermissions.screenRecording,
848
+ accessibility: input.macPermissions.accessibility,
849
+ inputMonitoring: input.macPermissions.inputMonitoring
850
+ }))) {
711
851
  return { updated: false };
712
852
  }
713
853
  return await setEnrollmentDisplayState(db, {
@@ -716,6 +856,8 @@ async function refreshEnrollmentDisplay(db, input) {
716
856
  enrollmentId: input.agentId,
717
857
  hasDisplay: input.hasDisplay,
718
858
  desktopUnavailableReason,
859
+ ...input.runtimeDesktop !== void 0 ? { runtimeDesktop: input.runtimeDesktop } : {},
860
+ ...input.macPermissions ? { macPermissions: input.macPermissions } : {},
719
861
  ...input.connectionInstanceId ? { connectionInstanceId: input.connectionInstanceId } : {}
720
862
  });
721
863
  }
@@ -804,6 +946,18 @@ async function handleHelloPayload(db, observability, payload, subject, bus) {
804
946
  error: error instanceof Error ? error.message : String(error)
805
947
  });
806
948
  }
949
+ void reconcileScreenControlOnHello(
950
+ { bus, observability },
951
+ {
952
+ authority,
953
+ hello,
954
+ target: {
955
+ workspaceId: ids.workspaceId,
956
+ enrollmentId: ids.agentId,
957
+ connectionInstanceId: ids.connectionInstanceId
958
+ }
959
+ }
960
+ );
807
961
  try {
808
962
  const { cleared } = await clearEnrollmentWentOffline(db, {
809
963
  accountId: authority.accountId,
@@ -1006,6 +1160,816 @@ function escapeSlackText(value) {
1006
1160
  return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
1007
1161
  }
1008
1162
 
1163
+ // src/scheduled-task-lifecycle.ts
1164
+ import {
1165
+ acknowledgeScheduledTaskScheduleReconciliation,
1166
+ deferScheduledTaskScheduleReconciliation,
1167
+ listExhaustedScheduledTasks,
1168
+ listScheduledTaskScheduleReconciliations,
1169
+ retireExhaustedScheduledTask
1170
+ } from "@opengeni/db";
1171
+
1172
+ // src/scheduled-task-blockers.ts
1173
+ import {
1174
+ NOTIFICATION_SUBTITLE_MAX_CHARS,
1175
+ SessionNotificationPostedPayload,
1176
+ SessionNotificationWithdrawnPayload
1177
+ } from "@opengeni/contracts";
1178
+ import {
1179
+ getPendingScheduledTaskBlockerNotice,
1180
+ listScheduledTaskBlockerReviews,
1181
+ reviewScheduledTaskBlockers,
1182
+ settleScheduledTaskBlockerNotice
1183
+ } from "@opengeni/db";
1184
+ import { appendAndPublishEvents as appendAndPublishEvents2 } from "@opengeni/events";
1185
+ var SCHEDULED_TASK_BLOCKER_STREAK = 3;
1186
+ var SETTLED = /* @__PURE__ */ new Set(["succeeded", "failed", "skipped"]);
1187
+ var RESUME = "then resume the schedule.";
1188
+ var NON_RETRYABLE_REFUSAL_TEXT = {
1189
+ scheduled_authority_unavailable: `The access it was set up with is no longer valid. Refresh the schedule's access, ${RESUME}`,
1190
+ machine_target_unavailable: `The machine it runs on is no longer available. Choose another machine or reconnect this one, ${RESUME}`,
1191
+ machine_enrollment_inactive: `The machine it runs on is disconnected from Opengeni. Reconnect the machine, ${RESUME}`,
1192
+ variable_set_unavailable: `A variable set it uses no longer exists or can't be used. Edit the schedule, ${RESUME}`,
1193
+ rig_version_unavailable: `Its rig has no usable version. Publish a rig version or edit the schedule, ${RESUME}`,
1194
+ scheduled_model_unavailable: `Its model is no longer available. Edit the schedule to choose another model, ${RESUME}`
1195
+ };
1196
+ var RETRYABLE_REFUSAL_TEXT = {
1197
+ machine_enrollment_inactive: "the machine it runs on is disconnected",
1198
+ insufficient_credits: "the workspace is out of credits",
1199
+ allowance_exhausted: "the usage allowance is used up",
1200
+ monthly_model_cost_limit: "the monthly model cost limit was reached",
1201
+ monthly_agent_run_limit: "the monthly run limit was reached",
1202
+ session_usage_limit: "the schedule reached its usage limit"
1203
+ };
1204
+ function scheduledRunHardBlocker(task, run) {
1205
+ if (run.admissionDiagnostic) {
1206
+ return {
1207
+ reason: "connection_account_unavailable",
1208
+ text: `A connected account it needs can no longer be used. Reconnect that account or refresh the schedule's access, ${RESUME}`
1209
+ };
1210
+ }
1211
+ if (run.admissionRefusal && !run.admissionRefusal.retryable) {
1212
+ const reason = run.admissionRefusal.reason;
1213
+ return {
1214
+ reason,
1215
+ text: NON_RETRYABLE_REFUSAL_TEXT[reason] ?? `Every run is refused before it starts (${reason}). Fix that, ${RESUME}`
1216
+ };
1217
+ }
1218
+ switch (run.error) {
1219
+ case "scheduled_session_paused":
1220
+ return {
1221
+ reason: run.error,
1222
+ text: `Its chat is paused, so no run can start there. Resume the chat, ${RESUME}`
1223
+ };
1224
+ case "session_cancelled":
1225
+ return task.runMode === "new_session_per_run" ? null : {
1226
+ reason: run.error,
1227
+ text: `The chat it runs in has ended, so no run can start there. Point the schedule at another chat or let it start a new chat each run, ${RESUME}`
1228
+ };
1229
+ case "scheduled_target_session_unavailable":
1230
+ return {
1231
+ reason: run.error,
1232
+ text: `The chat it runs in no longer exists. Point the schedule at another chat or let it start a new chat each run, ${RESUME}`
1233
+ };
1234
+ case "scheduled_model_unavailable":
1235
+ return { reason: run.error, text: NON_RETRYABLE_REFUSAL_TEXT.scheduled_model_unavailable };
1236
+ default:
1237
+ return null;
1238
+ }
1239
+ }
1240
+ function streakBlocker(streak) {
1241
+ const count = streak.length;
1242
+ if (streak.every((run) => run.error === "scheduled_session_not_idle")) {
1243
+ return {
1244
+ reason: "scheduled_session_not_idle",
1245
+ text: `The last ${count} scheduled runs were skipped because its chat was still busy with earlier work. Check whether that chat is stuck. The schedule keeps trying.`
1246
+ };
1247
+ }
1248
+ const refusal = streak[0]?.admissionRefusal;
1249
+ if (refusal?.retryable && streak.every(
1250
+ (run) => run.admissionRefusal?.retryable && run.admissionRefusal.reason === refusal.reason
1251
+ )) {
1252
+ return {
1253
+ reason: refusal.reason,
1254
+ text: `The last ${count} scheduled runs were skipped because ${RETRYABLE_REFUSAL_TEXT[refusal.reason] ?? `they were refused (${refusal.reason})`}. Runs start again once that clears.`
1255
+ };
1256
+ }
1257
+ const latest = streak[0];
1258
+ return {
1259
+ reason: latest.error ?? latest.status,
1260
+ text: `The last ${count} scheduled runs failed or were skipped. Open the latest run to see why. The schedule keeps trying.`
1261
+ };
1262
+ }
1263
+ function scheduledTaskBlockerNoticeKey(taskId) {
1264
+ return `schedule-${taskId}`;
1265
+ }
1266
+ function clip(text, max) {
1267
+ return text.length <= max ? text : `${text.slice(0, max - 1).trimEnd()}\u2026`;
1268
+ }
1269
+ function notice(facts, run, state, blocker) {
1270
+ const sessionId = run.sessionId ?? facts.task.reusableSessionId ?? facts.runs.find((candidate) => candidate.sessionId !== null)?.sessionId ?? null;
1271
+ if (!sessionId) return null;
1272
+ const key = scheduledTaskBlockerNoticeKey(facts.task.id);
1273
+ const payload = SessionNotificationPostedPayload.parse({
1274
+ key,
1275
+ title: state === "paused" ? "Schedule paused itself" : "Schedule needs attention",
1276
+ subtitle: clip(facts.task.name.trim() || "Scheduled task", NOTIFICATION_SUBTITLE_MAX_CHARS),
1277
+ body: state === "paused" ? `${blocker.text} It stays paused until someone resumes it.` : blocker.text,
1278
+ urgency: "normal",
1279
+ replaced: false
1280
+ });
1281
+ return { sessionId, key, payload };
1282
+ }
1283
+ function decideScheduledTaskBlocker(facts) {
1284
+ const { task, review, runs } = facts;
1285
+ const blocked = review !== null && review.state !== "clear";
1286
+ if (task.deleted) return blocked ? { kind: "clear", reviewedRunId: null } : none(null);
1287
+ if (review?.state === "paused" && task.status === "active") {
1288
+ return { kind: "clear", reviewedRunId: null };
1289
+ }
1290
+ if (review?.state === "notified" && task.status !== "active") {
1291
+ return { kind: "clear", reviewedRunId: null };
1292
+ }
1293
+ if (task.status !== "active") return none(null);
1294
+ const latest = runs[0];
1295
+ if (!latest || !SETTLED.has(latest.status) || latest.id === review?.reviewedRunId) {
1296
+ return none(null);
1297
+ }
1298
+ if (latest.status === "succeeded") {
1299
+ return review?.state === "notified" ? { kind: "clear", reviewedRunId: latest.id } : none(latest.id);
1300
+ }
1301
+ const current = (run) => run.createdAt >= task.updatedAt;
1302
+ const hard = current(latest) ? scheduledRunHardBlocker(task, latest) : null;
1303
+ if (hard) {
1304
+ return {
1305
+ kind: "block",
1306
+ state: "paused",
1307
+ reason: hard.reason,
1308
+ message: hard.text,
1309
+ blockedRunId: latest.id,
1310
+ blockedSince: latest.createdAt,
1311
+ reviewedRunId: latest.id,
1312
+ notice: notice(facts, latest, "paused", hard)
1313
+ };
1314
+ }
1315
+ if (review?.state === "notified") return none(latest.id);
1316
+ const streak = [];
1317
+ for (const run of runs) {
1318
+ if (!SETTLED.has(run.status) || run.status === "succeeded" || !current(run)) break;
1319
+ streak.push(run);
1320
+ }
1321
+ if (streak.length < SCHEDULED_TASK_BLOCKER_STREAK) return none(latest.id);
1322
+ const soft = streakBlocker(streak);
1323
+ const first = streak.at(-1);
1324
+ return {
1325
+ kind: "block",
1326
+ state: "notified",
1327
+ reason: soft.reason,
1328
+ message: soft.text,
1329
+ blockedRunId: first.id,
1330
+ blockedSince: first.createdAt,
1331
+ reviewedRunId: latest.id,
1332
+ notice: notice(facts, latest, "notified", soft)
1333
+ };
1334
+ }
1335
+ function none(reviewedRunId) {
1336
+ return { kind: "none", reviewedRunId };
1337
+ }
1338
+ function sessionMissing(error) {
1339
+ return error instanceof Error && /^Session not found: /u.test(error.message);
1340
+ }
1341
+ async function deliverScheduledTaskBlockerNotice(deps, workspaceId, taskId) {
1342
+ const pending = await getPendingScheduledTaskBlockerNotice(deps.db, workspaceId, taskId);
1343
+ if (!pending) return;
1344
+ try {
1345
+ await appendAndPublishEvents2(deps.db, deps.bus, workspaceId, pending.sessionId, [
1346
+ pending.pending === "post" ? {
1347
+ type: "session.notification.posted",
1348
+ payload: SessionNotificationPostedPayload.parse(pending.payload)
1349
+ } : {
1350
+ type: "session.notification.withdrawn",
1351
+ payload: SessionNotificationWithdrawnPayload.parse({ key: pending.key })
1352
+ }
1353
+ ]);
1354
+ } catch (error) {
1355
+ if (!sessionMissing(error)) throw error;
1356
+ }
1357
+ await settleScheduledTaskBlockerNotice(deps.db, workspaceId, taskId, pending);
1358
+ }
1359
+ async function reviewScheduledTaskBlockerInventory(deps) {
1360
+ for (const candidate of await listScheduledTaskBlockerReviews(deps.db)) {
1361
+ try {
1362
+ await reviewScheduledTaskBlockers(
1363
+ deps.db,
1364
+ candidate.workspace_id,
1365
+ candidate.id,
1366
+ decideScheduledTaskBlocker
1367
+ );
1368
+ await deliverScheduledTaskBlockerNotice(deps, candidate.workspace_id, candidate.id);
1369
+ } catch (error) {
1370
+ deps.observability?.error("Scheduled task blocker review will retry", {
1371
+ taskId: candidate.id,
1372
+ error: error instanceof Error ? error.message : String(error)
1373
+ });
1374
+ }
1375
+ }
1376
+ }
1377
+
1378
+ // src/scheduled-task-lifecycle.ts
1379
+ async function reconcileExhaustedScheduledTasks(deps) {
1380
+ for (const candidate of await listExhaustedScheduledTasks(deps.db)) {
1381
+ await retireExhaustedScheduledTask(deps.db, candidate.workspace_id, candidate.id);
1382
+ }
1383
+ if (deps.bus) {
1384
+ await reviewScheduledTaskBlockerInventory({
1385
+ db: deps.db,
1386
+ bus: deps.bus,
1387
+ observability: deps.observability
1388
+ });
1389
+ }
1390
+ for (const pending of await listScheduledTaskScheduleReconciliations(deps.db)) {
1391
+ try {
1392
+ await deps.sync({
1393
+ id: pending.task_id,
1394
+ workspaceId: pending.workspace_id,
1395
+ temporalScheduleId: pending.temporal_schedule_id
1396
+ });
1397
+ await acknowledgeScheduledTaskScheduleReconciliation(
1398
+ deps.db,
1399
+ pending.workspace_id,
1400
+ pending.id
1401
+ );
1402
+ } catch (error) {
1403
+ await deferScheduledTaskScheduleReconciliation(deps.db, pending.workspace_id, pending.id);
1404
+ deps.observability?.error("Exhausted schedule reconciliation will retry", {
1405
+ taskId: pending.task_id,
1406
+ error: error instanceof Error ? error.message : String(error)
1407
+ });
1408
+ }
1409
+ }
1410
+ }
1411
+ function startScheduledTaskLifecyclePump(deps, intervalMs = 3e4) {
1412
+ let stopped = false;
1413
+ let running;
1414
+ const tick = () => {
1415
+ if (stopped || running) return;
1416
+ running = reconcileExhaustedScheduledTasks(deps).catch((error) => {
1417
+ deps.observability?.error("Exhausted schedule inventory failed", {
1418
+ error: error instanceof Error ? error.message : String(error)
1419
+ });
1420
+ }).finally(() => {
1421
+ running = void 0;
1422
+ });
1423
+ };
1424
+ const timer = setInterval(tick, intervalMs);
1425
+ queueMicrotask(tick);
1426
+ return async () => {
1427
+ stopped = true;
1428
+ clearInterval(timer);
1429
+ await running;
1430
+ };
1431
+ }
1432
+
1433
+ // src/temporal-schedule-sync.ts
1434
+ import { getScheduledTaskIncludingDeleted } from "@opengeni/db";
1435
+ var LOCK_TIMEOUT = "5s";
1436
+ var WRITE_TIMEOUT_MS = 5e3;
1437
+ function createTemporalScheduleSynchronizer(deps) {
1438
+ const write = async (temporalScheduleId, work) => deps.db.transaction(async (tx) => {
1439
+ await tx.execute(sql`select set_config('lock_timeout', ${LOCK_TIMEOUT}, true)`);
1440
+ await tx.execute(sql`select pg_advisory_xact_lock(
1441
+ hashtextextended(${`scheduled-task-temporal:${temporalScheduleId}`}, 0)
1442
+ )`);
1443
+ return await work(tx);
1444
+ });
1445
+ return {
1446
+ async sync(task, onFailure) {
1447
+ const result = await write(task.temporalScheduleId, async (tx) => {
1448
+ try {
1449
+ await deps.withDeadline(Date.now() + WRITE_TIMEOUT_MS, async () => {
1450
+ const current = await getScheduledTaskIncludingDeleted(tx, task.workspaceId, task.id);
1451
+ if (current && current.temporalScheduleId !== task.temporalScheduleId)
1452
+ throw new Error("Scheduled task Temporal identity changed");
1453
+ if (!current || current.deletedAt !== null || current.schedule.type === "manual") {
1454
+ await deps.remove(task.temporalScheduleId);
1455
+ return;
1456
+ }
1457
+ const { deletedAt: _deletedAt, ...latest } = current;
1458
+ await deps.upsert(latest);
1459
+ });
1460
+ return { ok: true };
1461
+ } catch (error) {
1462
+ return { ok: false, error: onFailure ? await onFailure(tx, error) : error };
1463
+ }
1464
+ });
1465
+ if (!result.ok) throw result.error;
1466
+ },
1467
+ async remove(temporalScheduleId) {
1468
+ await write(
1469
+ temporalScheduleId,
1470
+ async () => deps.withDeadline(Date.now() + WRITE_TIMEOUT_MS, () => deps.remove(temporalScheduleId))
1471
+ );
1472
+ }
1473
+ };
1474
+ }
1475
+
1476
+ // src/native-push-dispatch.ts
1477
+ import { createSign, randomUUID } from "crypto";
1478
+ import { connect } from "http2";
1479
+ import {
1480
+ claimNativePushDeliveries,
1481
+ listSessionHumanInputRequests,
1482
+ pruneNativePushDeliveries,
1483
+ settleNativePushDelivery
1484
+ } from "@opengeni/db";
1485
+ var POLL_INTERVAL_MS = 1e3;
1486
+ var CLAIM_SECONDS = 60;
1487
+ var MAX_ATTEMPTS = 6;
1488
+ var PRUNE_EVERY_TICKS = 600;
1489
+ var SECRET_QUESTION_PUSH_BODY = "Open Opengeni to answer. The answer is saved to a vault.";
1490
+ async function markSecretQuestionDeliveries(db, deliveries) {
1491
+ const secretSessions = /* @__PURE__ */ new Map();
1492
+ return await Promise.all(
1493
+ deliveries.map(async (delivery) => {
1494
+ if (delivery.payload.eventType !== "session.humanInput.requested") return delivery;
1495
+ const key = `${delivery.payload.workspaceId}:${delivery.payload.sessionId}`;
1496
+ let lookup = secretSessions.get(key);
1497
+ if (!lookup) {
1498
+ lookup = listSessionHumanInputRequests(
1499
+ db,
1500
+ delivery.payload.workspaceId,
1501
+ delivery.payload.sessionId,
1502
+ { status: "pending", limit: 20 }
1503
+ ).then(
1504
+ (requests) => requests.some(
1505
+ (request) => request.questions.some((question) => question.kind === "secret")
1506
+ )
1507
+ ).catch(() => false);
1508
+ secretSessions.set(key, lookup);
1509
+ }
1510
+ if (!await lookup) return delivery;
1511
+ const payload = { ...delivery.payload, secretQuestion: true };
1512
+ return { ...delivery, payload };
1513
+ })
1514
+ );
1515
+ }
1516
+ function plainNotificationText(markdown) {
1517
+ return markdown.replace(/\[([^\]]+)\]\((?:[^)]+)\)/gu, "$1").replace(/(\*\*|__)(.+?)\1/gu, "$2").replace(/`([^`]+)`/gu, "$1").replace(/^[ \t]*[-*][ \t]+/gmu, "\u2022 ").replace(/\n{3,}/gu, "\n\n").trim();
1518
+ }
1519
+ function nativePushAlert(payload) {
1520
+ if (payload.secretQuestion) {
1521
+ return { title: payload.title ?? "The agent needs a secret", body: SECRET_QUESTION_PUSH_BODY };
1522
+ }
1523
+ const fallback = payload.rule === "needs_input" ? "The agent needs you" : payload.rule === "failed" ? "A turn failed" : payload.rule === "reply_ready" ? "Reply ready" : "Opengeni";
1524
+ const facts = (payload.facts ?? []).slice(0, 4).map((fact) => `${fact.label}: ${fact.value}`).join("\n");
1525
+ const message = payload.body ? plainNotificationText(payload.body) : "";
1526
+ const lines = [message === payload.title ? "" : message, facts].filter(Boolean);
1527
+ const body = lines.join("\n\n");
1528
+ return {
1529
+ title: payload.title ?? fallback,
1530
+ ...payload.subtitle ? { subtitle: payload.subtitle } : {},
1531
+ ...body || payload.rule !== "agent" ? { body: body || fallback } : {}
1532
+ };
1533
+ }
1534
+ function nativePushInterruptionLevel(payload) {
1535
+ if ((payload.rule === "agent" || payload.rule === "needs_input") && payload.urgency === "time_sensitive") {
1536
+ return "time-sensitive";
1537
+ }
1538
+ return "active";
1539
+ }
1540
+ function nativePushData(payload) {
1541
+ return {
1542
+ sessionId: payload.sessionId,
1543
+ workspaceId: payload.workspaceId,
1544
+ subjectId: payload.subjectId,
1545
+ rule: payload.rule,
1546
+ ...payload.eventType ? { eventType: payload.eventType } : {},
1547
+ ...payload.sequence ? { sequence: payload.sequence } : {}
1548
+ };
1549
+ }
1550
+ function nativePushCategory(payload) {
1551
+ if (payload.secretQuestion) return void 0;
1552
+ if (payload.eventType === "session.requiresAction") return "og.approval";
1553
+ if (payload.eventType === "session.humanInput.requested") return "og.question";
1554
+ return void 0;
1555
+ }
1556
+ function base64Url(input) {
1557
+ return Buffer.from(input).toString("base64url");
1558
+ }
1559
+ function signJwt(header, claims, privateKey, algorithm) {
1560
+ const unsigned = `${base64Url(JSON.stringify(header))}.${base64Url(JSON.stringify(claims))}`;
1561
+ const signer = createSign("SHA256");
1562
+ signer.update(unsigned);
1563
+ const signature = algorithm === "ES256" ? signer.sign({ key: privateKey, dsaEncoding: "ieee-p1363" }) : signer.sign(privateKey);
1564
+ return `${unsigned}.${base64Url(signature)}`;
1565
+ }
1566
+ function pem(value) {
1567
+ return value.includes("\\n") ? value.replace(/\\n/gu, "\n") : value;
1568
+ }
1569
+ function createApnsSender(input) {
1570
+ const key = pem(input.privateKey);
1571
+ let token = null;
1572
+ const sessions = /* @__PURE__ */ new Map();
1573
+ const bearer = () => {
1574
+ const now = Math.floor(Date.now() / 1e3);
1575
+ if (!token || now - token.issuedAt > 40 * 60) {
1576
+ token = {
1577
+ value: signJwt(
1578
+ { alg: "ES256", kid: input.keyId },
1579
+ { iss: input.teamId, iat: now },
1580
+ key,
1581
+ "ES256"
1582
+ ),
1583
+ issuedAt: now
1584
+ };
1585
+ }
1586
+ return token.value;
1587
+ };
1588
+ const session = (host) => {
1589
+ const existing = sessions.get(host);
1590
+ if (existing && !existing.closed && !existing.destroyed) return existing;
1591
+ const created = connect(`https://${host}`);
1592
+ created.on("error", () => sessions.delete(host));
1593
+ created.on("close", () => sessions.delete(host));
1594
+ sessions.set(host, created);
1595
+ return created;
1596
+ };
1597
+ return async (delivery) => {
1598
+ const host = delivery.environment === "development" ? "api.sandbox.push.apple.com" : "api.push.apple.com";
1599
+ const alert = nativePushAlert(delivery.payload);
1600
+ const category = nativePushCategory(delivery.payload);
1601
+ const body = JSON.stringify({
1602
+ aps: {
1603
+ alert,
1604
+ sound: "default",
1605
+ "thread-id": delivery.payload.sessionId,
1606
+ "interruption-level": nativePushInterruptionLevel(delivery.payload),
1607
+ ...category ? { category } : {}
1608
+ },
1609
+ body: nativePushData(delivery.payload)
1610
+ });
1611
+ return await new Promise((resolve) => {
1612
+ const request = session(host).request({
1613
+ ":method": "POST",
1614
+ ":path": `/3/device/${delivery.token}`,
1615
+ authorization: `bearer ${bearer()}`,
1616
+ "apns-topic": delivery.appId,
1617
+ "apns-push-type": "alert",
1618
+ "apns-priority": "10",
1619
+ "apns-collapse-id": `${delivery.payload.sessionId}:${delivery.payload.rule}`.slice(0, 64),
1620
+ "content-type": "application/json"
1621
+ });
1622
+ let status = 0;
1623
+ let text = "";
1624
+ request.setTimeout(1e4, () => request.close());
1625
+ request.on("response", (headers) => {
1626
+ status = Number(headers[":status"] ?? 0);
1627
+ });
1628
+ request.on("data", (chunk) => {
1629
+ text += chunk.toString("utf8");
1630
+ });
1631
+ request.on("error", (error) => resolve({ outcome: "retry", error: String(error) }));
1632
+ request.on("close", () => {
1633
+ if (status === 200) return resolve({ outcome: "delivered" });
1634
+ const reason = (() => {
1635
+ try {
1636
+ return JSON.parse(text).reason ?? text;
1637
+ } catch {
1638
+ return text || `status ${status}`;
1639
+ }
1640
+ })();
1641
+ if (status === 410 || reason === "BadDeviceToken" || reason === "Unregistered") {
1642
+ return resolve({ outcome: "unregistered", error: `${status} ${reason}` });
1643
+ }
1644
+ resolve({
1645
+ outcome: status === 429 || status >= 500 || status === 0 ? "retry" : "failed",
1646
+ error: `${status} ${reason}`
1647
+ });
1648
+ });
1649
+ request.end(body);
1650
+ });
1651
+ };
1652
+ }
1653
+ function createFcmSender(input) {
1654
+ const account = JSON.parse(input.serviceAccountJson);
1655
+ const http = input.fetch ?? fetch;
1656
+ let access = null;
1657
+ const accessToken = async () => {
1658
+ if (access && access.expiresAt - 6e4 > Date.now()) return access.token;
1659
+ const now = Math.floor(Date.now() / 1e3);
1660
+ const tokenUri = account.token_uri ?? "https://oauth2.googleapis.com/token";
1661
+ const assertion = signJwt(
1662
+ { alg: "RS256", typ: "JWT" },
1663
+ {
1664
+ iss: account.client_email,
1665
+ scope: "https://www.googleapis.com/auth/firebase.messaging",
1666
+ aud: tokenUri,
1667
+ iat: now,
1668
+ exp: now + 3600
1669
+ },
1670
+ pem(account.private_key),
1671
+ "RS256"
1672
+ );
1673
+ const response = await http(tokenUri, {
1674
+ method: "POST",
1675
+ headers: { "content-type": "application/x-www-form-urlencoded" },
1676
+ body: new URLSearchParams({
1677
+ grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
1678
+ assertion
1679
+ })
1680
+ });
1681
+ if (!response.ok) throw new Error(`FCM token exchange failed: ${response.status}`);
1682
+ const json = await response.json();
1683
+ access = { token: json.access_token, expiresAt: Date.now() + json.expires_in * 1e3 };
1684
+ return access.token;
1685
+ };
1686
+ return async (delivery) => {
1687
+ try {
1688
+ const alert = nativePushAlert(delivery.payload);
1689
+ const response = await http(
1690
+ `https://fcm.googleapis.com/v1/projects/${account.project_id}/messages:send`,
1691
+ {
1692
+ method: "POST",
1693
+ headers: {
1694
+ authorization: `Bearer ${await accessToken()}`,
1695
+ "content-type": "application/json"
1696
+ },
1697
+ body: JSON.stringify({
1698
+ message: {
1699
+ token: delivery.token,
1700
+ android: { priority: "HIGH", collapse_key: delivery.payload.sessionId },
1701
+ data: {
1702
+ title: alert.title,
1703
+ message: [alert.subtitle, alert.body].filter(Boolean).join("\n") || alert.title,
1704
+ body: JSON.stringify(nativePushData(delivery.payload))
1705
+ }
1706
+ }
1707
+ }),
1708
+ signal: AbortSignal.timeout(1e4)
1709
+ }
1710
+ );
1711
+ if (response.ok) return { outcome: "delivered" };
1712
+ const text = await response.text();
1713
+ if (response.status === 404 || text.includes("UNREGISTERED")) {
1714
+ return { outcome: "unregistered", error: `${response.status} ${text.slice(0, 200)}` };
1715
+ }
1716
+ return {
1717
+ outcome: response.status === 429 || response.status >= 500 ? "retry" : "failed",
1718
+ error: `${response.status} ${text.slice(0, 200)}`
1719
+ };
1720
+ } catch (error) {
1721
+ return { outcome: "retry", error: String(error).slice(0, 300) };
1722
+ }
1723
+ };
1724
+ }
1725
+ function configuredNativePushSenders(settings) {
1726
+ const senders = {};
1727
+ if (settings.apnsKeyId && settings.apnsTeamId && settings.apnsPrivateKey) {
1728
+ senders.ios = createApnsSender({
1729
+ keyId: settings.apnsKeyId,
1730
+ teamId: settings.apnsTeamId,
1731
+ privateKey: settings.apnsPrivateKey
1732
+ });
1733
+ }
1734
+ if (settings.fcmServiceAccountJson) {
1735
+ senders.android = createFcmSender({ serviceAccountJson: settings.fcmServiceAccountJson });
1736
+ }
1737
+ return senders;
1738
+ }
1739
+ async function drainNativePushDeliveries(deps) {
1740
+ const senders = deps.senders ?? configuredNativePushSenders(deps.settings);
1741
+ const claimId = randomUUID();
1742
+ const claimed = await markSecretQuestionDeliveries(
1743
+ deps.db,
1744
+ await claimNativePushDeliveries(deps.db, {
1745
+ claimId,
1746
+ limit: 32,
1747
+ claimSeconds: CLAIM_SECONDS
1748
+ })
1749
+ );
1750
+ let delivered = 0;
1751
+ await Promise.all(
1752
+ claimed.map(async (delivery) => {
1753
+ const sender = senders[delivery.platform];
1754
+ const result = sender ? await sender(delivery).catch((error) => ({
1755
+ outcome: "retry",
1756
+ error: String(error)
1757
+ })) : { outcome: "failed", error: `No ${delivery.platform} push credentials configured` };
1758
+ const outcome = result.outcome === "retry" && delivery.attempts >= MAX_ATTEMPTS ? "failed" : result.outcome;
1759
+ if (outcome === "delivered") delivered += 1;
1760
+ else
1761
+ deps.observability?.warn("Native push not delivered", {
1762
+ platform: delivery.platform,
1763
+ outcome,
1764
+ error: result.error
1765
+ });
1766
+ await settleNativePushDelivery(deps.db, {
1767
+ claimId,
1768
+ deliveryId: delivery.deliveryId,
1769
+ outcome,
1770
+ error: result.error ?? null,
1771
+ retrySeconds: Math.min(3600, 5 * 2 ** delivery.attempts)
1772
+ });
1773
+ })
1774
+ );
1775
+ return { claimed: claimed.length, delivered };
1776
+ }
1777
+ function startNativePushDispatchPump(deps) {
1778
+ const senders = deps.senders ?? configuredNativePushSenders(deps.settings);
1779
+ let stopped = false;
1780
+ let ticks = 0;
1781
+ let running = null;
1782
+ const tick = async () => {
1783
+ ticks += 1;
1784
+ try {
1785
+ const { claimed } = await drainNativePushDeliveries({ ...deps, senders });
1786
+ if (claimed > 0) deps.observability?.info("Native push batch", { claimed });
1787
+ if (ticks % PRUNE_EVERY_TICKS === 0) await pruneNativePushDeliveries(deps.db);
1788
+ } catch (error) {
1789
+ deps.observability?.error("Native push dispatch failed", { error: String(error) });
1790
+ }
1791
+ };
1792
+ const timer = setInterval(() => {
1793
+ if (stopped || running) return;
1794
+ running = tick().finally(() => {
1795
+ running = null;
1796
+ });
1797
+ }, POLL_INTERVAL_MS);
1798
+ return async () => {
1799
+ stopped = true;
1800
+ clearInterval(timer);
1801
+ await running;
1802
+ };
1803
+ }
1804
+
1805
+ // src/workspace-webhook-dispatch.ts
1806
+ import { randomUUID as randomUUID2 } from "crypto";
1807
+ import { environmentsEncryptionKeyBytes } from "@opengeni/config";
1808
+ import {
1809
+ OPENGENI_DELIVERY_ID_HEADER,
1810
+ OPENGENI_EVENT_ID_HEADER,
1811
+ OPENGENI_SIGNATURE_HEADER,
1812
+ signOpenGeniPayload
1813
+ } from "@opengeni/contracts";
1814
+ import {
1815
+ claimWorkspaceWebhookDeliveries,
1816
+ claimOrganizationWebhookDeliveries,
1817
+ decryptEnvironmentValue,
1818
+ maintainSessionUsageLimits,
1819
+ maintainWorkspaceAllowances,
1820
+ pruneOrganizationWebhookDeliveries,
1821
+ pruneWorkspaceWebhookDeliveries,
1822
+ settleOrganizationWebhookDelivery,
1823
+ settleWorkspaceWebhookDelivery
1824
+ } from "@opengeni/db";
1825
+ import { pinnedFetch } from "@opengeni/network";
1826
+ import { recordSessionUsageLimitEvent } from "@opengeni/core";
1827
+ var WORKSPACE_WEBHOOK_DISPATCH_BATCH_SIZE = 32;
1828
+ var CLAIM_SECONDS2 = 60;
1829
+ var POLL_INTERVAL_MS2 = 2e3;
1830
+ var DELIVERY_TIMEOUT_MS = 1e4;
1831
+ var PRUNE_EVERY_TICKS2 = 300;
1832
+ function errorText(error) {
1833
+ if (error instanceof Error) {
1834
+ if (error.name === "TimeoutError" || error.name === "AbortError") return "timeout";
1835
+ return `${error.name}: ${error.message}`.slice(0, 500);
1836
+ }
1837
+ return String(error).slice(0, 500);
1838
+ }
1839
+ async function deliverOne(deps, key, claimId, delivery, organization = false) {
1840
+ let status = null;
1841
+ let error = null;
1842
+ try {
1843
+ const body = JSON.stringify(delivery.payload);
1844
+ const secret = decryptEnvironmentValue(key, delivery.secretEncrypted);
1845
+ const response = await (deps.fetch ?? pinnedFetch)(
1846
+ delivery.url,
1847
+ {
1848
+ method: "POST",
1849
+ headers: {
1850
+ "content-type": "application/json",
1851
+ "user-agent": "OpenGeni-Webhooks/1",
1852
+ [OPENGENI_SIGNATURE_HEADER]: await signOpenGeniPayload(secret, body),
1853
+ [OPENGENI_EVENT_ID_HEADER]: delivery.eventId,
1854
+ [OPENGENI_DELIVERY_ID_HEADER]: delivery.deliveryId
1855
+ },
1856
+ body,
1857
+ signal: AbortSignal.timeout(DELIVERY_TIMEOUT_MS)
1858
+ },
1859
+ deps.settings,
1860
+ {
1861
+ label: organization ? "Organization webhook" : "Workspace webhook",
1862
+ requireHttpsOutsideLocalTest: true
1863
+ }
1864
+ );
1865
+ status = response.status;
1866
+ await response.body?.cancel().catch(() => void 0);
1867
+ if (status < 200 || status >= 300) error = `HTTP ${status}`;
1868
+ } catch (caught) {
1869
+ error = errorText(caught);
1870
+ }
1871
+ await (organization ? settleOrganizationWebhookDelivery : settleWorkspaceWebhookDelivery)(
1872
+ deps.db,
1873
+ {
1874
+ deliveryId: delivery.deliveryId,
1875
+ claimId,
1876
+ status,
1877
+ error
1878
+ }
1879
+ );
1880
+ return error === null ? "delivered" : "failed";
1881
+ }
1882
+ async function importLegacySessionUsage(deps) {
1883
+ const sink = deps.observability?.incrementCounter ? deps.observability : null;
1884
+ const metrics = sink;
1885
+ try {
1886
+ const result = await maintainSessionUsageLimits(deps.db);
1887
+ recordSessionUsageLimitEvent(metrics, "import", "imported", result.processed);
1888
+ recordSessionUsageLimitEvent(metrics, "import", "import_failed", result.failed);
1889
+ recordSessionUsageLimitEvent(metrics, "single_call", "lost_resolved", result.resolvedLost);
1890
+ if (result.failed > 0) {
1891
+ deps.observability?.warn("Session usage limit import pages failed", {
1892
+ failed: result.failed,
1893
+ sqlstates: JSON.stringify(result.errors)
1894
+ });
1895
+ }
1896
+ } catch (error) {
1897
+ recordSessionUsageLimitEvent(metrics, "import", "import_failed");
1898
+ deps.observability?.warn("Session usage limit import failed", { error: errorText(error) });
1899
+ }
1900
+ }
1901
+ async function drainWorkspaceWebhookDeliveries(deps) {
1902
+ await importLegacySessionUsage(deps);
1903
+ await maintainWorkspaceAllowances(deps.db);
1904
+ const key = environmentsEncryptionKeyBytes(deps.settings);
1905
+ if (!key) return { claimed: 0, delivered: 0, failed: 0 };
1906
+ const claimId = randomUUID2();
1907
+ const claims = await claimWorkspaceWebhookDeliveries(deps.db, {
1908
+ claimId,
1909
+ limit: WORKSPACE_WEBHOOK_DISPATCH_BATCH_SIZE,
1910
+ claimSeconds: CLAIM_SECONDS2
1911
+ });
1912
+ const organizationClaims = await claimOrganizationWebhookDeliveries(deps.db, {
1913
+ claimId,
1914
+ limit: WORKSPACE_WEBHOOK_DISPATCH_BATCH_SIZE,
1915
+ claimSeconds: CLAIM_SECONDS2
1916
+ });
1917
+ const outcomes = await Promise.all(
1918
+ [
1919
+ ...claims.map((delivery) => ({ delivery, organization: false })),
1920
+ ...organizationClaims.map((delivery) => ({ delivery, organization: true }))
1921
+ ].map(async ({ delivery, organization }) => {
1922
+ try {
1923
+ return await deliverOne(deps, key, claimId, delivery, organization);
1924
+ } catch (error) {
1925
+ deps.observability?.warn("Workspace webhook settlement failed", {
1926
+ deliveryId: delivery.deliveryId,
1927
+ error: errorText(error)
1928
+ });
1929
+ return "failed";
1930
+ }
1931
+ })
1932
+ );
1933
+ return {
1934
+ claimed: claims.length + organizationClaims.length,
1935
+ delivered: outcomes.filter((outcome) => outcome === "delivered").length,
1936
+ failed: outcomes.filter((outcome) => outcome === "failed").length
1937
+ };
1938
+ }
1939
+ function startWorkspaceWebhookDispatchPump(deps, options = {}) {
1940
+ const intervalMs = Math.max(100, options.intervalMs ?? POLL_INTERVAL_MS2);
1941
+ const drain = options.drain ?? drainWorkspaceWebhookDeliveries;
1942
+ let stopped = false;
1943
+ let running;
1944
+ let ticks = 0;
1945
+ const tick = () => {
1946
+ if (stopped || running) return;
1947
+ ticks += 1;
1948
+ let backlog = false;
1949
+ running = (async () => {
1950
+ const result = await drain(deps);
1951
+ backlog = result.claimed >= WORKSPACE_WEBHOOK_DISPATCH_BATCH_SIZE;
1952
+ if (result.claimed > 0) deps.observability?.info("Workspace webhook batch settled", result);
1953
+ if (ticks % PRUNE_EVERY_TICKS2 === 0) {
1954
+ await pruneWorkspaceWebhookDeliveries(deps.db);
1955
+ await pruneOrganizationWebhookDeliveries(deps.db);
1956
+ }
1957
+ })().catch((error) => {
1958
+ deps.observability?.error("Workspace webhook dispatch failed", { error: errorText(error) });
1959
+ }).finally(() => {
1960
+ running = void 0;
1961
+ if (backlog && !stopped) queueMicrotask(tick);
1962
+ });
1963
+ };
1964
+ const timer = setInterval(tick, intervalMs);
1965
+ queueMicrotask(tick);
1966
+ return async () => {
1967
+ stopped = true;
1968
+ clearInterval(timer);
1969
+ await running;
1970
+ };
1971
+ }
1972
+
1009
1973
  // src/editable-artifact-websocket.ts
1010
1974
  import {
1011
1975
  EditableArtifactDomainError,
@@ -1019,8 +1983,9 @@ var EDITABLE_ARTIFACT_LIVE_WEBSOCKET_MAX_MESSAGE_BYTES = 8 * 1024 * 1024 + 64 *
1019
1983
  var MAX_QUEUED_MESSAGES = 64;
1020
1984
  var MAX_QUEUED_BYTES = 12 * 1024 * 1024;
1021
1985
  var EditableArtifactWebSocketTransport = class {
1022
- constructor(application) {
1986
+ constructor(application, authorizeSourceSession) {
1023
1987
  this.application = application;
1988
+ this.authorizeSourceSession = authorizeSourceSession;
1024
1989
  this.websocket = Object.freeze({
1025
1990
  open: (socket) => socket.data.attach(socket),
1026
1991
  message: (socket, message) => socket.data.receive(message),
@@ -1044,7 +2009,10 @@ var EditableArtifactWebSocketTransport = class {
1044
2009
  if (!offeredProtocol(request.headers.get("sec-websocket-protocol"))) {
1045
2010
  return new Response("WebSocket protocol required", { status: 426 });
1046
2011
  }
1047
- const connection = new EditableArtifactWebSocketConnection(this.application);
2012
+ const connection = new EditableArtifactWebSocketConnection(
2013
+ this.application,
2014
+ this.authorizeSourceSession
2015
+ );
1048
2016
  const upgraded = server.upgrade(request, {
1049
2017
  data: connection,
1050
2018
  headers: {
@@ -1055,8 +2023,9 @@ var EditableArtifactWebSocketTransport = class {
1055
2023
  }
1056
2024
  };
1057
2025
  var EditableArtifactWebSocketConnection = class {
1058
- constructor(application) {
2026
+ constructor(application, authorizeSourceSession) {
1059
2027
  this.application = application;
2028
+ this.authorizeSourceSession = authorizeSourceSession;
1060
2029
  }
1061
2030
  socket = null;
1062
2031
  session = null;
@@ -1142,7 +2111,8 @@ var EditableArtifactWebSocketConnection = class {
1142
2111
  artifactId: frame.artifactId,
1143
2112
  protocolVersion: frame.protocolVersion,
1144
2113
  resume: frame.resume,
1145
- sink: this
2114
+ sink: this,
2115
+ ...this.authorizeSourceSession ? { authorizeSourceSession: this.authorizeSourceSession } : {}
1146
2116
  });
1147
2117
  if (this.terminal) {
1148
2118
  await session.close("transport_error");
@@ -1249,14 +2219,121 @@ function closeCode(reason) {
1249
2219
  return 4400;
1250
2220
  }
1251
2221
 
2222
+ // src/editable-artifact-source-session.ts
2223
+ import {
2224
+ hasPermission,
2225
+ requireExternalContinuationAuthority,
2226
+ requireSessionAuthorization
2227
+ } from "@opengeni/core";
2228
+ import {
2229
+ findActiveWorkspaceApiKeyById,
2230
+ getSession,
2231
+ getWorkspaceGrant,
2232
+ hasEditableArtifactSessionLink,
2233
+ lockExternalWorkspaceMembershipLifecycle,
2234
+ withRlsContext
2235
+ } from "@opengeni/db";
2236
+ function editableArtifactSourceSessionAuthorizer(deps) {
2237
+ return async (ticket, permission, transaction, signal) => {
2238
+ signal?.throwIfAborted();
2239
+ const authority = ticket.sourceSessionAuthority;
2240
+ if (!authority) return true;
2241
+ const { grant } = authority;
2242
+ const required = permission === "edit" ? "artifacts:publish" : "artifacts:read";
2243
+ if (!hasPermission(grant.permissions, required) || !hasPermission(grant.permissions, "sessions:read")) {
2244
+ return false;
2245
+ }
2246
+ if (grant.metadata?.externalActor && !authority.externalContinuation) return false;
2247
+ try {
2248
+ const scopedDeps = transaction ? { ...deps, db: transaction } : deps;
2249
+ if (transaction) {
2250
+ await lockExternalWorkspaceMembershipLifecycle(transaction, ticket.scope.accountId);
2251
+ signal?.throwIfAborted();
2252
+ await transaction.execute(sql`select pg_advisory_xact_lock_shared(
2253
+ hashtextextended(${`session-tenancy:${ticket.scope.workspaceId}`}, 0))`);
2254
+ signal?.throwIfAborted();
2255
+ }
2256
+ return await withAccessGrantSessionRlsContext(scopedDeps, grant, async () => {
2257
+ return await withRlsContext(scopedDeps.db, ticket.scope, async (tx) => {
2258
+ if (authority.externalContinuation) {
2259
+ await requireExternalContinuationAuthority(
2260
+ tx,
2261
+ authority.externalContinuation,
2262
+ { ...ticket.scope, subjectId: grant.subjectId },
2263
+ ["sessions:read", required]
2264
+ );
2265
+ signal?.throwIfAborted();
2266
+ } else if (transaction) {
2267
+ const actor = ticket.actor;
2268
+ if (actor.kind === "human" || actor.kind === "service" && actor.service === "configured_key") {
2269
+ await tx.execute(sql`select subject_id from workspace_memberships
2270
+ where account_id = ${ticket.scope.accountId}::uuid
2271
+ and workspace_id = ${ticket.scope.workspaceId}::uuid
2272
+ and subject_id = ${grant.subjectId} for share`);
2273
+ const live = await getWorkspaceGrant(tx, grant.subjectId, ticket.scope.workspaceId);
2274
+ if (!live || !hasPermission(live.permissions, "sessions:read")) return false;
2275
+ } else if (actor.kind === "service" && actor.service === "api_key") {
2276
+ await tx.execute(sql`select id from api_keys
2277
+ where account_id = ${ticket.scope.accountId}::uuid
2278
+ and id::text = ${grant.subjectId.slice("api_key:".length)}
2279
+ for share`);
2280
+ const live = await findActiveWorkspaceApiKeyById(tx, {
2281
+ ...ticket.scope,
2282
+ apiKeyId: grant.subjectId.slice("api_key:".length)
2283
+ });
2284
+ if (!live || !hasPermission(live.permissions, "sessions:read")) return false;
2285
+ } else if (actor.kind === "agent") {
2286
+ const live = await getSession(tx, ticket.scope.workspaceId, actor.sessionId);
2287
+ if (!live || live.firstPartyMcpPermissions !== null && !hasPermission(live.firstPartyMcpPermissions, "sessions:read")) {
2288
+ return false;
2289
+ }
2290
+ } else {
2291
+ return false;
2292
+ }
2293
+ signal?.throwIfAborted();
2294
+ }
2295
+ const linked = await hasEditableArtifactSessionLink(
2296
+ tx,
2297
+ ticket.scope,
2298
+ authority.sessionId,
2299
+ ticket.artifactId,
2300
+ { lockForCommit: transaction !== void 0 }
2301
+ );
2302
+ signal?.throwIfAborted();
2303
+ if (!linked) return false;
2304
+ await requireSessionAuthorization({ ...scopedDeps, db: tx }, grant, {
2305
+ sessionId: authority.sessionId,
2306
+ operation: "session.read",
2307
+ surface: "http"
2308
+ });
2309
+ signal?.throwIfAborted();
2310
+ return true;
2311
+ });
2312
+ });
2313
+ } catch {
2314
+ return false;
2315
+ }
2316
+ };
2317
+ }
2318
+
2319
+ // src/http/websocket-upgrade-dispatch.ts
2320
+ function dispatchApiWebSocketUpgrade(request, server, routes, localBrowserBoundary) {
2321
+ const route = routes.find((candidate) => candidate.handles(request));
2322
+ if (!route) return { handled: false };
2323
+ const rejection = localBrowserBoundary?.rejection(request);
2324
+ if (rejection) return { handled: true, response: localBrowserBoundaryResponse(rejection) };
2325
+ return { handled: true, response: route.upgrade(request, server) };
2326
+ }
2327
+
1252
2328
  // src/editable-artifact-production.ts
1253
- import { createHash as createHash4, randomUUID } from "crypto";
2329
+ import { createHash as createHash5, randomUUID as randomUUID3 } from "crypto";
1254
2330
  import {
1255
2331
  editableArtifactMaterializerCapabilitiesForRuntime
1256
2332
  } from "@opengeni/artifact-tool/runtime/materializer";
1257
2333
  import {
1258
2334
  EditableArtifactDurableExportService,
1259
2335
  EditableArtifactAgentApplication,
2336
+ EDITABLE_ARTIFACT_EXPORT_FORMATS,
1260
2337
  EDITABLE_ARTIFACT_EXPORT_MAX_DOWNLOAD_BYTES,
1261
2338
  EditableArtifactCompactionPipeline,
1262
2339
  EditableArtifactGenesisPipeline,
@@ -1286,8 +2363,9 @@ import {
1286
2363
  PostgresEditableArtifactStore,
1287
2364
  listEditableArtifactIdsForSession,
1288
2365
  touchEditableArtifactSessionLink,
2366
+ recordNativeDocumentInspection,
1289
2367
  dbSql,
1290
- withRlsContext
2368
+ withRlsContext as withRlsContext2
1291
2369
  } from "@opengeni/db";
1292
2370
  import { PostgresEditableArtifactDurableExportStore } from "@opengeni/db/editable-artifact-durable-export";
1293
2371
  import {
@@ -1351,7 +2429,7 @@ async function requireFlushBarrier(connection) {
1351
2429
  }
1352
2430
 
1353
2431
  // src/editable-artifact-native-kernel.ts
1354
- import { createHash } from "crypto";
2432
+ import { createHash as createHash2 } from "crypto";
1355
2433
  import { pathToFileURL } from "url";
1356
2434
  import {
1357
2435
  createNativeArtifactSession,
@@ -1475,7 +2553,7 @@ var NativeEditableArtifactKernelAdapter = class {
1475
2553
  canonicalChunks: fixedChunks(snapshot, input.signal),
1476
2554
  canonicalByteSize: snapshot.byteLength,
1477
2555
  canonicalContentHash: editableArtifactContentHash(
1478
- `sha256:${createHash("sha256").update(snapshot).digest("hex")}`
2556
+ `sha256:${createHash2("sha256").update(snapshot).digest("hex")}`
1479
2557
  ),
1480
2558
  stateHash: editableArtifactStateHash(session.stateHash()),
1481
2559
  coveredHeadSequence: 0,
@@ -1522,7 +2600,7 @@ var NativeEditableArtifactKernelAdapter = class {
1522
2600
  canonicalChunks: fixedChunks(canonical, input.signal),
1523
2601
  canonicalByteSize: canonical.byteLength,
1524
2602
  canonicalContentHash: editableArtifactContentHash(
1525
- `sha256:${createHash("sha256").update(canonical).digest("hex")}`
2603
+ `sha256:${createHash2("sha256").update(canonical).digest("hex")}`
1526
2604
  ),
1527
2605
  stateHash: editableArtifactStateHash(session.stateHash()),
1528
2606
  coveredHeadSequence: input.state.artifact.headSequence,
@@ -1569,7 +2647,7 @@ var NativeEditableArtifactKernelAdapter = class {
1569
2647
  const stateHash = editableArtifactStateHash(session.stateHash());
1570
2648
  if (stateHash !== expected.stateHash) throw new Error("Snapshot state hash mismatch");
1571
2649
  const canonicalContentHash = editableArtifactContentHash(
1572
- `sha256:${createHash("sha256").update(canonical).digest("hex")}`
2650
+ `sha256:${createHash2("sha256").update(canonical).digest("hex")}`
1573
2651
  );
1574
2652
  if (session.modality === "spreadsheet") {
1575
2653
  const coveredCausalFrontier = domainFrontier(session.frontier());
@@ -1785,7 +2863,7 @@ async function readVerifiedSnapshot(objects, snapshot, signal) {
1785
2863
  snapshot.byteSize,
1786
2864
  signal
1787
2865
  );
1788
- const contentHash = `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
2866
+ const contentHash = `sha256:${createHash2("sha256").update(bytes).digest("hex")}`;
1789
2867
  if (contentHash !== snapshot.contentHash) throw new Error("Snapshot content hash mismatch");
1790
2868
  await object.assertUnchanged(signal);
1791
2869
  return bytes;
@@ -1821,7 +2899,7 @@ async function* fixedChunks(bytes, signal) {
1821
2899
  }
1822
2900
  }
1823
2901
  function namespaceForArtifact(artifactId) {
1824
- const digest = createHash("sha256").update(artifactId).digest();
2902
+ const digest = createHash2("sha256").update(artifactId).digest();
1825
2903
  let value = 0n;
1826
2904
  for (let index = 0; index < 8; index += 1) value = value << 8n | BigInt(digest[index]);
1827
2905
  return value === 0n ? 1n : value;
@@ -1845,7 +2923,7 @@ function throwIfAborted(signal) {
1845
2923
  }
1846
2924
 
1847
2925
  // src/editable-artifact-office-import.ts
1848
- import { createHash as createHash2 } from "crypto";
2926
+ import { createHash as createHash3 } from "crypto";
1849
2927
  import {
1850
2928
  ArtifactOfficeSourceUnsupportedError,
1851
2929
  prepareArtifactOfficeImport
@@ -2011,7 +3089,7 @@ async function readVerifiedWorkspaceFile(storage, file, signal) {
2011
3089
  throw new EditableArtifactOfficeImportError("source_changed");
2012
3090
  }
2013
3091
  const bytes = new Uint8Array(file.sizeBytes);
2014
- const digest = createHash2("sha256");
3092
+ const digest = createHash3("sha256");
2015
3093
  for (let start = 0; start < file.sizeBytes; start += MAX_BOUNDED_OBJECT_CHUNK_BYTES) {
2016
3094
  throwIfAborted2(signal);
2017
3095
  const end = Math.min(file.sizeBytes, start + MAX_BOUNDED_OBJECT_CHUNK_BYTES) - 1;
@@ -2072,7 +3150,7 @@ function throwIfAborted2(signal) {
2072
3150
  }
2073
3151
 
2074
3152
  // src/editable-artifact-workspace-files.ts
2075
- import { createHash as createHash3 } from "crypto";
3153
+ import { createHash as createHash4 } from "crypto";
2076
3154
  import { completeFileUpload, prepareGeneratedWorkspaceFile } from "@opengeni/db";
2077
3155
  import { retryWhileMissing as retryWhileMissing2 } from "@opengeni/storage";
2078
3156
  var UPLOAD_INTENT_TTL_MS = 60 * 6e4;
@@ -2208,7 +3286,7 @@ function plainSha256(value) {
2208
3286
  return match[1];
2209
3287
  }
2210
3288
  function deterministicUuid(seed) {
2211
- const bytes = createHash3("sha256").update(seed, "utf8").digest().subarray(0, 16);
3289
+ const bytes = createHash4("sha256").update(seed, "utf8").digest().subarray(0, 16);
2212
3290
  bytes[6] = (bytes[6] ?? 0) & 15 | 80;
2213
3291
  bytes[8] = (bytes[8] ?? 0) & 63 | 128;
2214
3292
  const hex = bytes.toString("hex");
@@ -2297,6 +3375,11 @@ async function createStandaloneEditableArtifactApplication(input) {
2297
3375
  touch: async ({ scope, sessionId, artifactId }) => await touchEditableArtifactSessionLink(input.db, scope, sessionId, artifactId)
2298
3376
  }),
2299
3377
  inspector: kernel,
3378
+ recordInspection: async (inspection) => {
3379
+ if (inspection.actor.kind !== "agent")
3380
+ throw new Error("Inspection proof requires an agent attempt");
3381
+ return recordNativeDocumentInspection(input.db, { ...inspection, actor: inspection.actor });
3382
+ },
2300
3383
  officeImports,
2301
3384
  workspaceFiles: new EditableArtifactWorkspaceFileAdapter({
2302
3385
  db: input.db,
@@ -2355,7 +3438,7 @@ var PostgresEditableArtifactAuthorization = class {
2355
3438
  }
2356
3439
  async authorize(request) {
2357
3440
  const actor = request.actor;
2358
- const rows = await withRlsContext(
3441
+ const rows = await withRlsContext2(
2359
3442
  this.db,
2360
3443
  request.scope,
2361
3444
  async (tx) => rawRows(
@@ -2433,12 +3516,12 @@ var PollingEditableArtifactAuthorizationInvalidations = class {
2433
3516
  };
2434
3517
  var RandomEditableArtifactStableIds = class {
2435
3518
  next(_kind) {
2436
- return randomUUID().replaceAll("-", "");
3519
+ return randomUUID3().replaceAll("-", "");
2437
3520
  }
2438
3521
  };
2439
3522
  var RandomEditableArtifactDurableExportIds = class {
2440
3523
  next(_kind) {
2441
- return randomUUID().replaceAll("-", "");
3524
+ return randomUUID3().replaceAll("-", "");
2442
3525
  }
2443
3526
  };
2444
3527
  function materializationProfiles(capabilities) {
@@ -2448,8 +3531,9 @@ function materializationProfiles(capabilities) {
2448
3531
  }
2449
3532
  const codecVersion = capabilities.codecVersions["opengeni.xlsx"];
2450
3533
  return Object.freeze({
3534
+ supportedFormats: EDITABLE_ARTIFACT_EXPORT_FORMATS,
2451
3535
  async resolve(input) {
2452
- if (input.modality !== "spreadsheet" || input.format !== "xlsx" || Object.keys(input.options).length !== 0) {
3536
+ if (input.modality !== "spreadsheet" || input.format !== "xlsx" || !EDITABLE_ARTIFACT_EXPORT_FORMATS[input.modality].includes(input.format) || Object.keys(input.options).length !== 0) {
2453
3537
  return null;
2454
3538
  }
2455
3539
  return Object.freeze({
@@ -2478,7 +3562,7 @@ function snapshotBytesPort(objects) {
2478
3562
  throw new Error("Editable artifact snapshot content type mismatch");
2479
3563
  }
2480
3564
  const bytes = new Uint8Array(snapshot.byteSize);
2481
- const digest = createHash4("sha256");
3565
+ const digest = createHash5("sha256");
2482
3566
  let offset = 0;
2483
3567
  for await (const chunk of object.chunks({
2484
3568
  chunkBytes: MAX_BOUNDED_OBJECT_CHUNK_BYTES2
@@ -2509,7 +3593,8 @@ function rawRows(result) {
2509
3593
  }
2510
3594
 
2511
3595
  // src/fatal-process-boundary.ts
2512
- import { randomUUID as randomUUID2 } from "crypto";
3596
+ import { randomUUID as randomUUID4 } from "crypto";
3597
+ import { isDatabaseConnectionLoss } from "@opengeni/db";
2513
3598
  var API_FATAL_FLUSH_TIMEOUT_MS = 1e3;
2514
3599
  var API_FATAL_CORRELATION_ID_PATTERN = /^[A-Za-z0-9._:-]{1,128}$/;
2515
3600
  var FATAL_ERROR_CODES = {
@@ -2520,19 +3605,69 @@ var FATAL_ERROR_CODES = {
2520
3605
  function installApiFatalProcessBoundary(options = {}) {
2521
3606
  const runtimeProcess = options.process ?? defaultProcessBoundary();
2522
3607
  const flushTimeoutMs = options.flushTimeoutMs ?? API_FATAL_FLUSH_TIMEOUT_MS;
2523
- const correlationId = options.correlationId ?? (() => `api-fatal.${randomUUID2()}`);
3608
+ const correlationId = options.correlationId ?? (() => `api-fatal.${randomUUID4()}`);
2524
3609
  const fallbackLog = options.fallbackLog ?? ((message) => console.error(message));
3610
+ const isRecoverableRejection = options.isRecoverableRejection ?? isDatabaseConnectionLoss;
2525
3611
  let observability = options.observability;
2526
3612
  let phase = "startup";
2527
3613
  let reporting = false;
3614
+ const recover = (reason) => {
3615
+ if (phase !== "running" || reporting) return false;
3616
+ let recoverable = false;
3617
+ try {
3618
+ recoverable = isRecoverableRejection(reason);
3619
+ } catch {
3620
+ return false;
3621
+ }
3622
+ if (!recoverable) return false;
3623
+ const attributes = {
3624
+ errorClass: "ApiRecoveredRejection",
3625
+ errorCode: "api_unhandled_database_connection_loss",
3626
+ origin: "api",
3627
+ reasonKind: apiFatalReasonKind(reason)
3628
+ };
3629
+ try {
3630
+ if (observability?.warn) {
3631
+ observability.warn(
3632
+ "Opengeni API recovered an unhandled database connection loss",
3633
+ attributes
3634
+ );
3635
+ } else {
3636
+ safeFallbackLog(
3637
+ fallbackLog,
3638
+ "Opengeni API recovered an unhandled database connection loss (api_unhandled_database_connection_loss)"
3639
+ );
3640
+ }
3641
+ observability?.incrementCounter?.({
3642
+ name: "opengeni_api_recovered_rejections_total",
3643
+ help: "Unhandled rejections the API survived because they only reported a lost database connection.",
3644
+ labels: { code: "api_unhandled_database_connection_loss" }
3645
+ });
3646
+ } catch {
3647
+ }
3648
+ return true;
3649
+ };
2528
3650
  const report = async (event, reason) => {
2529
3651
  if (reporting) return;
2530
3652
  reporting = true;
2531
3653
  const diagnostic = apiFatalDiagnostic(event, phase, reason, correlationId);
2532
- const message = apiFatalMessage(diagnostic);
3654
+ let message = apiFatalMessage(diagnostic);
2533
3655
  const activeObservability = observability;
2534
3656
  try {
2535
3657
  if (activeObservability) {
3658
+ try {
3659
+ const diagnosticId = activeObservability.recordFailureDiagnostic?.({
3660
+ code: FATAL_ERROR_CODES[event],
3661
+ stage: phase,
3662
+ retryDecision: "not_retryable",
3663
+ error: reason
3664
+ });
3665
+ if (diagnosticId) {
3666
+ diagnostic.correlationId = diagnosticId;
3667
+ message = apiFatalMessage(diagnostic);
3668
+ }
3669
+ } catch {
3670
+ }
2536
3671
  let logged = false;
2537
3672
  try {
2538
3673
  activeObservability.error(message, diagnostic);
@@ -2556,6 +3691,7 @@ function installApiFatalProcessBoundary(options = {}) {
2556
3691
  }
2557
3692
  };
2558
3693
  const onUnhandledRejection = (reason) => {
3694
+ if (recover(reason)) return;
2559
3695
  void report("unhandled_rejection", reason);
2560
3696
  };
2561
3697
  const onUncaughtException = (reason) => {
@@ -2608,7 +3744,7 @@ function apiFatalReasonKind(reason) {
2608
3744
  }
2609
3745
  }
2610
3746
  function apiFatalMessage(diagnostic) {
2611
- return `OpenGeni API fatal process failure (${diagnostic.errorCode}; phase=${diagnostic.phase}; reason_kind=${diagnostic.reasonKind}; correlation_id=${diagnostic.correlationId})`;
3747
+ return `Opengeni API fatal process failure (${diagnostic.errorCode}; phase=${diagnostic.phase}; reason_kind=${diagnostic.reasonKind}; correlation_id=${diagnostic.correlationId})`;
2612
3748
  }
2613
3749
  async function flushWithin(observability, timeoutMs) {
2614
3750
  let flush;
@@ -2682,6 +3818,26 @@ async function createTemporalWorkflowClient(settings, db) {
2682
3818
  connection,
2683
3819
  namespace: settings.temporalNamespace
2684
3820
  });
3821
+ const scheduledTasks = createTemporalScheduleSynchronizer({
3822
+ db,
3823
+ withDeadline: (deadline, work) => temporal.withDeadline(deadline, work),
3824
+ upsert: async (task) => {
3825
+ const options = temporalScheduleOptions(task, settings.temporalTaskQueue);
3826
+ try {
3827
+ await temporal.schedule.getHandle(task.temporalScheduleId).update(() => temporalScheduleUpdateOptions(options));
3828
+ } catch (error) {
3829
+ if (!shouldCreateScheduleAfterUpdateError(error)) throw error;
3830
+ await temporal.schedule.create(options);
3831
+ }
3832
+ },
3833
+ remove: async (temporalScheduleId) => {
3834
+ try {
3835
+ await temporal.schedule.getHandle(temporalScheduleId).delete();
3836
+ } catch (error) {
3837
+ if (!(error instanceof ScheduleNotFoundError)) throw error;
3838
+ }
3839
+ }
3840
+ });
2685
3841
  const client = {
2686
3842
  triggerAutomationRun: async ({ accountId, workspaceId, runId }) => {
2687
3843
  try {
@@ -2725,7 +3881,13 @@ async function createTemporalWorkflowClient(settings, db) {
2725
3881
  });
2726
3882
  },
2727
3883
  requestSessionWorkflowWakeDispatch: async () => {
2728
- await temporal.schedule.getHandle(SESSION_WORKFLOW_WAKE_DISPATCHER_SCHEDULE_ID).trigger(ScheduleOverlapPolicy.BUFFER_ONE);
3884
+ await temporal.workflow.signalWithStart(SESSION_WORKFLOW_WAKE_DISPATCHER_WORKFLOW_TYPE, {
3885
+ taskQueue: settings.temporalTaskQueue,
3886
+ workflowId: SESSION_WORKFLOW_WAKE_DISPATCHER_WORKFLOW_ID,
3887
+ workflowIdReusePolicy: "ALLOW_DUPLICATE",
3888
+ args: [],
3889
+ signal: SESSION_WORKFLOW_WAKE_DISPATCH_REQUESTED_SIGNAL
3890
+ });
2729
3891
  },
2730
3892
  signalCodexCapacity: async ({
2731
3893
  accountId,
@@ -2775,41 +3937,14 @@ async function createTemporalWorkflowClient(settings, db) {
2775
3937
  wakeRevision: workflowWakeRevision
2776
3938
  });
2777
3939
  },
2778
- syncScheduledTask: async ({ task }) => {
2779
- const schedule = temporal.schedule.getHandle(task.temporalScheduleId);
2780
- if (task.schedule.type === "manual") {
2781
- try {
2782
- await schedule.delete();
2783
- } catch (error) {
2784
- if (!(error instanceof ScheduleNotFoundError)) throw error;
2785
- }
2786
- return;
2787
- }
2788
- const options = temporalScheduleOptions(task, settings.temporalTaskQueue);
2789
- try {
2790
- await schedule.update(() => temporalScheduleUpdateOptions(options));
2791
- } catch (error) {
2792
- if (!shouldCreateScheduleAfterUpdateError(error)) {
2793
- throw error;
2794
- }
2795
- await temporal.schedule.create(options);
2796
- }
2797
- },
2798
- deleteScheduledTaskSchedule: async ({ temporalScheduleId }) => {
2799
- try {
2800
- await temporal.withDeadline(Date.now() + 5e3, async () => {
2801
- await temporal.schedule.getHandle(temporalScheduleId).delete();
2802
- });
2803
- } catch (error) {
2804
- if (error instanceof ScheduleNotFoundError) return;
2805
- throw error;
2806
- }
2807
- },
3940
+ syncScheduledTask: async ({ task, onFailure }) => scheduledTasks.sync(task, onFailure),
3941
+ deleteScheduledTaskSchedule: async ({ temporalScheduleId }) => scheduledTasks.remove(temporalScheduleId),
2808
3942
  triggerScheduledTask: async ({
2809
3943
  task,
2810
3944
  agentRunUsageIdempotencyKey,
2811
3945
  triggerWorkflowId,
2812
3946
  initiator,
3947
+ credentialRestriction,
2813
3948
  triggerType = "manual"
2814
3949
  }) => {
2815
3950
  const workflowId = triggerWorkflowId;
@@ -2825,7 +3960,8 @@ async function createTemporalWorkflowClient(settings, db) {
2825
3960
  taskId: task.id,
2826
3961
  triggerType,
2827
3962
  agentRunUsageIdempotencyKey,
2828
- initiator
3963
+ initiator,
3964
+ ...credentialRestriction ? { credentialRestriction } : {}
2829
3965
  }
2830
3966
  ]
2831
3967
  });
@@ -2839,7 +3975,7 @@ async function createTemporalWorkflowClient(settings, db) {
2839
3975
  startRigVerification: async ({ workspaceId, changeId, versionId, workflowId }) => {
2840
3976
  const targetId = changeId ?? versionId;
2841
3977
  if (!targetId) {
2842
- throw new Error("rig verification requires changeId or versionId");
3978
+ throw new Error("sandbox environment verification requires changeId or versionId");
2843
3979
  }
2844
3980
  try {
2845
3981
  await temporal.workflow.start("rigVerificationWorkflow", {
@@ -2887,7 +4023,7 @@ async function startApi(options = {}) {
2887
4023
  const observability = options.observability ?? createObservability(settings, { component: "api" });
2888
4024
  const searchPath = dbSearchPath(settings);
2889
4025
  const dbClient = createDb(settings.databaseUrl, {
2890
- max: 32,
4026
+ max: settings.apiDatabasePoolMax,
2891
4027
  ...searchPath ? { searchPath } : {},
2892
4028
  rlsStrategy: settings.rlsStrategy
2893
4029
  });
@@ -2898,8 +4034,7 @@ async function startApi(options = {}) {
2898
4034
  const databasePosture = {
2899
4035
  rlsStrategy: settings.rlsStrategy,
2900
4036
  expectedRole: settings.runtimeDatabaseRole,
2901
- targetSchema: settings.dbSchema.trim() || "public",
2902
- organizationTenancyCanonicalActivationEnabled: settings.organizationTenancyCanonicalActivationEnabled
4037
+ targetSchema: settings.dbSchema.trim() || "public"
2903
4038
  };
2904
4039
  const controlPlaneAuth = resolveNatsControlPlaneAuth(settings);
2905
4040
  try {
@@ -2912,14 +4047,14 @@ async function startApi(options = {}) {
2912
4047
  await retryStartupDependency(
2913
4048
  "PostgreSQL runtime posture",
2914
4049
  () => assertRuntimeDatabasePosture(dbClient.db, databasePosture),
2915
- { ...retryOptions, onRetry }
4050
+ { ...retryOptions, onRetry, shouldRetry: isRetryableRuntimeDatabaseStartupError }
2916
4051
  );
2917
4052
  const resolvedCatalog = await retryStartupDependency(
2918
4053
  "model catalog",
2919
4054
  () => resolveCatalogSettings(dbClient.db, settings),
2920
4055
  { ...retryOptions, onRetry }
2921
4056
  );
2922
- observability.info("OpenGeni model catalog resolved", {
4057
+ observability.info("Opengeni model catalog resolved", {
2923
4058
  catalogSource: resolvedCatalog.source,
2924
4059
  catalogVersion: resolvedCatalog.version
2925
4060
  });
@@ -2928,7 +4063,7 @@ async function startApi(options = {}) {
2928
4063
  () => createNatsEventBus(
2929
4064
  settings.natsUrl,
2930
4065
  controlPlaneAuth ? { user: controlPlaneAuth.user, pass: controlPlaneAuth.password } : void 0,
2931
- { logger: observabilityEventLogger(observability) }
4066
+ observabilityEventBusOptions(observability)
2932
4067
  ),
2933
4068
  {
2934
4069
  ...retryOptions,
@@ -2949,7 +4084,7 @@ async function startApi(options = {}) {
2949
4084
  }
2950
4085
  if (!bus || !workflowClient) {
2951
4086
  await dbClient.close();
2952
- throw new Error("OpenGeni API startup dependencies were not initialized");
4087
+ throw new Error("Opengeni API startup dependencies were not initialized");
2953
4088
  }
2954
4089
  const objectStorage = createObjectStorage(settings);
2955
4090
  let editableArtifactComposition;
@@ -2967,7 +4102,6 @@ async function startApi(options = {}) {
2967
4102
  }
2968
4103
  const { app, routeDeps } = createAppComposition({
2969
4104
  settings,
2970
- connectionCredentials: createNativeRemoteMcpCredentialsPort(settings, dbClient.db),
2971
4105
  db: dbClient.db,
2972
4106
  bus,
2973
4107
  workflowClient: workflowClient.client,
@@ -2990,21 +4124,29 @@ async function startApi(options = {}) {
2990
4124
  behavior: "http_and_websocket_fail_closed"
2991
4125
  });
2992
4126
  }
2993
- const artifactWebSockets = new EditableArtifactWebSocketTransport(routeDeps.editableArtifacts);
4127
+ const artifactWebSockets = new EditableArtifactWebSocketTransport(
4128
+ routeDeps.editableArtifacts,
4129
+ editableArtifactSourceSessionAuthorizer(routeDeps)
4130
+ );
2994
4131
  const interactionFrameProxies = new InteractionFrameProxyTransport(
2995
4132
  resolveFirstPartyDelegationSecret(settings)
2996
4133
  );
4134
+ const localBrowserBoundary = createLocalBrowserBoundary(settings, {
4135
+ warn: (message, attributes) => observability.warn(message, attributes)
4136
+ });
4137
+ const webSocketUpgrades = [interactionFrameProxies, artifactWebSockets];
2997
4138
  const server = Bun.serve({
2998
4139
  hostname: settings.apiHost,
2999
4140
  port: settings.apiPort,
3000
4141
  idleTimeout: 255,
3001
4142
  fetch: (request, bunServer) => {
3002
- if (interactionFrameProxies.handles(request)) {
3003
- return interactionFrameProxies.upgrade(request, bunServer);
3004
- }
3005
- if (artifactWebSockets.handles(request)) {
3006
- return artifactWebSockets.upgrade(request, bunServer);
3007
- }
4143
+ const upgrade = dispatchApiWebSocketUpgrade(
4144
+ request,
4145
+ bunServer,
4146
+ webSocketUpgrades,
4147
+ localBrowserBoundary
4148
+ );
4149
+ if (upgrade.handled) return upgrade.response;
3008
4150
  return app.fetch(
3009
4151
  request,
3010
4152
  apiRequestBindingsForTransportPeer(bunServer.requestIP(request)?.address)
@@ -3019,8 +4161,19 @@ async function startApi(options = {}) {
3019
4161
  close: (socket) => socket.data.transportClosed()
3020
4162
  }
3021
4163
  });
4164
+ const metricsServer = startApiMetricsListener(settings, observability);
3022
4165
  const stopSlackInteractionPump = settings.slackSigningSecret ? startSlackInteractionPump(routeDeps) : void 0;
3023
4166
  const stopMemorySlackPublicationPump = startMemorySlackPublicationPump(routeDeps);
4167
+ const stopWorkspaceWebhookDispatchPump = startWorkspaceWebhookDispatchPump({
4168
+ db: dbClient.db,
4169
+ settings,
4170
+ observability
4171
+ });
4172
+ const stopNativePushDispatchPump = startNativePushDispatchPump({
4173
+ db: dbClient.db,
4174
+ settings,
4175
+ observability
4176
+ });
3024
4177
  const stopTemporalScheduleCleanupPump = startTemporalScheduleCleanupPump({
3025
4178
  db: dbClient.db,
3026
4179
  cleanupConnectorAuthorization: async (claim) => await cleanupScheduledTaskConnectorAuthorization(routeDeps, claim),
@@ -3029,6 +4182,12 @@ async function startApi(options = {}) {
3029
4182
  },
3030
4183
  observability
3031
4184
  });
4185
+ const stopScheduledTaskLifecyclePump = startScheduledTaskLifecyclePump({
4186
+ db: dbClient.db,
4187
+ sync: (task) => workflowClient.client.syncScheduledTask({ task }),
4188
+ bus,
4189
+ observability
4190
+ });
3032
4191
  let stopMetricsIngestion;
3033
4192
  let stopHelloIngestion;
3034
4193
  let authCalloutResponder;
@@ -3036,14 +4195,20 @@ async function startApi(options = {}) {
3036
4195
  stopMetricsIngestion = startMetricsIngestion({
3037
4196
  db: dbClient.db,
3038
4197
  bus,
3039
- observability
4198
+ observability,
4199
+ agentUpdates: createAutomaticAgentUpdater({
4200
+ db: dbClient.db,
4201
+ bus,
4202
+ settings,
4203
+ observability
4204
+ })
3040
4205
  });
3041
4206
  stopHelloIngestion = startHelloIngestion({
3042
4207
  db: dbClient.db,
3043
4208
  bus,
3044
4209
  observability
3045
4210
  });
3046
- observability.info("OpenGeni machine-metrics + hello ingestion consumers started", {});
4211
+ observability.info("Opengeni machine-metrics + hello ingestion consumers started", {});
3047
4212
  const callout = resolveNatsCalloutConfig(settings);
3048
4213
  if (callout) {
3049
4214
  try {
@@ -3052,7 +4217,7 @@ async function startApi(options = {}) {
3052
4217
  settings.natsUrl
3053
4218
  );
3054
4219
  } catch {
3055
- observability.error("OpenGeni NATS auth-callout responder failed to start", {
4220
+ observability.error("Opengeni NATS auth-callout responder failed to start", {
3056
4221
  errorClass: "NatsAuthCalloutOperationError",
3057
4222
  errorCode: "nats_auth_callout_start_failed",
3058
4223
  origin: "api"
@@ -3060,24 +4225,30 @@ async function startApi(options = {}) {
3060
4225
  }
3061
4226
  } else {
3062
4227
  observability.warn(
3063
- "OpenGeni selfhosted enabled but the NATS auth-callout plane is not configured; selfhosted agents cannot connect",
4228
+ "Opengeni selfhosted enabled but the NATS auth-callout plane is not configured; selfhosted agents cannot connect",
3064
4229
  {}
3065
4230
  );
3066
4231
  }
3067
4232
  }
3068
- observability.info("OpenGeni API listening", {
4233
+ observability.info("Opengeni API listening", {
3069
4234
  host: settings.apiHost,
3070
- port: settings.apiPort
4235
+ port: settings.apiPort,
4236
+ ...metricsServer ? { metricsPort: settings.apiMetricsPort } : {}
3071
4237
  });
3072
4238
  return {
3073
4239
  server,
3074
4240
  close: async () => {
3075
4241
  server.stop(true);
4242
+ metricsServer?.stop(true);
3076
4243
  stopSlackInteractionPump?.();
3077
4244
  await stopMemorySlackPublicationPump();
3078
4245
  stopMetricsIngestion?.();
3079
4246
  stopHelloIngestion?.();
3080
4247
  await stopTemporalScheduleCleanupPump();
4248
+ await stopScheduledTaskLifecyclePump();
4249
+ await stopWorkspaceWebhookDispatchPump();
4250
+ await stopNativePushDispatchPump();
4251
+ await routeDeps.userPresence?.close().catch(() => void 0);
3081
4252
  await Promise.allSettled([
3082
4253
  Promise.resolve(editableArtifactComposition?.close()),
3083
4254
  authCalloutResponder?.close(),