@opengeni/api-router 0.30.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (436) hide show
  1. package/dist/access-grant-rls.d.ts +3 -0
  2. package/dist/admission-trace.d.ts +8 -0
  3. package/dist/api-websocket.d.ts +18 -0
  4. package/dist/app.d.ts +50 -2
  5. package/dist/app.js +12 -1
  6. package/dist/artifact-catalog-cursor.d.ts +27 -0
  7. package/dist/auth/managed-auth-attempt-context.d.ts +10 -0
  8. package/dist/auth/managed-auth-rate-limits.d.ts +167 -0
  9. package/dist/auth/managed-auth-session-adapter.d.ts +6 -0
  10. package/dist/auth/managed-auth.d.ts +102 -3
  11. package/dist/auth/managed-email.d.ts +29 -0
  12. package/dist/auth/managed-sign-in-notifications.d.ts +4 -0
  13. package/dist/auth/new-signups-gate.d.ts +23 -0
  14. package/dist/auth/organization-user-setup.d.ts +80 -0
  15. package/dist/auth/signup-funnel-metrics.d.ts +44 -0
  16. package/dist/browser-controller-authority.d.ts +1 -61
  17. package/dist/browser-ephemeral.d.ts +5 -0
  18. package/dist/browser-state-authority.d.ts +1 -27
  19. package/dist/chunk-6TZ5LG3O.js +123948 -0
  20. package/dist/chunk-6TZ5LG3O.js.map +1 -0
  21. package/dist/chunk-OGQDXVXY.js +18 -0
  22. package/dist/chunk-OGQDXVXY.js.map +1 -0
  23. package/dist/claude-subscription-account-conflicts.d.ts +1 -0
  24. package/dist/claude-subscription-account-usage.d.ts +39 -0
  25. package/dist/claude-subscription-oauth.d.ts +25 -0
  26. package/dist/claude-subscription-profile.d.ts +9 -0
  27. package/dist/claude-subscription-usage.d.ts +44 -0
  28. package/dist/claude-workspace-connection.d.ts +15 -0
  29. package/dist/codemode.d.ts +32 -1
  30. package/dist/company-brain-okf.d.ts +24 -0
  31. package/dist/connected-machine-computer-access.d.ts +11 -0
  32. package/dist/connection-ownership.d.ts +54 -0
  33. package/dist/controller-data-plane.d.ts +32 -0
  34. package/dist/editable-artifact-office-import.d.ts +7 -4
  35. package/dist/editable-artifact-source-session.d.ts +3 -0
  36. package/dist/editable-artifact-websocket.d.ts +10 -18
  37. package/dist/editable-artifact-workspace-files.d.ts +1 -1
  38. package/dist/fatal-process-boundary.d.ts +25 -0
  39. package/dist/github-access.d.ts +31 -2
  40. package/dist/http/acting-person.d.ts +24 -0
  41. package/dist/http/api-contract-compat.d.ts +19 -0
  42. package/dist/http/api-error.d.ts +27 -1
  43. package/dist/http/cors.d.ts +17 -0
  44. package/dist/http/deprecation.d.ts +42 -0
  45. package/dist/http/interaction-control-error.d.ts +13 -0
  46. package/dist/http/keyed-admission.d.ts +12 -0
  47. package/dist/http/local-browser-boundary.d.ts +72 -0
  48. package/dist/http/metrics-listener.d.ts +22 -0
  49. package/dist/http/path-identifier.d.ts +17 -0
  50. package/dist/http/registered-route-label.d.ts +16 -0
  51. package/dist/http/request-body.d.ts +27 -0
  52. package/dist/http/request-source.d.ts +52 -0
  53. package/dist/http/sse.d.ts +17 -6
  54. package/dist/http/unmatched-route.d.ts +18 -0
  55. package/dist/http/user-content.d.ts +36 -0
  56. package/dist/http/websocket-upgrade-dispatch.d.ts +18 -0
  57. package/dist/index.d.ts +10 -3
  58. package/dist/index.js +1129 -166
  59. package/dist/index.js.map +1 -1
  60. package/dist/integration-endpoint-test.d.ts +20 -0
  61. package/dist/integrations/api-integrations.d.ts +2 -2
  62. package/dist/integrations/atlassian.d.ts +29 -3
  63. package/dist/integrations/connect-authority.d.ts +1 -0
  64. package/dist/integrations/connect-callback-return.d.ts +6 -0
  65. package/dist/integrations/fiken.d.ts +16 -2
  66. package/dist/integrations/github-app-connect.d.ts +47 -0
  67. package/dist/integrations/github-installation-proof.d.ts +3 -0
  68. package/dist/integrations/github-lens-connect.d.ts +116 -0
  69. package/dist/integrations/github-skill-source.d.ts +1 -5
  70. package/dist/integrations/google-drive.d.ts +25 -0
  71. package/dist/integrations/integration-commit-authority.d.ts +13 -0
  72. package/dist/integrations/marketplace-plugin.d.ts +2691 -0
  73. package/dist/integrations/oauth-client.d.ts +178 -18
  74. package/dist/integrations/oauth-profiles.d.ts +222 -0
  75. package/dist/integrations/oauth-return-path.d.ts +1 -0
  76. package/dist/integrations/personal-github-repositories.d.ts +84 -0
  77. package/dist/integrations/personal-github.d.ts +42 -0
  78. package/dist/integrations/pr-review-provider.d.ts +22 -0
  79. package/dist/integrations/provider-oauth.d.ts +19 -1
  80. package/dist/integrations/slack-app-home.d.ts +24 -0
  81. package/dist/integrations/slack-bot.d.ts +367 -12
  82. package/dist/integrations/slack-file-upload-flow.d.ts +62 -0
  83. package/dist/integrations/slack-install.d.ts +20 -0
  84. package/dist/integrations/slack-interactions.d.ts +113 -10
  85. package/dist/integrations/slack-mrkdwn.d.ts +41 -0
  86. package/dist/integrations/slack-routing.d.ts +120 -0
  87. package/dist/integrations/slack-session-defaults.d.ts +58 -0
  88. package/dist/integrations/slack-task-file-upload.d.ts +36 -0
  89. package/dist/integrations/social-oauth.d.ts +7 -0
  90. package/dist/interaction-agent-access.d.ts +15 -0
  91. package/dist/interaction-frame-proxy.d.ts +68 -0
  92. package/dist/interaction-metrics.d.ts +2 -0
  93. package/dist/mcp/action-catalog-types.d.ts +9 -0
  94. package/dist/mcp/action-catalog.gen.d.ts +2 -0
  95. package/dist/mcp/capability-account-readiness.d.ts +7 -0
  96. package/dist/mcp/child-read-evidence.d.ts +9 -0
  97. package/dist/mcp/command-read.d.ts +24 -0
  98. package/dist/mcp/company-profile-agent-admin.d.ts +44 -0
  99. package/dist/mcp/contract-input.d.ts +11 -0
  100. package/dist/mcp/documents.d.ts +7 -5
  101. package/dist/mcp/files.d.ts +2 -2
  102. package/dist/mcp/first-party-tool-permissions.d.ts +650 -0
  103. package/dist/mcp/knowledge-entries.d.ts +5 -0
  104. package/dist/mcp/orchestration-failure-diagnostic.d.ts +43 -0
  105. package/dist/mcp/receipts.d.ts +9 -0
  106. package/dist/mcp/request-abort.d.ts +19 -0
  107. package/dist/mcp/scheduled-task-input.d.ts +1189 -0
  108. package/dist/mcp/scheduled-task-view.d.ts +22 -4
  109. package/dist/mcp/server.d.ts +255 -3
  110. package/dist/mcp/session-event-view.d.ts +94 -0
  111. package/dist/mcp/session-view.d.ts +66 -2
  112. package/dist/mcp/session-wait.d.ts +174 -0
  113. package/dist/mcp-oauth.d.ts +52 -0
  114. package/dist/model-catalog.d.ts +11 -31
  115. package/dist/observability.d.ts +3 -1
  116. package/dist/organization-mcp.d.ts +52 -0
  117. package/dist/organization-recovery-notifications.d.ts +41 -0
  118. package/dist/routes/analytics-consent.d.ts +26 -0
  119. package/dist/routes/api-integrations.d.ts +25 -0
  120. package/dist/routes/api-keys.d.ts +17 -0
  121. package/dist/routes/artifact-catalog.d.ts +6 -0
  122. package/dist/routes/automations.d.ts +19 -0
  123. package/dist/routes/billing.d.ts +49 -0
  124. package/dist/routes/browser-sessions.d.ts +71 -3
  125. package/dist/routes/claude-subscription-accounts.d.ts +3 -0
  126. package/dist/routes/claude-subscription-oauth.d.ts +3 -0
  127. package/dist/routes/client-errors.d.ts +146 -0
  128. package/dist/routes/codex.d.ts +76 -2
  129. package/dist/routes/company-brain.d.ts +3 -0
  130. package/dist/routes/company-profile.d.ts +2 -1
  131. package/dist/routes/computer-sessions.d.ts +14 -0
  132. package/dist/routes/connect.d.ts +5 -0
  133. package/dist/routes/editable-artifacts.d.ts +2 -0
  134. package/dist/routes/external-identity-links.d.ts +6 -0
  135. package/dist/routes/feedback.d.ts +3 -0
  136. package/dist/routes/files.d.ts +4 -0
  137. package/dist/routes/github-browser-pages.d.ts +12 -0
  138. package/dist/routes/github.d.ts +2 -0
  139. package/dist/routes/insights-response-cache.d.ts +10 -0
  140. package/dist/routes/insights-usage.d.ts +12 -0
  141. package/dist/routes/insights.d.ts +30 -1
  142. package/dist/routes/knowledge.d.ts +3 -0
  143. package/dist/routes/managed-auth-session-sets.d.ts +26 -0
  144. package/dist/routes/managed-onboarding.d.ts +29 -0
  145. package/dist/routes/managed-sign-in-methods.d.ts +6 -0
  146. package/dist/routes/model-connection-access.d.ts +3 -0
  147. package/dist/routes/organization-integration-policy.d.ts +3 -0
  148. package/dist/routes/organization-integrations.d.ts +6 -0
  149. package/dist/routes/organization-mcp-connections.d.ts +3 -0
  150. package/dist/routes/organization-model-providers.d.ts +3 -0
  151. package/dist/routes/organization-recovery.d.ts +19 -0
  152. package/dist/routes/organization-service-accounts.d.ts +3 -0
  153. package/dist/routes/organization-sessions.d.ts +26 -0
  154. package/dist/routes/personal-github-git-broker.d.ts +29 -0
  155. package/dist/routes/personal-github.d.ts +3 -0
  156. package/dist/routes/pr-review-github.d.ts +3 -0
  157. package/dist/routes/pr-review.d.ts +3 -0
  158. package/dist/routes/session-artifact-associations.d.ts +4 -0
  159. package/dist/routes/session-history-imports.d.ts +9 -0
  160. package/dist/routes/sessions.d.ts +44 -6
  161. package/dist/routes/skill-content.d.ts +4 -0
  162. package/dist/routes/skill-install-authority.d.ts +6 -0
  163. package/dist/routes/subscription-account-pools.d.ts +34 -0
  164. package/dist/routes/subscription-pool-access.d.ts +16 -0
  165. package/dist/routes/supergrok.d.ts +6 -1
  166. package/dist/routes/usage-allowances.d.ts +12 -0
  167. package/dist/routes/user-resource-authorities.d.ts +3 -0
  168. package/dist/routes/workspace-artifacts.d.ts +2 -1
  169. package/dist/routes/workspace-capture.d.ts +10 -2
  170. package/dist/routes/workspace-integrations.d.ts +35 -0
  171. package/dist/routes/workspace-learning.d.ts +3 -0
  172. package/dist/routes/workspace-model-providers.d.ts +4 -0
  173. package/dist/routes/workspaces.d.ts +29 -0
  174. package/dist/sandbox/auth-callout.d.ts +8 -0
  175. package/dist/sandbox/channel-a.d.ts +19 -1
  176. package/dist/sandbox/connection-authority.d.ts +3 -0
  177. package/dist/sandbox/enrollment.d.ts +9 -1
  178. package/dist/sandbox/machines.d.ts +5 -2
  179. package/dist/sandbox/metrics-ingestion.d.ts +28 -20
  180. package/dist/sandbox/rematerialize.d.ts +2 -0
  181. package/dist/sandbox/session-attach-variable-sets.d.ts +30 -0
  182. package/dist/sandbox/viewer.d.ts +26 -1
  183. package/dist/sandbox-file-artifacts.d.ts +50 -0
  184. package/dist/scheduled-task-deletion.d.ts +13 -0
  185. package/dist/session-create-observability.d.ts +5 -0
  186. package/dist/session-event-view-MPS2OZD3.js +292 -0
  187. package/dist/session-event-view-MPS2OZD3.js.map +1 -0
  188. package/dist/site-session-origin.d.ts +13 -0
  189. package/dist/site-uploads.d.ts +41 -0
  190. package/dist/slack-reaction-files.d.ts +1 -1
  191. package/dist/temporal-schedule-cleanup.d.ts +1 -0
  192. package/dist/temporal-schedule-sync.d.ts +26 -0
  193. package/dist/transcription/providers/xai-subscription.d.ts +1 -0
  194. package/dist/transcription/service.d.ts +2 -1
  195. package/dist/work-discovery-observability.d.ts +33 -0
  196. package/dist/workspace-artifact-content.d.ts +28 -0
  197. package/dist/workspace-artifact-provenance.d.ts +7 -0
  198. package/dist/workspace-delete-observability.d.ts +10 -0
  199. package/dist/workspace-deletion.d.ts +6 -0
  200. package/dist/workspace-state-projection.d.ts +3 -5
  201. package/dist/workspace-tool-gateway-observability.d.ts +17 -0
  202. package/dist/workspace-tool-gateway.d.ts +123 -0
  203. package/dist/workspace-webhook-dispatch.d.ts +26 -0
  204. package/package.json +23 -19
  205. package/src/access-grant-rls.ts +75 -0
  206. package/src/admission-trace.ts +14 -0
  207. package/src/api-websocket.ts +23 -0
  208. package/src/app.ts +1974 -256
  209. package/src/artifact-catalog-cursor.ts +60 -0
  210. package/src/auth/managed-auth-attempt-context.ts +70 -0
  211. package/src/auth/managed-auth-rate-limits.ts +252 -0
  212. package/src/auth/managed-auth-session-adapter.ts +222 -0
  213. package/src/auth/managed-auth.ts +536 -60
  214. package/src/auth/managed-email.ts +174 -0
  215. package/src/auth/managed-sign-in-notifications.ts +81 -0
  216. package/src/auth/new-signups-gate.ts +49 -0
  217. package/src/auth/organization-user-setup.ts +307 -0
  218. package/src/auth/signup-funnel-metrics.ts +164 -0
  219. package/src/browser-controller-authority.ts +1 -186
  220. package/src/browser-ephemeral.ts +39 -0
  221. package/src/browser-state-authority.ts +1 -236
  222. package/src/claude-subscription-account-conflicts.ts +12 -0
  223. package/src/claude-subscription-account-usage.ts +48 -0
  224. package/src/claude-subscription-oauth.ts +362 -0
  225. package/src/claude-subscription-profile.ts +73 -0
  226. package/src/claude-subscription-usage.ts +97 -0
  227. package/src/claude-workspace-connection.ts +82 -0
  228. package/src/codemode.ts +194 -30
  229. package/src/codex-realtime.ts +8 -2
  230. package/src/company-brain-okf.ts +340 -0
  231. package/src/connected-machine-computer-access.ts +33 -0
  232. package/src/connection-ownership.ts +199 -0
  233. package/src/controller-data-plane.ts +81 -0
  234. package/src/editable-artifact-native-kernel.ts +26 -15
  235. package/src/editable-artifact-office-import.ts +77 -9
  236. package/src/editable-artifact-production.ts +23 -7
  237. package/src/editable-artifact-source-session.ts +123 -0
  238. package/src/editable-artifact-websocket.ts +27 -21
  239. package/src/editable-artifact-workspace-files.ts +31 -6
  240. package/src/fatal-process-boundary.ts +246 -0
  241. package/src/gateway-realtime.ts +1 -1
  242. package/src/github-access.ts +147 -5
  243. package/src/http/acting-person.ts +71 -0
  244. package/src/http/api-contract-compat.ts +49 -0
  245. package/src/http/api-error.ts +115 -1
  246. package/src/http/auth.ts +48 -0
  247. package/src/http/cors.ts +67 -0
  248. package/src/http/deprecation.ts +118 -0
  249. package/src/http/interaction-control-error.ts +164 -0
  250. package/src/http/keyed-admission.ts +29 -0
  251. package/src/http/local-browser-boundary.ts +343 -0
  252. package/src/http/metrics-listener.ts +57 -0
  253. package/src/http/path-identifier.ts +54 -0
  254. package/src/http/registered-route-label.ts +34 -0
  255. package/src/http/request-body.ts +127 -0
  256. package/src/http/request-source.ts +178 -0
  257. package/src/http/sse.ts +486 -132
  258. package/src/http/unmatched-route.ts +60 -0
  259. package/src/http/user-content.ts +158 -0
  260. package/src/http/websocket-upgrade-dispatch.ts +29 -0
  261. package/src/index.ts +157 -52
  262. package/src/integration-endpoint-test.ts +291 -0
  263. package/src/integrations/api-integrations.ts +98 -11
  264. package/src/integrations/atlassian.ts +474 -136
  265. package/src/integrations/connect-authority.ts +2 -0
  266. package/src/integrations/connect-callback-return.ts +86 -0
  267. package/src/integrations/fiken.ts +354 -64
  268. package/src/integrations/github-app-connect.ts +435 -0
  269. package/src/integrations/github-installation-proof.ts +60 -0
  270. package/src/integrations/github-lens-connect.ts +76 -0
  271. package/src/integrations/github-skill-source.ts +2 -142
  272. package/src/integrations/google-drive.ts +820 -222
  273. package/src/integrations/integration-commit-authority.ts +69 -0
  274. package/src/integrations/marketplace-plugin.ts +48 -0
  275. package/src/integrations/oauth-client.ts +1356 -459
  276. package/src/integrations/oauth-profiles.ts +537 -0
  277. package/src/integrations/oauth-return-path.ts +18 -0
  278. package/src/integrations/personal-github-repositories.ts +674 -0
  279. package/src/integrations/personal-github.ts +825 -0
  280. package/src/integrations/pr-review-provider.ts +246 -0
  281. package/src/integrations/provider-oauth.ts +250 -22
  282. package/src/integrations/slack-app-home.ts +300 -0
  283. package/src/integrations/slack-bot.ts +963 -239
  284. package/src/integrations/slack-file-upload-flow.ts +147 -0
  285. package/src/integrations/slack-install.ts +83 -0
  286. package/src/integrations/slack-interactions.ts +3170 -474
  287. package/src/integrations/slack-mrkdwn.ts +193 -0
  288. package/src/integrations/slack-routing.ts +337 -0
  289. package/src/integrations/slack-session-defaults.ts +224 -0
  290. package/src/integrations/slack-task-file-upload.ts +323 -0
  291. package/src/integrations/social-oauth.ts +213 -15
  292. package/src/interaction-agent-access.ts +48 -0
  293. package/src/interaction-frame-proxy.ts +409 -0
  294. package/src/interaction-metrics.ts +30 -0
  295. package/src/mcp/action-catalog-types.ts +9 -0
  296. package/src/mcp/action-catalog.gen.ts +6696 -0
  297. package/src/mcp/capability-account-readiness.ts +17 -0
  298. package/src/mcp/child-read-evidence.ts +19 -0
  299. package/src/mcp/command-read.ts +109 -0
  300. package/src/mcp/company-profile-agent-admin.ts +198 -0
  301. package/src/mcp/contract-input.ts +141 -0
  302. package/src/mcp/documents.ts +65 -377
  303. package/src/mcp/editable-artifacts.ts +12 -6
  304. package/src/mcp/files.ts +80 -38
  305. package/src/mcp/first-party-tool-permissions.ts +305 -0
  306. package/src/mcp/knowledge-entries.ts +322 -0
  307. package/src/mcp/orchestration-failure-diagnostic.ts +43 -0
  308. package/src/mcp/receipts.ts +34 -0
  309. package/src/mcp/request-abort.ts +44 -0
  310. package/src/mcp/scheduled-task-input.ts +76 -0
  311. package/src/mcp/scheduled-task-view.ts +44 -3
  312. package/src/mcp/server.ts +3030 -1267
  313. package/src/mcp/session-event-view.ts +383 -0
  314. package/src/mcp/session-view.ts +84 -3
  315. package/src/mcp/session-wait.ts +665 -0
  316. package/src/mcp-oauth.ts +1123 -0
  317. package/src/model-catalog.ts +91 -333
  318. package/src/observability.ts +15 -1
  319. package/src/organization-mcp.ts +397 -0
  320. package/src/organization-recovery-notifications.ts +103 -0
  321. package/src/routes/analytics-consent.ts +123 -0
  322. package/src/routes/api-integrations.ts +364 -74
  323. package/src/routes/api-keys.ts +336 -10
  324. package/src/routes/artifact-catalog.ts +279 -0
  325. package/src/routes/automations.ts +768 -0
  326. package/src/routes/billing.ts +461 -46
  327. package/src/routes/browser-sessions.ts +839 -131
  328. package/src/routes/canonical-human-identities.ts +90 -14
  329. package/src/routes/capabilities.ts +107 -6
  330. package/src/routes/channels.ts +20 -3
  331. package/src/routes/claude-subscription-accounts.ts +334 -0
  332. package/src/routes/claude-subscription-oauth.ts +142 -0
  333. package/src/routes/client-errors.ts +362 -0
  334. package/src/routes/codex.ts +833 -116
  335. package/src/routes/company-brain.ts +322 -0
  336. package/src/routes/company-profile.ts +65 -1
  337. package/src/routes/computer-sessions.ts +557 -106
  338. package/src/routes/connect.ts +1938 -0
  339. package/src/routes/connections.ts +1040 -349
  340. package/src/routes/documents.ts +488 -287
  341. package/src/routes/editable-artifacts.ts +79 -9
  342. package/src/routes/enrollments.ts +135 -39
  343. package/src/routes/environments.ts +154 -58
  344. package/src/routes/external-identity-links.ts +201 -0
  345. package/src/routes/feedback.ts +113 -0
  346. package/src/routes/files.ts +337 -39
  347. package/src/routes/github-browser-pages.ts +162 -0
  348. package/src/routes/github.ts +446 -144
  349. package/src/routes/insights-response-cache.ts +83 -0
  350. package/src/routes/insights-usage.ts +345 -0
  351. package/src/routes/insights.ts +153 -19
  352. package/src/routes/install.ts +133 -38
  353. package/src/routes/integration-facets.ts +107 -33
  354. package/src/routes/interaction-resources.ts +30 -3
  355. package/src/routes/knowledge.ts +488 -0
  356. package/src/routes/machines.ts +313 -15
  357. package/src/routes/managed-auth-session-sets.ts +1218 -0
  358. package/src/routes/managed-onboarding.ts +337 -0
  359. package/src/routes/managed-sign-in-methods.ts +433 -0
  360. package/src/routes/memory-slack-publications.ts +3 -2
  361. package/src/routes/model-connection-access.ts +208 -0
  362. package/src/routes/organization-integration-policy.ts +116 -0
  363. package/src/routes/organization-integrations.ts +326 -0
  364. package/src/routes/organization-mcp-connections.ts +178 -0
  365. package/src/routes/organization-memberships.ts +1019 -27
  366. package/src/routes/organization-model-providers.ts +293 -0
  367. package/src/routes/organization-recovery.ts +325 -0
  368. package/src/routes/organization-service-accounts.ts +128 -0
  369. package/src/routes/organization-sessions.ts +350 -0
  370. package/src/routes/personal-github-git-broker.ts +785 -0
  371. package/src/routes/personal-github.ts +370 -0
  372. package/src/routes/plugins.ts +251 -22
  373. package/src/routes/pr-review-github.ts +881 -0
  374. package/src/routes/pr-review.ts +622 -0
  375. package/src/routes/preference-registry.ts +12 -93
  376. package/src/routes/rigs.ts +167 -50
  377. package/src/routes/scheduled-tasks.ts +346 -41
  378. package/src/routes/session-artifact-associations.ts +87 -0
  379. package/src/routes/session-history-imports.ts +280 -0
  380. package/src/routes/sessions.ts +2306 -264
  381. package/src/routes/skill-content.ts +297 -0
  382. package/src/routes/skill-install-authority.ts +64 -0
  383. package/src/routes/skills.ts +62 -9
  384. package/src/routes/social.ts +46 -18
  385. package/src/routes/subscription-account-pools.ts +436 -0
  386. package/src/routes/subscription-pool-access.ts +126 -0
  387. package/src/routes/supergrok.ts +219 -303
  388. package/src/routes/transcription-recordings.ts +38 -3
  389. package/src/routes/transcriptions.ts +4 -0
  390. package/src/routes/usage-allowances.ts +378 -0
  391. package/src/routes/user-resource-authorities.ts +138 -0
  392. package/src/routes/video-generation.ts +14 -10
  393. package/src/routes/workspace-artifacts.ts +275 -69
  394. package/src/routes/workspace-capture.ts +21 -0
  395. package/src/routes/workspace-integrations.ts +576 -0
  396. package/src/routes/workspace-learning.ts +159 -0
  397. package/src/routes/workspace-model-providers.ts +148 -0
  398. package/src/routes/workspace-state.ts +38 -25
  399. package/src/routes/workspaces.ts +1004 -127
  400. package/src/sandbox/auth-callout.ts +96 -19
  401. package/src/sandbox/channel-a.ts +323 -24
  402. package/src/sandbox/connection-authority.ts +3 -0
  403. package/src/sandbox/enrollment.ts +81 -6
  404. package/src/sandbox/machines.ts +218 -76
  405. package/src/sandbox/metrics-ingestion.ts +411 -62
  406. package/src/sandbox/rematerialize.ts +121 -19
  407. package/src/sandbox/session-attach-variable-sets.ts +131 -0
  408. package/src/sandbox/viewer.ts +280 -25
  409. package/src/sandbox-file-artifacts.ts +413 -0
  410. package/src/scheduled-task-deletion.ts +138 -0
  411. package/src/session-create-observability.ts +46 -0
  412. package/src/session-realtime-context.ts +6 -3
  413. package/src/site-session-origin.ts +48 -0
  414. package/src/site-uploads.ts +224 -0
  415. package/src/slack-reaction-files.ts +16 -5
  416. package/src/temporal-schedule-cleanup.ts +13 -0
  417. package/src/temporal-schedule-sync.ts +74 -0
  418. package/src/transcription/providers/codex-subscription.ts +2 -0
  419. package/src/transcription/providers/openai.ts +1 -0
  420. package/src/transcription/providers/xai-subscription.ts +49 -3
  421. package/src/transcription/segmenter.ts +8 -2
  422. package/src/transcription/service.ts +43 -6
  423. package/src/work-discovery-observability.ts +121 -0
  424. package/src/workspace-artifact-content.ts +144 -0
  425. package/src/workspace-artifact-provenance.ts +104 -0
  426. package/src/workspace-delete-observability.ts +75 -0
  427. package/src/workspace-deletion.ts +73 -0
  428. package/src/workspace-state-projection.ts +18 -148
  429. package/src/workspace-tool-gateway-observability.ts +100 -0
  430. package/src/workspace-tool-gateway.ts +870 -0
  431. package/src/workspace-webhook-dispatch.ts +190 -0
  432. package/src/xai-subscription-auth.ts +9 -2
  433. package/dist/chunk-2PQMSRCB.js +0 -49759
  434. package/dist/chunk-2PQMSRCB.js.map +0 -1
  435. package/dist/routes/packs.d.ts +0 -3
  436. package/src/routes/packs.ts +0 -729
package/src/http/sse.ts CHANGED
@@ -5,23 +5,29 @@ import {
5
5
  } from "@opengeni/contracts";
6
6
  import {
7
7
  getWorkspaceInteractionRevisionState,
8
- listSessionEvents,
8
+ listSessionEventPage,
9
9
  listWorkspaceControlEvents,
10
10
  type Database,
11
11
  } from "@opengeni/db";
12
12
  import {
13
+ coalesceSessionEventDeltasWithCoverage,
13
14
  formatSessionEventSse,
14
15
  formatWorkspaceControlEventSse,
16
+ requireSessionEventDurableFanoutCapability,
15
17
  SESSION_EVENT_SSE_FRAME_MAX_BYTES,
16
18
  type EventBus,
17
19
  } from "@opengeni/events";
18
20
  import type { Observability } from "@opengeni/observability";
21
+ import { MANAGED_AUTH_ACTOR_EPOCH_HEADER } from "@opengeni/core/managed-auth-session-sets";
19
22
 
20
23
  const SESSION_REPLAY_PAGE_SIZE = 100;
24
+ const SESSION_REPLAY_PAGE_MAX_BYTES = 8 * 1024 * 1024;
21
25
  const WORKSPACE_CONTROL_REPLAY_PAGE_SIZE = 100;
22
26
  export const SSE_QUEUED_FRAME_MAX_COUNT = 1;
23
27
  export const SSE_WRITE_STALL_TIMEOUT_MS = 30_000;
24
28
  export const SSE_HEARTBEAT_INTERVAL_MS = 15_000;
29
+ export const HTTP1_BROWSER_SSE_BATCH_MAX_BYTES = 512 * 1024;
30
+ export const HTTP1_BROWSER_SSE_BATCH_CONTENT_TYPE = "application/vnd.opengeni.sse-batch";
25
31
  type SseStreamKind = "session" | "workspace_control" | "workspace_interaction";
26
32
  const activeSseStreams: Record<SseStreamKind, number> = {
27
33
  session: 0,
@@ -37,6 +43,7 @@ export type SseDeliveryBoundObservation = {
37
43
  };
38
44
 
39
45
  export type ByteBoundedSseStreamOptions = {
46
+ connectionLifetimeMs?: number | undefined;
40
47
  maxQueuedBytes?: number;
41
48
  stallTimeoutMs?: number;
42
49
  onStop?: () => void;
@@ -57,27 +64,36 @@ export type ByteBoundedSseStream = {
57
64
  * checking `desiredSize` can still accumulate an unbounded server-side queue.
58
65
  *
59
66
  * One writer is expected per stream. The Web Streams queue holds at most one
60
- * complete frame, and that frame must fit inside the byte cap. A second write
67
+ * complete frame. One oversized frame is delivered intact rather than lost;
68
+ * the byte budget is a batching target, not a content limit. A second write
61
69
  * waits for consumer pull only for a bounded interval; cancellation or a stalled
62
70
  * reader wakes it and terminates upstream delivery before another durable page is
63
- * read. One frame is deliberate: it makes both queued-frame count and queued
64
- * bytes independently bounded instead of relying on byte accounting alone.
71
+ * read. One frame is deliberate: queued memory is bounded by one event, not
72
+ * by the size of the session, without imposing a lossy per-message limit.
65
73
  */
66
74
  export function createByteBoundedSseStream(
67
75
  options: ByteBoundedSseStreamOptions = {},
68
76
  ): ByteBoundedSseStream {
69
77
  const maxQueuedBytes = options.maxQueuedBytes ?? SESSION_EVENT_SSE_FRAME_MAX_BYTES;
70
78
  const stallTimeoutMs = options.stallTimeoutMs ?? SSE_WRITE_STALL_TIMEOUT_MS;
79
+ const connectionLifetimeMs = options.connectionLifetimeMs;
71
80
  if (!Number.isSafeInteger(maxQueuedBytes) || maxQueuedBytes <= 0) {
72
81
  throw new RangeError("SSE byte high-water mark must be a positive safe integer");
73
82
  }
74
83
  if (!Number.isSafeInteger(stallTimeoutMs) || stallTimeoutMs <= 0) {
75
84
  throw new RangeError("SSE write stall timeout must be a positive safe integer");
76
85
  }
86
+ if (
87
+ connectionLifetimeMs !== undefined &&
88
+ (!Number.isSafeInteger(connectionLifetimeMs) || connectionLifetimeMs <= 0)
89
+ ) {
90
+ throw new RangeError("SSE connection lifetime must be a positive safe integer");
91
+ }
77
92
  const encoder = new TextEncoder();
78
93
  let controller!: ReadableStreamDefaultController<Uint8Array>;
79
94
  let stopped = false;
80
95
  let capacityWake: (() => void) | null = null;
96
+ let lifetimeTimer: ReturnType<typeof setTimeout> | null = null;
81
97
  let queuedFrames = 0;
82
98
  let queuedBytes = 0;
83
99
 
@@ -89,6 +105,10 @@ export function createByteBoundedSseStream(
89
105
  const stop = (settle: () => void) => {
90
106
  if (stopped) return;
91
107
  stopped = true;
108
+ if (lifetimeTimer !== null) {
109
+ clearTimeout(lifetimeTimer);
110
+ lifetimeTimer = null;
111
+ }
92
112
  wakeWriter();
93
113
  options.onStop?.();
94
114
  try {
@@ -123,29 +143,19 @@ export function createByteBoundedSseStream(
123
143
  size: () => 1,
124
144
  },
125
145
  );
146
+ if (connectionLifetimeMs !== undefined) {
147
+ lifetimeTimer = setTimeout(() => stop(() => controller.close()), connectionLifetimeMs);
148
+ }
126
149
 
127
150
  return {
128
151
  stream,
129
152
  write: async (frame) => {
130
- const chunk = encoder.encode(frame);
131
- if (chunk.byteLength > maxQueuedBytes) {
132
- const error = new RangeError(
133
- `SSE frame cannot fit in the configured queue (${chunk.byteLength} > ${maxQueuedBytes} bytes)`,
134
- );
135
- options.onObservation?.({
136
- reason: "frame_too_large",
137
- desiredSize: controller.desiredSize,
138
- queuedFrames,
139
- queuedBytes,
140
- });
141
- stop(() => controller.error(error));
142
- throw error;
143
- }
144
153
  for (;;) {
145
154
  if (stopped) return false;
146
155
  const desired = controller.desiredSize;
147
156
  if (desired === null) return false;
148
157
  if (desired >= 1 && queuedFrames === 0) {
158
+ const chunk = encoder.encode(frame);
149
159
  controller.enqueue(chunk);
150
160
  queuedFrames = 1;
151
161
  queuedBytes = chunk.byteLength;
@@ -267,32 +277,42 @@ export async function sseSessionStream(
267
277
  signal: AbortSignal,
268
278
  options: SessionSseDeliveryOptions = {},
269
279
  ): Promise<Response> {
270
- const heartbeatIntervalMs = resolveHeartbeatInterval(options.heartbeatIntervalMs);
271
- if (
272
- options.reauthorize &&
273
- options.reauthorizeAfterMs !== undefined &&
274
- (!Number.isSafeInteger(options.reauthorizeAfterMs) ||
275
- options.reauthorizeAfterMs < 1_000 ||
276
- options.reauthorizeAfterMs > 60_000)
277
- ) {
278
- throw new RangeError("session stream reauthorization must be between 1000 and 60000ms");
280
+ if (isHttp1BrowserBatch(options)) {
281
+ const { events } = await listSessionEventPage(db, workspaceId, sessionId, {
282
+ after,
283
+ limit: SESSION_REPLAY_PAGE_SIZE,
284
+ maxBytes: options.finiteResponseMaxBytes ?? HTTP1_BROWSER_SSE_BATCH_MAX_BYTES,
285
+ });
286
+ await options.reauthorize?.();
287
+ const compactProjection = coalesceSessionEventDeltasWithCoverage(events);
288
+ return finiteSseBatchResponse(
289
+ compactProjection.events.map((event) =>
290
+ formatSessionEventSse(
291
+ event,
292
+ compactProjection.coveredThroughBySequence.get(event.sequence) ?? event.sequence,
293
+ ),
294
+ ),
295
+ options,
296
+ );
279
297
  }
298
+ const durableFanout = requireSessionEventDurableFanoutCapability(bus);
299
+ const heartbeatIntervalMs = resolveHeartbeatInterval(options.heartbeatIntervalMs);
280
300
  let lastSent = after;
281
301
  let bootstrapping = true;
282
302
  let newestBuffered: SessionEvent | null = null;
283
303
  let unsubscribe: (() => void) | null = null;
284
304
  let delivery: LatestWinsDelivery<SessionEvent> | null = null;
285
- let reauthorizationTimer: ReturnType<typeof setTimeout> | null = null;
305
+ let stopReconnectObservation = () => {};
306
+ let stopReauthorization = () => {};
286
307
  let heartbeatTimer: ReturnType<typeof setTimeout> | null = null;
287
308
  let detachAbortListener = () => {};
288
309
  let closeMetrics = () => {};
289
310
  const stopUpstream = () => {
290
311
  closeMetrics();
291
312
  detachAbortListener();
292
- if (reauthorizationTimer) {
293
- clearTimeout(reauthorizationTimer);
294
- reauthorizationTimer = null;
295
- }
313
+ stopReconnectObservation();
314
+ stopReconnectObservation = () => {};
315
+ stopReauthorization();
296
316
  if (heartbeatTimer) {
297
317
  clearTimeout(heartbeatTimer);
298
318
  heartbeatTimer = null;
@@ -303,6 +323,7 @@ export async function sseSessionStream(
303
323
  release?.();
304
324
  };
305
325
  const channel = createByteBoundedSseStream({
326
+ connectionLifetimeMs: options.connectionLifetimeMs,
306
327
  maxQueuedBytes: options.maxQueuedBytes ?? SESSION_EVENT_SSE_FRAME_MAX_BYTES,
307
328
  ...(options.stallTimeoutMs === undefined ? {} : { stallTimeoutMs: options.stallTimeoutMs }),
308
329
  onObservation: sseObservationReporter("session", options),
@@ -313,25 +334,111 @@ export async function sseSessionStream(
313
334
  const fail = (error: unknown) => {
314
335
  channel.fail(retryableSseFailure("session event stream delivery failed", error));
315
336
  };
316
- const scheduleReauthorization = () => {
317
- if (!options.reauthorize || channel.stopped()) return;
318
- const interval = options.reauthorizeAfterMs ?? 15_000;
319
- reauthorizationTimer = setTimeout(() => {
320
- reauthorizationTimer = null;
321
- void options.reauthorize!()
322
- .then(scheduleReauthorization)
323
- .catch((error) => fail(error));
324
- }, interval);
325
- };
326
- scheduleReauthorization();
337
+ stopReauthorization = startSseReauthorization(options, channel.stopped, channel.close);
327
338
  let writeTail = Promise.resolve();
328
339
  const writeFrame = (frame: string): Promise<void> => {
329
340
  const write = writeTail.then(async () => {
341
+ // A periodic check closes an idle stream, while this exact pre-delivery
342
+ // check prevents a buffered/replayed event from crossing a revocation
343
+ // boundary merely because its timer has not fired yet.
344
+ await reauthorizeSseOrClose(options, channel);
330
345
  if (!(await channel.write(frame))) throw new SseStreamStoppedError();
331
346
  });
332
347
  writeTail = write.catch(() => {});
333
348
  return write;
334
349
  };
350
+ const deliverDurableThrough = async (targetSequence?: number) => {
351
+ while (true) {
352
+ if (targetSequence !== undefined && lastSent >= targetSequence) return;
353
+ const previousLastSent = lastSent;
354
+ const limit =
355
+ targetSequence === undefined
356
+ ? SESSION_REPLAY_PAGE_SIZE
357
+ : Math.min(SESSION_REPLAY_PAGE_SIZE, targetSequence - lastSent);
358
+ const replayPage = await listSessionEventPage(db, workspaceId, sessionId, {
359
+ after: lastSent,
360
+ limit,
361
+ maxBytes: SESSION_REPLAY_PAGE_MAX_BYTES,
362
+ });
363
+ const page = replayPage.events;
364
+ const eligible =
365
+ targetSequence === undefined
366
+ ? page
367
+ : page.filter((event) => event.sequence <= targetSequence);
368
+ if (eligible.length === 0) {
369
+ if (targetSequence === undefined) return;
370
+ throw new Error(
371
+ `Session event replay stalled before sequence ${targetSequence}; last sent ${lastSent}`,
372
+ );
373
+ }
374
+ // The durable audit log remains exact. The browser transport combines
375
+ // adjacent text deltas into lossless batches carrying `coalescedUntil`, so
376
+ // a long answer cannot create thousands of React renders and starve
377
+ // command acknowledgements behind its own token stream.
378
+ const compactProjection = coalesceSessionEventDeltasWithCoverage(eligible);
379
+ for (const projected of compactProjection.events) {
380
+ const coveredThrough =
381
+ compactProjection.coveredThroughBySequence.get(projected.sequence) ?? projected.sequence;
382
+ await writeFrame(formatSessionEventSse(projected, coveredThrough));
383
+ lastSent = coveredThrough;
384
+ }
385
+ if (lastSent <= previousLastSent) {
386
+ throw new Error(`Session event replay made no progress after sequence ${lastSent}`);
387
+ }
388
+ if (targetSequence !== undefined && lastSent >= targetSequence) return;
389
+ if (targetSequence === undefined && !replayPage.hasMore) return;
390
+ // A byte-selected page may contain fewer rows than requested, especially
391
+ // when one large message travels alone. Use explicit continuation rather
392
+ // than page length; live publication covers appends after the snapshot.
393
+ }
394
+ };
395
+ let durableDeliveryTail = Promise.resolve();
396
+ const reconcileDurableThrough = (targetSequence?: number): Promise<void> => {
397
+ const deliveryRun = durableDeliveryTail.then(() => deliverDurableThrough(targetSequence));
398
+ durableDeliveryTail = deliveryRun.catch(() => {});
399
+ return deliveryRun;
400
+ };
401
+ let newestReconnectGeneration = 0;
402
+ let reconnectReconcilePending = false;
403
+ let reconnectReconcileRunning = false;
404
+ const drainReconnectReconciliation = () => {
405
+ if (
406
+ bootstrapping ||
407
+ reconnectReconcileRunning ||
408
+ !reconnectReconcilePending ||
409
+ channel.stopped()
410
+ ) {
411
+ return;
412
+ }
413
+ reconnectReconcileRunning = true;
414
+ void (async () => {
415
+ while (reconnectReconcilePending && !channel.stopped()) {
416
+ // Multiple reconnects during one durable read collapse into one newest
417
+ // catch-up. Postgres is authoritative, so that later read covers every
418
+ // disconnect window without one query per heartbeat or buffered event.
419
+ reconnectReconcilePending = false;
420
+ await reconcileDurableThrough();
421
+ }
422
+ })()
423
+ .catch((error) => {
424
+ if (!(error instanceof SseStreamStoppedError)) fail(error);
425
+ })
426
+ .finally(() => {
427
+ reconnectReconcileRunning = false;
428
+ drainReconnectReconciliation();
429
+ });
430
+ };
431
+ const scheduleReconnectReconciliation = (generation: number) => {
432
+ if (generation <= newestReconnectGeneration || channel.stopped()) return;
433
+ newestReconnectGeneration = generation;
434
+ reconnectReconcilePending = true;
435
+ drainReconnectReconciliation();
436
+ };
437
+ const send = async (event: SessionEvent) => {
438
+ const targetSequence = event.sequence;
439
+ if (targetSequence <= lastSent) return;
440
+ await reconcileDurableThrough(targetSequence);
441
+ };
335
442
  const scheduleHeartbeat = () => {
336
443
  if (channel.stopped()) return;
337
444
  heartbeatTimer = setTimeout(() => {
@@ -343,66 +450,43 @@ export async function sseSessionStream(
343
450
  });
344
451
  }, heartbeatIntervalMs);
345
452
  };
346
- const send = async (event: SessionEvent) => {
347
- if (event.sequence <= lastSent) return;
348
- if (event.sequence > lastSent + 1) {
349
- while (lastSent + 1 < event.sequence) {
350
- const previousLastSent = lastSent;
351
- const missing = await listSessionEvents(db, workspaceId, sessionId, {
352
- after: lastSent,
353
- limit: Math.min(SESSION_REPLAY_PAGE_SIZE, event.sequence - lastSent - 1),
354
- });
355
- if (missing.length === 0) {
356
- throw new Error(
357
- `Session event replay stalled before sequence ${event.sequence}; last sent ${lastSent}`,
358
- );
359
- }
360
- for (const missed of missing) {
361
- if (missed.sequence >= event.sequence) break;
362
- if (missed.sequence > lastSent) {
363
- await writeFrame(formatSessionEventSse(missed));
364
- lastSent = missed.sequence;
365
- }
366
- }
367
- if (lastSent === previousLastSent) {
368
- throw new Error(
369
- `Session event replay made no progress before sequence ${event.sequence}; last sent ${lastSent}`,
370
- );
371
- }
372
- }
373
- }
374
- await writeFrame(formatSessionEventSse(event));
375
- lastSent = event.sequence;
376
- };
377
453
  delivery = createLatestWinsDelivery(send, fail);
454
+ stopReconnectObservation = durableFanout.subscribeRecovery(scheduleReconnectReconciliation);
378
455
 
379
456
  void (async () => {
380
- const release = await bus.subscribe(workspaceId, sessionId, (events) => {
381
- if (bootstrapping) {
382
- for (const event of events) {
383
- if (!newestBuffered || event.sequence > newestBuffered.sequence) {
384
- newestBuffered = event;
457
+ const release = await bus.subscribe(
458
+ workspaceId,
459
+ sessionId,
460
+ (events) => {
461
+ if (bootstrapping) {
462
+ for (const event of events) {
463
+ if (!newestBuffered || event.sequence > newestBuffered.sequence) {
464
+ newestBuffered = event;
465
+ }
385
466
  }
467
+ } else {
468
+ delivery?.publish(events);
386
469
  }
387
- } else {
388
- delivery?.publish(events);
389
- }
390
- });
470
+ },
471
+ {
472
+ // Live fanout stopped while this stream still held it. Heartbeats alone
473
+ // would keep a silently stale timeline open; fail retryably so the
474
+ // client reconnects and replays from the durable Postgres cursor.
475
+ onTerminated: (error) =>
476
+ fail(retryableSseFailure("session live fanout subscription ended", error)),
477
+ },
478
+ );
391
479
  if (channel.stopped()) {
392
480
  release();
393
481
  return;
394
482
  }
395
483
  unsubscribe = release;
396
484
 
397
- await replaySessionEvents(
398
- (cursor, limit) => listSessionEvents(db, workspaceId, sessionId, cursor, limit),
399
- send,
400
- after,
401
- SESSION_REPLAY_PAGE_SIZE,
402
- );
485
+ await reconcileDurableThrough();
403
486
  await writeFrame(": connected\n\n");
404
487
  scheduleHeartbeat();
405
488
  bootstrapping = false;
489
+ drainReconnectReconciliation();
406
490
  const buffered = newestBuffered;
407
491
  newestBuffered = null;
408
492
  if (buffered) delivery.publish([buffered]);
@@ -419,13 +503,7 @@ export async function sseSessionStream(
419
503
  detachAbortListener = () => signal.removeEventListener("abort", abort);
420
504
  }
421
505
 
422
- return new Response(channel.stream, {
423
- headers: {
424
- "Content-Type": "text/event-stream; charset=utf-8",
425
- "Cache-Control": "no-cache, no-transform",
426
- Connection: "keep-alive",
427
- },
428
- });
506
+ return await sseHttpResponse(channel.stream, options);
429
507
  }
430
508
 
431
509
  export async function replaySessionEvents(
@@ -465,18 +543,35 @@ export async function sseWorkspaceControlStream(
465
543
  signal: AbortSignal,
466
544
  options: SseDeliveryOptions = {},
467
545
  ): Promise<Response> {
546
+ if (isHttp1BrowserBatch(options)) {
547
+ const events = await listWorkspaceControlEvents(
548
+ db,
549
+ workspaceId,
550
+ after,
551
+ WORKSPACE_CONTROL_REPLAY_PAGE_SIZE,
552
+ );
553
+ await options.reauthorize?.();
554
+ return finiteSseBatchResponse(
555
+ events
556
+ .sort((left, right) => left.sequence - right.sequence)
557
+ .map(formatWorkspaceControlEventSse),
558
+ options,
559
+ );
560
+ }
468
561
  const heartbeatIntervalMs = resolveHeartbeatInterval(options.heartbeatIntervalMs);
469
562
  let lastSent = after;
470
563
  let bootstrapping = true;
471
564
  let newestBuffered: WorkspaceControlEvent | null = null;
472
565
  let unsubscribe: (() => void) | null = null;
473
566
  let delivery: LatestWinsDelivery<WorkspaceControlEvent> | null = null;
567
+ let stopReauthorization = () => {};
474
568
  let heartbeatTimer: ReturnType<typeof setTimeout> | null = null;
475
569
  let detachAbortListener = () => {};
476
570
  let closeMetrics = () => {};
477
571
  const stopUpstream = () => {
478
572
  closeMetrics();
479
573
  detachAbortListener();
574
+ stopReauthorization();
480
575
  if (heartbeatTimer) {
481
576
  clearTimeout(heartbeatTimer);
482
577
  heartbeatTimer = null;
@@ -487,6 +582,7 @@ export async function sseWorkspaceControlStream(
487
582
  release?.();
488
583
  };
489
584
  const channel = createByteBoundedSseStream({
585
+ connectionLifetimeMs: options.connectionLifetimeMs,
490
586
  maxQueuedBytes: options.maxQueuedBytes ?? SESSION_EVENT_SSE_FRAME_MAX_BYTES,
491
587
  ...(options.stallTimeoutMs === undefined ? {} : { stallTimeoutMs: options.stallTimeoutMs }),
492
588
  onObservation: sseObservationReporter("workspace_control", options),
@@ -497,9 +593,11 @@ export async function sseWorkspaceControlStream(
497
593
  const fail = (error: unknown) => {
498
594
  channel.fail(retryableSseFailure("workspace control stream delivery failed", error));
499
595
  };
596
+ stopReauthorization = startSseReauthorization(options, channel.stopped, channel.close);
500
597
  let writeTail = Promise.resolve();
501
598
  const writeFrame = (frame: string): Promise<void> => {
502
599
  const write = writeTail.then(async () => {
600
+ await reauthorizeSseOrClose(options, channel);
503
601
  if (!(await channel.write(frame))) throw new SseStreamStoppedError();
504
602
  });
505
603
  writeTail = write.catch(() => {});
@@ -551,13 +649,22 @@ export async function sseWorkspaceControlStream(
551
649
  delivery = createLatestWinsDelivery(send, fail);
552
650
 
553
651
  void (async () => {
554
- const release = await bus.subscribeWorkspaceControl(workspaceId, (event) => {
555
- if (bootstrapping) {
556
- if (!newestBuffered || event.sequence > newestBuffered.sequence) newestBuffered = event;
557
- } else {
558
- delivery?.publish([event]);
559
- }
560
- });
652
+ const release = await bus.subscribeWorkspaceControl(
653
+ workspaceId,
654
+ (event) => {
655
+ if (bootstrapping) {
656
+ if (!newestBuffered || event.sequence > newestBuffered.sequence) newestBuffered = event;
657
+ } else {
658
+ delivery?.publish([event]);
659
+ }
660
+ },
661
+ {
662
+ // Same contract as session SSE: a dead live subscription must reconnect
663
+ // and replay the durable control cursor, not idle on heartbeats.
664
+ onTerminated: (error) =>
665
+ fail(retryableSseFailure("workspace control live subscription ended", error)),
666
+ },
667
+ );
561
668
  if (channel.stopped()) {
562
669
  release();
563
670
  return;
@@ -588,13 +695,7 @@ export async function sseWorkspaceControlStream(
588
695
  detachAbortListener = () => signal.removeEventListener("abort", abort);
589
696
  }
590
697
 
591
- return new Response(channel.stream, {
592
- headers: {
593
- "Content-Type": "text/event-stream; charset=utf-8",
594
- "Cache-Control": "no-cache, no-transform",
595
- Connection: "keep-alive",
596
- },
597
- });
698
+ return await sseHttpResponse(channel.stream, options);
598
699
  }
599
700
 
600
701
  export type WorkspaceInteractionSseOptions = SseDeliveryOptions & {
@@ -618,12 +719,42 @@ export async function sseWorkspaceLiveStream(
618
719
  signal: AbortSignal,
619
720
  options: WorkspaceInteractionSseOptions = {},
620
721
  ): Promise<Response> {
722
+ if (isHttp1BrowserBatch(options)) {
723
+ const [controlEvents, interactionState] = await Promise.all([
724
+ listWorkspaceControlEvents(db, workspaceId, controlAfter, WORKSPACE_CONTROL_REPLAY_PAGE_SIZE),
725
+ getWorkspaceInteractionRevisionState(db, { accountId, workspaceId }),
726
+ ]);
727
+ await options.reauthorize?.();
728
+ const frames: string[] = [];
729
+ if (interactionState.revision > interactionAfter) {
730
+ frames.push(
731
+ formatWorkspaceInteractionRevisionSse(
732
+ WorkspaceInteractionRevisionEvent.parse({
733
+ workspaceId,
734
+ sequence: interactionState.revision,
735
+ revision: interactionState.revision,
736
+ type: "workspace.interaction.changed",
737
+ occurredAt: (interactionState.updatedAt ?? new Date()).toISOString(),
738
+ }),
739
+ ),
740
+ );
741
+ }
742
+ frames.push(
743
+ ...controlEvents
744
+ .sort((left, right) => left.sequence - right.sequence)
745
+ .map(formatWorkspaceControlEventSse),
746
+ );
747
+ return finiteSseBatchResponse(frames, options);
748
+ }
621
749
  const upstream = new AbortController();
750
+ let stopReauthorization = () => {};
622
751
  const channel = createByteBoundedSseStream({
752
+ connectionLifetimeMs: options.connectionLifetimeMs,
623
753
  maxQueuedBytes: options.maxQueuedBytes ?? SESSION_EVENT_SSE_FRAME_MAX_BYTES,
624
754
  ...(options.stallTimeoutMs === undefined ? {} : { stallTimeoutMs: options.stallTimeoutMs }),
625
755
  onObservation: sseObservationReporter("workspace_interaction", options),
626
756
  onStop: () => {
757
+ stopReauthorization();
627
758
  signal.removeEventListener("abort", abort);
628
759
  upstream.abort();
629
760
  },
@@ -634,10 +765,23 @@ export async function sseWorkspaceLiveStream(
634
765
  };
635
766
  if (signal.aborted) abort();
636
767
  else signal.addEventListener("abort", abort, { once: true });
768
+ stopReauthorization = startSseReauthorization(options, channel.stopped, channel.close);
769
+
770
+ const upstreamOptions: WorkspaceInteractionSseOptions = {
771
+ ...options,
772
+ connectionLifetimeMs: undefined,
773
+ finiteResponseMaxBytes: undefined,
774
+ finiteResponseMediaType: undefined,
775
+ reauthorize: undefined,
776
+ reauthorizeAfterMs: undefined,
777
+ };
637
778
 
638
779
  let writeTail = Promise.resolve(true);
639
780
  const write = (frame: string): Promise<boolean> => {
640
- const pending = writeTail.then(() => channel.write(frame));
781
+ const pending = writeTail.then(async () => {
782
+ await reauthorizeSseOrClose(options, channel);
783
+ return await channel.write(frame);
784
+ });
641
785
  writeTail = pending.catch(() => false);
642
786
  return pending;
643
787
  };
@@ -665,14 +809,21 @@ export async function sseWorkspaceLiveStream(
665
809
  void (async () => {
666
810
  try {
667
811
  const [control, interaction] = await Promise.all([
668
- sseWorkspaceControlStream(db, bus, workspaceId, controlAfter, upstream.signal, options),
812
+ sseWorkspaceControlStream(
813
+ db,
814
+ bus,
815
+ workspaceId,
816
+ controlAfter,
817
+ upstream.signal,
818
+ upstreamOptions,
819
+ ),
669
820
  sseWorkspaceInteractionRevisionStream(
670
821
  db,
671
822
  accountId,
672
823
  workspaceId,
673
824
  interactionAfter,
674
825
  upstream.signal,
675
- options,
826
+ upstreamOptions,
676
827
  ),
677
828
  ]);
678
829
  const pumps = [pump(control), pump(interaction)];
@@ -694,13 +845,7 @@ export async function sseWorkspaceLiveStream(
694
845
  }
695
846
  })();
696
847
 
697
- return new Response(channel.stream, {
698
- headers: {
699
- "Content-Type": "text/event-stream; charset=utf-8",
700
- "Cache-Control": "no-cache, no-transform",
701
- Connection: "keep-alive",
702
- },
703
- });
848
+ return await sseHttpResponse(channel.stream, options);
704
849
  }
705
850
 
706
851
  /**
@@ -716,26 +861,53 @@ export async function sseWorkspaceInteractionRevisionStream(
716
861
  signal: AbortSignal,
717
862
  options: WorkspaceInteractionSseOptions = {},
718
863
  ): Promise<Response> {
864
+ if (isHttp1BrowserBatch(options)) {
865
+ const state = await getWorkspaceInteractionRevisionState(db, {
866
+ accountId,
867
+ workspaceId,
868
+ });
869
+ await options.reauthorize?.();
870
+ const frames =
871
+ state.revision > after
872
+ ? [
873
+ formatWorkspaceInteractionRevisionSse(
874
+ WorkspaceInteractionRevisionEvent.parse({
875
+ workspaceId,
876
+ sequence: state.revision,
877
+ revision: state.revision,
878
+ type: "workspace.interaction.changed",
879
+ occurredAt: (state.updatedAt ?? new Date()).toISOString(),
880
+ }),
881
+ ),
882
+ ]
883
+ : [];
884
+ return finiteSseBatchResponse(frames, options);
885
+ }
719
886
  const heartbeatIntervalMs = resolveHeartbeatInterval(options.heartbeatIntervalMs);
720
887
  const pollIntervalMs = resolveInteractionPollInterval(options.pollIntervalMs);
721
888
  let lastSent = after;
722
889
  let lastWriteAt = Date.now();
723
890
  let stopRequested = false;
891
+ let stopReauthorization = () => {};
724
892
  let detachAbortListener = () => {};
725
893
  let closeMetrics = () => {};
726
894
  const channel = createByteBoundedSseStream({
895
+ connectionLifetimeMs: options.connectionLifetimeMs,
727
896
  maxQueuedBytes: options.maxQueuedBytes ?? SESSION_EVENT_SSE_FRAME_MAX_BYTES,
728
897
  ...(options.stallTimeoutMs === undefined ? {} : { stallTimeoutMs: options.stallTimeoutMs }),
729
898
  onObservation: sseObservationReporter("workspace_interaction", options),
730
899
  onStop: () => {
731
900
  stopRequested = true;
901
+ stopReauthorization();
732
902
  closeMetrics();
733
903
  detachAbortListener();
734
904
  },
735
905
  });
736
906
  closeMetrics = observeSseConnection("workspace_interaction", after, options.observability);
907
+ stopReauthorization = startSseReauthorization(options, channel.stopped, channel.close);
737
908
 
738
909
  const write = async (frame: string): Promise<boolean> => {
910
+ await reauthorizeSseOrClose(options, channel);
739
911
  const accepted = await channel.write(frame);
740
912
  if (accepted) lastWriteAt = Date.now();
741
913
  return accepted;
@@ -746,7 +918,10 @@ export async function sseWorkspaceInteractionRevisionStream(
746
918
  if (!(await write(": connected\n\n"))) return;
747
919
  for (;;) {
748
920
  if (stopRequested || signal.aborted || channel.stopped()) return;
749
- const state = await getWorkspaceInteractionRevisionState(db, { accountId, workspaceId });
921
+ const state = await getWorkspaceInteractionRevisionState(db, {
922
+ accountId,
923
+ workspaceId,
924
+ });
750
925
  if (state.revision > lastSent) {
751
926
  const event = WorkspaceInteractionRevisionEvent.parse({
752
927
  workspaceId,
@@ -776,13 +951,7 @@ export async function sseWorkspaceInteractionRevisionStream(
776
951
  detachAbortListener = () => signal.removeEventListener("abort", abort);
777
952
  }
778
953
 
779
- return new Response(channel.stream, {
780
- headers: {
781
- "Content-Type": "text/event-stream; charset=utf-8",
782
- "Cache-Control": "no-cache, no-transform",
783
- Connection: "keep-alive",
784
- },
785
- });
954
+ return await sseHttpResponse(channel.stream, options);
786
955
  }
787
956
 
788
957
  function observeSseConnection(
@@ -843,20 +1012,179 @@ async function replayWorkspaceControlEvents(
843
1012
 
844
1013
  class SseStreamStoppedError extends Error {}
845
1014
 
1015
+ async function reauthorizeSseOrClose(
1016
+ options: SseDeliveryOptions,
1017
+ channel: ByteBoundedSseStream,
1018
+ ): Promise<void> {
1019
+ try {
1020
+ await options.reauthorize?.();
1021
+ } catch {
1022
+ // Authorization loss is an expected fail-closed terminal condition, not a
1023
+ // delivery fault. End the HTTP response cleanly so Bun and Chromium retire
1024
+ // the HTTP/1 socket even if the initiating document is being replaced.
1025
+ // No frame crosses the failed check; a still-live SDK may reconnect and is
1026
+ // fenced again at request admission, while a destroyed realm cannot leave
1027
+ // an errored response occupying the per-origin connection pool.
1028
+ channel.close();
1029
+ throw new SseStreamStoppedError();
1030
+ }
1031
+ }
1032
+
846
1033
  export type SseDeliveryOptions = {
1034
+ connectionLifetimeMs?: number | undefined;
1035
+ /** Return a known-length batch instead of a chunked response. HTTP/1 only. */
1036
+ finiteResponseMaxBytes?: number | undefined;
1037
+ /** Browser transport classification for a finite response. */
1038
+ finiteResponseMediaType?: "event-stream" | "http1-browser-batch" | undefined;
847
1039
  maxQueuedBytes?: number;
848
1040
  stallTimeoutMs?: number;
849
1041
  heartbeatIntervalMs?: number;
850
1042
  observability?: Observability | undefined;
851
1043
  onObservation?: ((observation: SseDeliveryBoundObservation) => void) | undefined;
852
- };
853
-
854
- export type SessionSseDeliveryOptions = SseDeliveryOptions & {
855
- /** Host ACL re-check, run even while the event stream is otherwise idle. */
1044
+ /** Current ACL re-check, run even while the event stream is idle. */
856
1045
  reauthorize?: (() => Promise<void>) | undefined;
857
1046
  reauthorizeAfterMs?: number | undefined;
1047
+ /** Exact selected actor emitted on the stream response for cross-tab fencing. */
1048
+ actorEpoch?: string | undefined;
858
1049
  };
859
1050
 
1051
+ export function browserSseDeliveryOptions(
1052
+ transport: string | undefined,
1053
+ ): Pick<
1054
+ SseDeliveryOptions,
1055
+ "connectionLifetimeMs" | "finiteResponseMaxBytes" | "finiteResponseMediaType"
1056
+ > {
1057
+ return transport === "http1-bounded"
1058
+ ? {
1059
+ finiteResponseMaxBytes: HTTP1_BROWSER_SSE_BATCH_MAX_BYTES,
1060
+ finiteResponseMediaType: "http1-browser-batch",
1061
+ }
1062
+ : {};
1063
+ }
1064
+
1065
+ async function sseHttpResponse(
1066
+ stream: ReadableStream<Uint8Array>,
1067
+ options: SseDeliveryOptions,
1068
+ ): Promise<Response> {
1069
+ const maxBytes = options.finiteResponseMaxBytes;
1070
+ let body: ReadableStream<Uint8Array> | ArrayBuffer = stream;
1071
+ let contentLength: number | null = null;
1072
+ if (maxBytes !== undefined) {
1073
+ if (
1074
+ !Number.isSafeInteger(maxBytes) ||
1075
+ maxBytes < SESSION_EVENT_SSE_FRAME_MAX_BYTES ||
1076
+ maxBytes > HTTP1_BROWSER_SSE_BATCH_MAX_BYTES
1077
+ ) {
1078
+ throw new RangeError(
1079
+ `finite SSE batch limit must be between ${SESSION_EVENT_SSE_FRAME_MAX_BYTES} and ${HTTP1_BROWSER_SSE_BATCH_MAX_BYTES} bytes`,
1080
+ );
1081
+ }
1082
+ body = await collectFiniteSseBatch(stream, maxBytes);
1083
+ contentLength = body.byteLength;
1084
+ }
1085
+ return new Response(body, {
1086
+ headers: {
1087
+ // A bounded HTTP/1 poll carries the same SSE-framed bytes the SDK
1088
+ // already parses, but it is an ordinary finite response at the browser
1089
+ // transport boundary. Keeping `text/event-stream` here lets Chromium
1090
+ // retain an orphaned fetch in its shared per-origin SSE pool after the
1091
+ // initiating document is replaced, starving unrelated finite reads in
1092
+ // every tab. HTTP/2 and other unbounded streams keep the standard media
1093
+ // type; only the explicit `http1-bounded` fallback uses this vendor type.
1094
+ "Content-Type":
1095
+ contentLength !== null && options.finiteResponseMediaType === "http1-browser-batch"
1096
+ ? `${HTTP1_BROWSER_SSE_BATCH_CONTENT_TYPE}; charset=utf-8`
1097
+ : "text/event-stream; charset=utf-8",
1098
+ "Cache-Control": "no-cache, no-transform",
1099
+ // A known-length response is already terminal and leaves the HTTP/1
1100
+ // socket reusable. Only a genuinely live SSE response needs an explicit
1101
+ // close when its stream ends.
1102
+ ...(contentLength === null
1103
+ ? { Connection: "close" }
1104
+ : { "Content-Length": String(contentLength) }),
1105
+ ...(options.actorEpoch ? { [MANAGED_AUTH_ACTOR_EPOCH_HEADER]: options.actorEpoch } : {}),
1106
+ },
1107
+ });
1108
+ }
1109
+
1110
+ function isHttp1BrowserBatch(options: SseDeliveryOptions): boolean {
1111
+ return (
1112
+ options.finiteResponseMediaType === "http1-browser-batch" &&
1113
+ options.finiteResponseMaxBytes !== undefined
1114
+ );
1115
+ }
1116
+
1117
+ function finiteSseBatchResponse(frames: readonly string[], options: SseDeliveryOptions): Response {
1118
+ const maxBytes = options.finiteResponseMaxBytes;
1119
+ if (
1120
+ maxBytes === undefined ||
1121
+ !Number.isSafeInteger(maxBytes) ||
1122
+ maxBytes < SESSION_EVENT_SSE_FRAME_MAX_BYTES ||
1123
+ maxBytes > HTTP1_BROWSER_SSE_BATCH_MAX_BYTES
1124
+ ) {
1125
+ throw new RangeError(
1126
+ `finite SSE batch limit must be between ${SESSION_EVENT_SSE_FRAME_MAX_BYTES} and ${HTTP1_BROWSER_SSE_BATCH_MAX_BYTES} bytes`,
1127
+ );
1128
+ }
1129
+ const encoder = new TextEncoder();
1130
+ const chunks: Uint8Array[] = [];
1131
+ let length = 0;
1132
+ for (const frame of frames) {
1133
+ const chunk = encoder.encode(frame);
1134
+ if (chunks.length > 0 && length + chunk.byteLength > maxBytes) break;
1135
+ chunks.push(chunk);
1136
+ length += chunk.byteLength;
1137
+ }
1138
+ const body = new Uint8Array(length);
1139
+ let offset = 0;
1140
+ for (const chunk of chunks) {
1141
+ body.set(chunk, offset);
1142
+ offset += chunk.byteLength;
1143
+ }
1144
+ return new Response(body, {
1145
+ headers: {
1146
+ "Content-Type": `${HTTP1_BROWSER_SSE_BATCH_CONTENT_TYPE}; charset=utf-8`,
1147
+ "Cache-Control": "no-cache, no-transform",
1148
+ "Content-Length": String(body.byteLength),
1149
+ ...(options.actorEpoch ? { [MANAGED_AUTH_ACTOR_EPOCH_HEADER]: options.actorEpoch } : {}),
1150
+ },
1151
+ });
1152
+ }
1153
+
1154
+ async function collectFiniteSseBatch(
1155
+ stream: ReadableStream<Uint8Array>,
1156
+ maxBytes: number,
1157
+ ): Promise<ArrayBuffer> {
1158
+ const reader = stream.getReader();
1159
+ const chunks: Uint8Array[] = [];
1160
+ let length = 0;
1161
+ try {
1162
+ for (;;) {
1163
+ const next = await reader.read();
1164
+ if (next.done) break;
1165
+ // Each source chunk is one complete SSE frame. End before an overflowing
1166
+ // frame so reconnect replay starts from the consumer's last whole cursor.
1167
+ if (chunks.length > 0 && length + next.value.byteLength > maxBytes) {
1168
+ await reader.cancel("finite SSE batch reached its byte limit");
1169
+ break;
1170
+ }
1171
+ chunks.push(next.value);
1172
+ length += next.value.byteLength;
1173
+ }
1174
+ } finally {
1175
+ reader.releaseLock();
1176
+ }
1177
+ const bytes = new Uint8Array(length);
1178
+ let offset = 0;
1179
+ for (const chunk of chunks) {
1180
+ bytes.set(chunk, offset);
1181
+ offset += chunk.byteLength;
1182
+ }
1183
+ return bytes.buffer;
1184
+ }
1185
+
1186
+ export type SessionSseDeliveryOptions = SseDeliveryOptions;
1187
+
860
1188
  function sseObservationReporter(
861
1189
  stream: SseStreamKind,
862
1190
  options: SseDeliveryOptions,
@@ -900,6 +1228,32 @@ function resolveInteractionPollInterval(value: number | undefined): number {
900
1228
  return interval;
901
1229
  }
902
1230
 
1231
+ function startSseReauthorization(
1232
+ options: SseDeliveryOptions,
1233
+ stopped: () => boolean,
1234
+ fail: (error: unknown) => void,
1235
+ ): () => void {
1236
+ if (!options.reauthorize) return () => {};
1237
+ const interval = options.reauthorizeAfterMs ?? 15_000;
1238
+ if (!Number.isSafeInteger(interval) || interval < 1_000 || interval > 60_000) {
1239
+ throw new RangeError("SSE reauthorization must be between 1000 and 60000ms");
1240
+ }
1241
+ let timer: ReturnType<typeof setTimeout> | null = null;
1242
+ const schedule = () => {
1243
+ if (stopped()) return;
1244
+ timer = setTimeout(() => {
1245
+ timer = null;
1246
+ void options.reauthorize!().then(schedule).catch(fail);
1247
+ }, interval);
1248
+ };
1249
+ schedule();
1250
+ return () => {
1251
+ if (!timer) return;
1252
+ clearTimeout(timer);
1253
+ timer = null;
1254
+ };
1255
+ }
1256
+
903
1257
  function formatWorkspaceInteractionRevisionSse(event: WorkspaceInteractionRevisionEvent): string {
904
1258
  return `id: ${event.sequence}\nevent: ${event.type}\ndata: ${JSON.stringify(event)}\n\n`;
905
1259
  }