@opengeni/api-router 0.30.3 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (434) hide show
  1. package/dist/access-grant-rls.d.ts +3 -0
  2. package/dist/admission-trace.d.ts +8 -0
  3. package/dist/api-websocket.d.ts +18 -0
  4. package/dist/app.d.ts +50 -2
  5. package/dist/app.js +12 -1
  6. package/dist/artifact-catalog-cursor.d.ts +27 -0
  7. package/dist/auth/managed-auth-attempt-context.d.ts +10 -0
  8. package/dist/auth/managed-auth-rate-limits.d.ts +167 -0
  9. package/dist/auth/managed-auth-session-adapter.d.ts +6 -0
  10. package/dist/auth/managed-auth.d.ts +102 -3
  11. package/dist/auth/managed-email.d.ts +29 -0
  12. package/dist/auth/managed-sign-in-notifications.d.ts +4 -0
  13. package/dist/auth/new-signups-gate.d.ts +23 -0
  14. package/dist/auth/organization-user-setup.d.ts +80 -0
  15. package/dist/auth/signup-funnel-metrics.d.ts +44 -0
  16. package/dist/browser-controller-authority.d.ts +1 -61
  17. package/dist/browser-ephemeral.d.ts +5 -0
  18. package/dist/browser-state-authority.d.ts +1 -27
  19. package/dist/chunk-6TZ5LG3O.js +123948 -0
  20. package/dist/chunk-6TZ5LG3O.js.map +1 -0
  21. package/dist/chunk-OGQDXVXY.js +18 -0
  22. package/dist/chunk-OGQDXVXY.js.map +1 -0
  23. package/dist/claude-subscription-account-conflicts.d.ts +1 -0
  24. package/dist/claude-subscription-account-usage.d.ts +39 -0
  25. package/dist/claude-subscription-oauth.d.ts +25 -0
  26. package/dist/claude-subscription-profile.d.ts +9 -0
  27. package/dist/claude-subscription-usage.d.ts +44 -0
  28. package/dist/claude-workspace-connection.d.ts +15 -0
  29. package/dist/codemode.d.ts +32 -1
  30. package/dist/company-brain-okf.d.ts +24 -0
  31. package/dist/connected-machine-computer-access.d.ts +11 -0
  32. package/dist/connection-ownership.d.ts +54 -0
  33. package/dist/controller-data-plane.d.ts +32 -0
  34. package/dist/editable-artifact-source-session.d.ts +3 -0
  35. package/dist/editable-artifact-websocket.d.ts +10 -18
  36. package/dist/editable-artifact-workspace-files.d.ts +1 -1
  37. package/dist/fatal-process-boundary.d.ts +25 -0
  38. package/dist/github-access.d.ts +31 -2
  39. package/dist/http/acting-person.d.ts +24 -0
  40. package/dist/http/api-contract-compat.d.ts +19 -0
  41. package/dist/http/api-error.d.ts +27 -1
  42. package/dist/http/cors.d.ts +17 -0
  43. package/dist/http/deprecation.d.ts +42 -0
  44. package/dist/http/interaction-control-error.d.ts +13 -0
  45. package/dist/http/keyed-admission.d.ts +12 -0
  46. package/dist/http/local-browser-boundary.d.ts +72 -0
  47. package/dist/http/metrics-listener.d.ts +22 -0
  48. package/dist/http/path-identifier.d.ts +17 -0
  49. package/dist/http/registered-route-label.d.ts +16 -0
  50. package/dist/http/request-body.d.ts +27 -0
  51. package/dist/http/request-source.d.ts +52 -0
  52. package/dist/http/sse.d.ts +17 -6
  53. package/dist/http/unmatched-route.d.ts +18 -0
  54. package/dist/http/user-content.d.ts +36 -0
  55. package/dist/http/websocket-upgrade-dispatch.d.ts +18 -0
  56. package/dist/index.d.ts +10 -3
  57. package/dist/index.js +1091 -162
  58. package/dist/index.js.map +1 -1
  59. package/dist/integration-endpoint-test.d.ts +20 -0
  60. package/dist/integrations/api-integrations.d.ts +2 -2
  61. package/dist/integrations/atlassian.d.ts +29 -3
  62. package/dist/integrations/connect-authority.d.ts +1 -0
  63. package/dist/integrations/connect-callback-return.d.ts +6 -0
  64. package/dist/integrations/fiken.d.ts +16 -2
  65. package/dist/integrations/github-app-connect.d.ts +47 -0
  66. package/dist/integrations/github-installation-proof.d.ts +3 -0
  67. package/dist/integrations/github-lens-connect.d.ts +116 -0
  68. package/dist/integrations/github-skill-source.d.ts +1 -5
  69. package/dist/integrations/google-drive.d.ts +24 -0
  70. package/dist/integrations/integration-commit-authority.d.ts +13 -0
  71. package/dist/integrations/marketplace-plugin.d.ts +2691 -0
  72. package/dist/integrations/oauth-client.d.ts +178 -18
  73. package/dist/integrations/oauth-profiles.d.ts +222 -0
  74. package/dist/integrations/oauth-return-path.d.ts +1 -0
  75. package/dist/integrations/personal-github-repositories.d.ts +84 -0
  76. package/dist/integrations/personal-github.d.ts +42 -0
  77. package/dist/integrations/pr-review-provider.d.ts +22 -0
  78. package/dist/integrations/provider-oauth.d.ts +19 -1
  79. package/dist/integrations/slack-app-home.d.ts +24 -0
  80. package/dist/integrations/slack-bot.d.ts +367 -12
  81. package/dist/integrations/slack-file-upload-flow.d.ts +62 -0
  82. package/dist/integrations/slack-install.d.ts +20 -0
  83. package/dist/integrations/slack-interactions.d.ts +107 -9
  84. package/dist/integrations/slack-mrkdwn.d.ts +41 -0
  85. package/dist/integrations/slack-routing.d.ts +120 -0
  86. package/dist/integrations/slack-session-defaults.d.ts +58 -0
  87. package/dist/integrations/slack-task-file-upload.d.ts +36 -0
  88. package/dist/integrations/social-oauth.d.ts +7 -0
  89. package/dist/interaction-agent-access.d.ts +15 -0
  90. package/dist/interaction-frame-proxy.d.ts +68 -0
  91. package/dist/interaction-metrics.d.ts +2 -0
  92. package/dist/mcp/action-catalog-types.d.ts +9 -0
  93. package/dist/mcp/action-catalog.gen.d.ts +2 -0
  94. package/dist/mcp/capability-account-readiness.d.ts +7 -0
  95. package/dist/mcp/child-read-evidence.d.ts +9 -0
  96. package/dist/mcp/command-read.d.ts +24 -0
  97. package/dist/mcp/company-profile-agent-admin.d.ts +44 -0
  98. package/dist/mcp/contract-input.d.ts +11 -0
  99. package/dist/mcp/documents.d.ts +7 -5
  100. package/dist/mcp/files.d.ts +2 -2
  101. package/dist/mcp/first-party-tool-permissions.d.ts +650 -0
  102. package/dist/mcp/knowledge-entries.d.ts +5 -0
  103. package/dist/mcp/orchestration-failure-diagnostic.d.ts +43 -0
  104. package/dist/mcp/receipts.d.ts +9 -0
  105. package/dist/mcp/request-abort.d.ts +19 -0
  106. package/dist/mcp/scheduled-task-input.d.ts +1189 -0
  107. package/dist/mcp/scheduled-task-view.d.ts +22 -4
  108. package/dist/mcp/server.d.ts +255 -3
  109. package/dist/mcp/session-event-view.d.ts +94 -0
  110. package/dist/mcp/session-view.d.ts +66 -2
  111. package/dist/mcp/session-wait.d.ts +174 -0
  112. package/dist/mcp-oauth.d.ts +52 -0
  113. package/dist/model-catalog.d.ts +11 -31
  114. package/dist/observability.d.ts +3 -1
  115. package/dist/organization-mcp.d.ts +52 -0
  116. package/dist/organization-recovery-notifications.d.ts +41 -0
  117. package/dist/routes/analytics-consent.d.ts +26 -0
  118. package/dist/routes/api-integrations.d.ts +25 -0
  119. package/dist/routes/api-keys.d.ts +17 -0
  120. package/dist/routes/artifact-catalog.d.ts +6 -0
  121. package/dist/routes/automations.d.ts +19 -0
  122. package/dist/routes/billing.d.ts +49 -0
  123. package/dist/routes/browser-sessions.d.ts +59 -2
  124. package/dist/routes/claude-subscription-accounts.d.ts +3 -0
  125. package/dist/routes/claude-subscription-oauth.d.ts +3 -0
  126. package/dist/routes/client-errors.d.ts +146 -0
  127. package/dist/routes/codex.d.ts +76 -2
  128. package/dist/routes/company-brain.d.ts +3 -0
  129. package/dist/routes/company-profile.d.ts +2 -1
  130. package/dist/routes/computer-sessions.d.ts +14 -0
  131. package/dist/routes/connect.d.ts +5 -0
  132. package/dist/routes/editable-artifacts.d.ts +2 -0
  133. package/dist/routes/external-identity-links.d.ts +6 -0
  134. package/dist/routes/feedback.d.ts +3 -0
  135. package/dist/routes/github-browser-pages.d.ts +12 -0
  136. package/dist/routes/github.d.ts +2 -0
  137. package/dist/routes/insights-response-cache.d.ts +10 -0
  138. package/dist/routes/insights-usage.d.ts +12 -0
  139. package/dist/routes/insights.d.ts +30 -1
  140. package/dist/routes/knowledge.d.ts +3 -0
  141. package/dist/routes/managed-auth-session-sets.d.ts +26 -0
  142. package/dist/routes/managed-onboarding.d.ts +29 -0
  143. package/dist/routes/managed-sign-in-methods.d.ts +6 -0
  144. package/dist/routes/model-connection-access.d.ts +3 -0
  145. package/dist/routes/organization-integration-policy.d.ts +3 -0
  146. package/dist/routes/organization-integrations.d.ts +6 -0
  147. package/dist/routes/organization-mcp-connections.d.ts +3 -0
  148. package/dist/routes/organization-model-providers.d.ts +3 -0
  149. package/dist/routes/organization-recovery.d.ts +19 -0
  150. package/dist/routes/organization-service-accounts.d.ts +3 -0
  151. package/dist/routes/organization-sessions.d.ts +26 -0
  152. package/dist/routes/personal-github-git-broker.d.ts +29 -0
  153. package/dist/routes/personal-github.d.ts +3 -0
  154. package/dist/routes/pr-review-github.d.ts +3 -0
  155. package/dist/routes/pr-review.d.ts +3 -0
  156. package/dist/routes/session-artifact-associations.d.ts +4 -0
  157. package/dist/routes/session-history-imports.d.ts +9 -0
  158. package/dist/routes/sessions.d.ts +44 -6
  159. package/dist/routes/skill-content.d.ts +4 -0
  160. package/dist/routes/skill-install-authority.d.ts +6 -0
  161. package/dist/routes/subscription-account-pools.d.ts +34 -0
  162. package/dist/routes/subscription-pool-access.d.ts +16 -0
  163. package/dist/routes/supergrok.d.ts +6 -1
  164. package/dist/routes/usage-allowances.d.ts +12 -0
  165. package/dist/routes/user-resource-authorities.d.ts +3 -0
  166. package/dist/routes/workspace-artifacts.d.ts +2 -1
  167. package/dist/routes/workspace-capture.d.ts +10 -2
  168. package/dist/routes/workspace-integrations.d.ts +35 -0
  169. package/dist/routes/workspace-learning.d.ts +3 -0
  170. package/dist/routes/workspace-model-providers.d.ts +4 -0
  171. package/dist/routes/workspaces.d.ts +29 -0
  172. package/dist/sandbox/auth-callout.d.ts +8 -0
  173. package/dist/sandbox/channel-a.d.ts +19 -1
  174. package/dist/sandbox/connection-authority.d.ts +3 -0
  175. package/dist/sandbox/enrollment.d.ts +9 -1
  176. package/dist/sandbox/machines.d.ts +5 -2
  177. package/dist/sandbox/metrics-ingestion.d.ts +28 -20
  178. package/dist/sandbox/rematerialize.d.ts +2 -0
  179. package/dist/sandbox/session-attach-variable-sets.d.ts +30 -0
  180. package/dist/sandbox/viewer.d.ts +26 -1
  181. package/dist/sandbox-file-artifacts.d.ts +50 -0
  182. package/dist/scheduled-task-deletion.d.ts +13 -0
  183. package/dist/session-create-observability.d.ts +5 -0
  184. package/dist/session-event-view-MPS2OZD3.js +292 -0
  185. package/dist/session-event-view-MPS2OZD3.js.map +1 -0
  186. package/dist/site-session-origin.d.ts +13 -0
  187. package/dist/site-uploads.d.ts +41 -0
  188. package/dist/slack-reaction-files.d.ts +1 -1
  189. package/dist/temporal-schedule-cleanup.d.ts +1 -0
  190. package/dist/temporal-schedule-sync.d.ts +26 -0
  191. package/dist/transcription/providers/xai-subscription.d.ts +1 -0
  192. package/dist/transcription/service.d.ts +2 -1
  193. package/dist/work-discovery-observability.d.ts +33 -0
  194. package/dist/workspace-artifact-content.d.ts +28 -0
  195. package/dist/workspace-artifact-provenance.d.ts +7 -0
  196. package/dist/workspace-delete-observability.d.ts +10 -0
  197. package/dist/workspace-deletion.d.ts +6 -0
  198. package/dist/workspace-state-projection.d.ts +3 -5
  199. package/dist/workspace-tool-gateway-observability.d.ts +17 -0
  200. package/dist/workspace-tool-gateway.d.ts +123 -0
  201. package/dist/workspace-webhook-dispatch.d.ts +26 -0
  202. package/package.json +23 -19
  203. package/src/access-grant-rls.ts +75 -0
  204. package/src/admission-trace.ts +14 -0
  205. package/src/api-websocket.ts +23 -0
  206. package/src/app.ts +1967 -252
  207. package/src/artifact-catalog-cursor.ts +60 -0
  208. package/src/auth/managed-auth-attempt-context.ts +70 -0
  209. package/src/auth/managed-auth-rate-limits.ts +252 -0
  210. package/src/auth/managed-auth-session-adapter.ts +222 -0
  211. package/src/auth/managed-auth.ts +536 -60
  212. package/src/auth/managed-email.ts +174 -0
  213. package/src/auth/managed-sign-in-notifications.ts +81 -0
  214. package/src/auth/new-signups-gate.ts +49 -0
  215. package/src/auth/organization-user-setup.ts +307 -0
  216. package/src/auth/signup-funnel-metrics.ts +164 -0
  217. package/src/browser-controller-authority.ts +1 -186
  218. package/src/browser-ephemeral.ts +39 -0
  219. package/src/browser-state-authority.ts +1 -236
  220. package/src/claude-subscription-account-conflicts.ts +12 -0
  221. package/src/claude-subscription-account-usage.ts +48 -0
  222. package/src/claude-subscription-oauth.ts +362 -0
  223. package/src/claude-subscription-profile.ts +73 -0
  224. package/src/claude-subscription-usage.ts +97 -0
  225. package/src/claude-workspace-connection.ts +82 -0
  226. package/src/codemode.ts +194 -30
  227. package/src/codex-realtime.ts +8 -2
  228. package/src/company-brain-okf.ts +340 -0
  229. package/src/connected-machine-computer-access.ts +33 -0
  230. package/src/connection-ownership.ts +199 -0
  231. package/src/controller-data-plane.ts +81 -0
  232. package/src/editable-artifact-native-kernel.ts +26 -15
  233. package/src/editable-artifact-office-import.ts +28 -3
  234. package/src/editable-artifact-production.ts +23 -7
  235. package/src/editable-artifact-source-session.ts +123 -0
  236. package/src/editable-artifact-websocket.ts +27 -21
  237. package/src/editable-artifact-workspace-files.ts +31 -6
  238. package/src/fatal-process-boundary.ts +246 -0
  239. package/src/gateway-realtime.ts +1 -1
  240. package/src/github-access.ts +147 -5
  241. package/src/http/acting-person.ts +71 -0
  242. package/src/http/api-contract-compat.ts +49 -0
  243. package/src/http/api-error.ts +115 -1
  244. package/src/http/auth.ts +48 -0
  245. package/src/http/cors.ts +67 -0
  246. package/src/http/deprecation.ts +118 -0
  247. package/src/http/interaction-control-error.ts +164 -0
  248. package/src/http/keyed-admission.ts +29 -0
  249. package/src/http/local-browser-boundary.ts +343 -0
  250. package/src/http/metrics-listener.ts +57 -0
  251. package/src/http/path-identifier.ts +54 -0
  252. package/src/http/registered-route-label.ts +34 -0
  253. package/src/http/request-body.ts +127 -0
  254. package/src/http/request-source.ts +178 -0
  255. package/src/http/sse.ts +486 -132
  256. package/src/http/unmatched-route.ts +60 -0
  257. package/src/http/user-content.ts +158 -0
  258. package/src/http/websocket-upgrade-dispatch.ts +29 -0
  259. package/src/index.ts +157 -52
  260. package/src/integration-endpoint-test.ts +291 -0
  261. package/src/integrations/api-integrations.ts +98 -11
  262. package/src/integrations/atlassian.ts +474 -136
  263. package/src/integrations/connect-authority.ts +2 -0
  264. package/src/integrations/connect-callback-return.ts +86 -0
  265. package/src/integrations/fiken.ts +354 -64
  266. package/src/integrations/github-app-connect.ts +435 -0
  267. package/src/integrations/github-installation-proof.ts +60 -0
  268. package/src/integrations/github-lens-connect.ts +76 -0
  269. package/src/integrations/github-skill-source.ts +2 -142
  270. package/src/integrations/google-drive.ts +717 -225
  271. package/src/integrations/integration-commit-authority.ts +69 -0
  272. package/src/integrations/marketplace-plugin.ts +48 -0
  273. package/src/integrations/oauth-client.ts +1356 -459
  274. package/src/integrations/oauth-profiles.ts +537 -0
  275. package/src/integrations/oauth-return-path.ts +18 -0
  276. package/src/integrations/personal-github-repositories.ts +674 -0
  277. package/src/integrations/personal-github.ts +825 -0
  278. package/src/integrations/pr-review-provider.ts +246 -0
  279. package/src/integrations/provider-oauth.ts +250 -22
  280. package/src/integrations/slack-app-home.ts +300 -0
  281. package/src/integrations/slack-bot.ts +963 -239
  282. package/src/integrations/slack-file-upload-flow.ts +147 -0
  283. package/src/integrations/slack-install.ts +83 -0
  284. package/src/integrations/slack-interactions.ts +3167 -474
  285. package/src/integrations/slack-mrkdwn.ts +193 -0
  286. package/src/integrations/slack-routing.ts +337 -0
  287. package/src/integrations/slack-session-defaults.ts +224 -0
  288. package/src/integrations/slack-task-file-upload.ts +323 -0
  289. package/src/integrations/social-oauth.ts +213 -15
  290. package/src/interaction-agent-access.ts +48 -0
  291. package/src/interaction-frame-proxy.ts +409 -0
  292. package/src/interaction-metrics.ts +30 -0
  293. package/src/mcp/action-catalog-types.ts +9 -0
  294. package/src/mcp/action-catalog.gen.ts +6696 -0
  295. package/src/mcp/capability-account-readiness.ts +17 -0
  296. package/src/mcp/child-read-evidence.ts +19 -0
  297. package/src/mcp/command-read.ts +109 -0
  298. package/src/mcp/company-profile-agent-admin.ts +198 -0
  299. package/src/mcp/contract-input.ts +141 -0
  300. package/src/mcp/documents.ts +65 -377
  301. package/src/mcp/editable-artifacts.ts +12 -6
  302. package/src/mcp/files.ts +80 -43
  303. package/src/mcp/first-party-tool-permissions.ts +305 -0
  304. package/src/mcp/knowledge-entries.ts +322 -0
  305. package/src/mcp/orchestration-failure-diagnostic.ts +43 -0
  306. package/src/mcp/receipts.ts +34 -0
  307. package/src/mcp/request-abort.ts +44 -0
  308. package/src/mcp/scheduled-task-input.ts +76 -0
  309. package/src/mcp/scheduled-task-view.ts +44 -3
  310. package/src/mcp/server.ts +3030 -1267
  311. package/src/mcp/session-event-view.ts +383 -0
  312. package/src/mcp/session-view.ts +84 -3
  313. package/src/mcp/session-wait.ts +665 -0
  314. package/src/mcp-oauth.ts +1123 -0
  315. package/src/model-catalog.ts +91 -333
  316. package/src/observability.ts +15 -1
  317. package/src/organization-mcp.ts +397 -0
  318. package/src/organization-recovery-notifications.ts +103 -0
  319. package/src/routes/analytics-consent.ts +123 -0
  320. package/src/routes/api-integrations.ts +364 -74
  321. package/src/routes/api-keys.ts +336 -10
  322. package/src/routes/artifact-catalog.ts +279 -0
  323. package/src/routes/automations.ts +768 -0
  324. package/src/routes/billing.ts +461 -46
  325. package/src/routes/browser-sessions.ts +780 -112
  326. package/src/routes/canonical-human-identities.ts +90 -14
  327. package/src/routes/capabilities.ts +107 -6
  328. package/src/routes/channels.ts +20 -3
  329. package/src/routes/claude-subscription-accounts.ts +334 -0
  330. package/src/routes/claude-subscription-oauth.ts +142 -0
  331. package/src/routes/client-errors.ts +362 -0
  332. package/src/routes/codex.ts +833 -116
  333. package/src/routes/company-brain.ts +322 -0
  334. package/src/routes/company-profile.ts +65 -1
  335. package/src/routes/computer-sessions.ts +557 -106
  336. package/src/routes/connect.ts +1938 -0
  337. package/src/routes/connections.ts +1039 -349
  338. package/src/routes/documents.ts +488 -287
  339. package/src/routes/editable-artifacts.ts +79 -9
  340. package/src/routes/enrollments.ts +135 -39
  341. package/src/routes/environments.ts +154 -58
  342. package/src/routes/external-identity-links.ts +201 -0
  343. package/src/routes/feedback.ts +113 -0
  344. package/src/routes/files.ts +267 -33
  345. package/src/routes/github-browser-pages.ts +162 -0
  346. package/src/routes/github.ts +446 -144
  347. package/src/routes/insights-response-cache.ts +83 -0
  348. package/src/routes/insights-usage.ts +345 -0
  349. package/src/routes/insights.ts +153 -19
  350. package/src/routes/install.ts +133 -38
  351. package/src/routes/integration-facets.ts +107 -33
  352. package/src/routes/interaction-resources.ts +30 -3
  353. package/src/routes/knowledge.ts +488 -0
  354. package/src/routes/machines.ts +313 -15
  355. package/src/routes/managed-auth-session-sets.ts +1218 -0
  356. package/src/routes/managed-onboarding.ts +337 -0
  357. package/src/routes/managed-sign-in-methods.ts +433 -0
  358. package/src/routes/memory-slack-publications.ts +3 -2
  359. package/src/routes/model-connection-access.ts +208 -0
  360. package/src/routes/organization-integration-policy.ts +116 -0
  361. package/src/routes/organization-integrations.ts +326 -0
  362. package/src/routes/organization-mcp-connections.ts +178 -0
  363. package/src/routes/organization-memberships.ts +1019 -27
  364. package/src/routes/organization-model-providers.ts +293 -0
  365. package/src/routes/organization-recovery.ts +325 -0
  366. package/src/routes/organization-service-accounts.ts +128 -0
  367. package/src/routes/organization-sessions.ts +350 -0
  368. package/src/routes/personal-github-git-broker.ts +785 -0
  369. package/src/routes/personal-github.ts +370 -0
  370. package/src/routes/plugins.ts +251 -22
  371. package/src/routes/pr-review-github.ts +881 -0
  372. package/src/routes/pr-review.ts +622 -0
  373. package/src/routes/preference-registry.ts +12 -93
  374. package/src/routes/rigs.ts +167 -50
  375. package/src/routes/scheduled-tasks.ts +346 -41
  376. package/src/routes/session-artifact-associations.ts +87 -0
  377. package/src/routes/session-history-imports.ts +280 -0
  378. package/src/routes/sessions.ts +2306 -264
  379. package/src/routes/skill-content.ts +297 -0
  380. package/src/routes/skill-install-authority.ts +64 -0
  381. package/src/routes/skills.ts +62 -9
  382. package/src/routes/social.ts +46 -18
  383. package/src/routes/subscription-account-pools.ts +436 -0
  384. package/src/routes/subscription-pool-access.ts +126 -0
  385. package/src/routes/supergrok.ts +219 -303
  386. package/src/routes/transcription-recordings.ts +38 -3
  387. package/src/routes/transcriptions.ts +4 -0
  388. package/src/routes/usage-allowances.ts +378 -0
  389. package/src/routes/user-resource-authorities.ts +138 -0
  390. package/src/routes/video-generation.ts +14 -10
  391. package/src/routes/workspace-artifacts.ts +275 -69
  392. package/src/routes/workspace-capture.ts +21 -0
  393. package/src/routes/workspace-integrations.ts +576 -0
  394. package/src/routes/workspace-learning.ts +159 -0
  395. package/src/routes/workspace-model-providers.ts +148 -0
  396. package/src/routes/workspace-state.ts +38 -25
  397. package/src/routes/workspaces.ts +1004 -127
  398. package/src/sandbox/auth-callout.ts +96 -19
  399. package/src/sandbox/channel-a.ts +323 -24
  400. package/src/sandbox/connection-authority.ts +3 -0
  401. package/src/sandbox/enrollment.ts +81 -6
  402. package/src/sandbox/machines.ts +218 -76
  403. package/src/sandbox/metrics-ingestion.ts +411 -62
  404. package/src/sandbox/rematerialize.ts +121 -19
  405. package/src/sandbox/session-attach-variable-sets.ts +131 -0
  406. package/src/sandbox/viewer.ts +280 -25
  407. package/src/sandbox-file-artifacts.ts +413 -0
  408. package/src/scheduled-task-deletion.ts +138 -0
  409. package/src/session-create-observability.ts +46 -0
  410. package/src/session-realtime-context.ts +6 -3
  411. package/src/site-session-origin.ts +48 -0
  412. package/src/site-uploads.ts +224 -0
  413. package/src/slack-reaction-files.ts +16 -5
  414. package/src/temporal-schedule-cleanup.ts +13 -0
  415. package/src/temporal-schedule-sync.ts +74 -0
  416. package/src/transcription/providers/codex-subscription.ts +2 -0
  417. package/src/transcription/providers/openai.ts +1 -0
  418. package/src/transcription/providers/xai-subscription.ts +49 -3
  419. package/src/transcription/segmenter.ts +8 -2
  420. package/src/transcription/service.ts +43 -6
  421. package/src/work-discovery-observability.ts +121 -0
  422. package/src/workspace-artifact-content.ts +144 -0
  423. package/src/workspace-artifact-provenance.ts +104 -0
  424. package/src/workspace-delete-observability.ts +75 -0
  425. package/src/workspace-deletion.ts +73 -0
  426. package/src/workspace-state-projection.ts +18 -148
  427. package/src/workspace-tool-gateway-observability.ts +100 -0
  428. package/src/workspace-tool-gateway.ts +870 -0
  429. package/src/workspace-webhook-dispatch.ts +190 -0
  430. package/src/xai-subscription-auth.ts +9 -2
  431. package/dist/chunk-WCXHI3FF.js +0 -49920
  432. package/dist/chunk-WCXHI3FF.js.map +0 -1
  433. package/dist/routes/packs.d.ts +0 -3
  434. package/src/routes/packs.ts +0 -729
@@ -1,8 +1,12 @@
1
+ import { readSessionAttachmentFiles } from "@opengeni/core";
2
+ import { ZodError } from "zod";
1
3
  import {
2
4
  CompleteFileUploadResponse,
3
5
  CreateFileUploadRequest,
4
6
  CreateFileUploadResponse,
5
7
  FileAsset,
8
+ FileListRequest,
9
+ FileListResponse,
6
10
  FileDownloadUrlResponse,
7
11
  RETAINED_OUTPUT_DEFAULT_PAGE_BYTES,
8
12
  RETAINED_OUTPUT_MAX_PAGE_BYTES,
@@ -11,20 +15,24 @@ import {
11
15
  retainedGeneratedImageReferenceFromFile,
12
16
  retainedGeneratedVideoReferenceFromFile,
13
17
  retainedScreenshotReferenceFromFile,
18
+ retainedSessionScreenshotKindFromObjectKey,
14
19
  VideoArtifactPlaybackSource,
15
20
  resolveRetainedOutputRange,
16
21
  type RetainedArtifactMetadata,
17
22
  type RetainedOutputUnavailableReason,
18
23
  } from "@opengeni/contracts";
19
24
  import {
25
+ withSessionRlsActorContext,
20
26
  claimFileUploadCleanup,
21
27
  completeFileUploadCleanup,
22
28
  completeFileUpload,
23
29
  createFileUpload,
24
30
  getFileUpload,
25
31
  getGeneratedImageArtifact,
32
+ recordAuditEvent,
26
33
  getGeneratedVideoArtifact,
27
34
  getFilesForSubject,
35
+ listFilesForSubject,
28
36
  getRetainedFileArtifact,
29
37
  getRetainedScreenshotArtifact,
30
38
  requireFileForSubject,
@@ -36,27 +44,110 @@ import {
36
44
  import type { Context, Hono } from "hono";
37
45
  import { HTTPException } from "hono/http-exception";
38
46
  import {
47
+ fileOwnerContextForAccess,
48
+ requireAccessGrantAuthorization,
49
+ grantHasAgentAttemptAuthority,
39
50
  requireAccessGrant,
40
51
  requireLiveAgentAttemptAuthorization,
52
+ requireSessionAuthorization,
41
53
  SessionAuthorizationDeniedError,
54
+ SessionAuthorizationUnavailableError,
42
55
  } from "@opengeni/core";
43
56
  import { recordWorkspaceUsage, requireLimit } from "@opengeni/core";
44
57
  import type { ApiRouteDeps } from "@opengeni/core";
58
+ import { retryWhileMissing } from "@opengeni/storage";
45
59
  import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
46
60
  import { buildFilesMcpServer } from "../mcp/files";
61
+ import { userContentResponseHeaders, userContentSignedGetUrlOptions } from "../http/user-content";
62
+ import { mcpOAuthAuthenticateHeader, resolveMcpOAuthRouteAccess } from "../mcp-oauth";
63
+ import { withAccessGrantSessionRlsContext } from "../access-grant-rls";
64
+ import {
65
+ buildWorkspaceToolGatewayMcpServer,
66
+ prepareMcpOAuthWorkspaceToolGateway,
67
+ } from "../workspace-tool-gateway";
68
+ import { parseRequestJson } from "../http/request-body";
47
69
 
48
70
  export function registerFileRoutes(app: Hono, deps: ApiRouteDeps): void {
49
71
  const { db, objectStorage } = deps;
50
72
 
73
+ const fileRequestAuthority = new WeakMap<Request, { owner: string | null; agent: boolean }>();
74
+ // This scope covers metadata, original bytes, range reads and finalization.
75
+ // A service/agent subject string alone cannot unlock a personal attachment.
76
+ for (const path of [
77
+ "/v1/workspaces/:workspaceId/files",
78
+ "/v1/workspaces/:workspaceId/files/*",
79
+ "/v1/workspaces/:workspaceId/artifacts/*",
80
+ "/v1/workspaces/:workspaceId/sessions/:sessionId/artifacts/*",
81
+ ]) {
82
+ app.use(path, async (c, next) => {
83
+ const permission = c.req.path.includes("/files/uploads") ? "files:upload" : "files:read";
84
+ const access = await requireAccessGrantAuthorization(
85
+ c,
86
+ deps,
87
+ c.req.param("workspaceId") ?? "",
88
+ permission,
89
+ );
90
+ const actor = await fileOwnerContextForAccess(deps, access, permission);
91
+ fileRequestAuthority.set(c.req.raw, {
92
+ owner: actor.privateFileOwnerSubjectId ?? null,
93
+ agent: access.grant.principalKind === "agent_attempt",
94
+ });
95
+ return withSessionRlsActorContext(actor, next);
96
+ });
97
+ }
98
+
51
99
  app.all("/v1/workspaces/:workspaceId/mcp/files", async (c) => {
52
100
  const workspaceId = c.req.param("workspaceId");
53
- const grant = await requireAccessGrant(c, deps, workspaceId, "files:read");
54
- const transport = new WebStandardStreamableHTTPServerTransport({
55
- enableJsonResponse: true,
101
+ let oauthAccess: Awaited<ReturnType<typeof resolveMcpOAuthRouteAccess>>;
102
+ try {
103
+ oauthAccess = await resolveMcpOAuthRouteAccess(deps, c.req.raw, workspaceId);
104
+ } catch (error) {
105
+ if (error instanceof HTTPException && error.status === 401) {
106
+ c.header("www-authenticate", mcpOAuthAuthenticateHeader(deps, new URL(c.req.url).pathname));
107
+ }
108
+ throw error;
109
+ }
110
+ if (oauthAccess) {
111
+ return await withAccessGrantSessionRlsContext(deps, oauthAccess.grant, async () => {
112
+ const prepared = await prepareMcpOAuthWorkspaceToolGateway(
113
+ deps,
114
+ oauthAccess.grant,
115
+ oauthAccess.allowedToolIdentities,
116
+ );
117
+ const transport = new WebStandardStreamableHTTPServerTransport({
118
+ enableJsonResponse: true,
119
+ });
120
+ const server = buildWorkspaceToolGatewayMcpServer(
121
+ prepared,
122
+ oauthAccess.grant,
123
+ deps.observability,
124
+ );
125
+ try {
126
+ await server.connect(transport);
127
+ return await transport.handleRequest(c.req.raw);
128
+ } finally {
129
+ await Promise.allSettled([server.close(), prepared.close()]);
130
+ }
131
+ });
132
+ }
133
+ let access: Awaited<ReturnType<typeof requireAccessGrantAuthorization>>;
134
+ try {
135
+ access = await requireAccessGrantAuthorization(c, deps, workspaceId, "files:read");
136
+ } catch (error) {
137
+ if (deps.settings.mcpOauthEnabled && error instanceof HTTPException && error.status === 401) {
138
+ c.header("www-authenticate", mcpOAuthAuthenticateHeader(deps, new URL(c.req.url).pathname));
139
+ }
140
+ throw error;
141
+ }
142
+ const { grant } = access;
143
+ return await withAccessGrantSessionRlsContext(deps, grant, async () => {
144
+ const transport = new WebStandardStreamableHTTPServerTransport({
145
+ enableJsonResponse: true,
146
+ });
147
+ const server = buildFilesMcpServer(deps, access);
148
+ await server.connect(transport);
149
+ return await transport.handleRequest(c.req.raw);
56
150
  });
57
- const server = buildFilesMcpServer(deps, grant);
58
- await server.connect(transport);
59
- return await transport.handleRequest(c.req.raw);
60
151
  });
61
152
 
62
153
  app.post("/v1/workspaces/:workspaceId/files/uploads", async (c) => {
@@ -67,7 +158,13 @@ export function registerFileRoutes(app: Hono, deps: ApiRouteDeps): void {
67
158
  message: "object storage is not configured",
68
159
  });
69
160
  }
70
- const payload = CreateFileUploadRequest.parse(await c.req.json());
161
+ const payload = await parseRequestJson(c, CreateFileUploadRequest);
162
+ const privateOwner = fileRequestAuthority.get(c.req.raw)?.owner ?? null;
163
+ const personal =
164
+ payload.scope === "personal" ||
165
+ (fileRequestAuthority.get(c.req.raw)?.agent && privateOwner !== null);
166
+ if (personal && !privateOwner)
167
+ throw new HTTPException(403, { message: "Personal uploads require the authenticated owner" });
71
168
  await requireLimit(deps, {
72
169
  accountId: grant.accountId,
73
170
  workspaceId,
@@ -91,6 +188,7 @@ export function registerFileRoutes(app: Hono, deps: ApiRouteDeps): void {
91
188
  accountId: grant.accountId,
92
189
  workspaceId,
93
190
  fileId,
191
+ privateOwnerSubjectId: personal ? privateOwner : null,
94
192
  filename: payload.filename,
95
193
  safeFilename,
96
194
  contentType: payload.contentType,
@@ -100,6 +198,23 @@ export function registerFileRoutes(app: Hono, deps: ApiRouteDeps): void {
100
198
  objectKey,
101
199
  expiresAt: signed.expiresAt,
102
200
  });
201
+ // Every principal-facing signed URL issuance is a
202
+ // metadata-only audit fact. Awaited before the URL leaves the platform:
203
+ // no recorded fact, no bearer capability. Never the URL or object key.
204
+ await recordAuditEvent(db, {
205
+ accountId: grant.accountId,
206
+ workspaceId,
207
+ subjectId: grant.subjectId,
208
+ action: "file.signed_upload.issued",
209
+ targetType: "workspace_file",
210
+ targetId: fileId,
211
+ metadata: {
212
+ fileId,
213
+ contentType: payload.contentType,
214
+ sizeBytes: payload.sizeBytes,
215
+ expiresAt: signed.expiresAt.toISOString(),
216
+ },
217
+ });
103
218
  return c.json(
104
219
  CreateFileUploadResponse.parse({
105
220
  fileId: upload.file.id,
@@ -231,11 +346,13 @@ export function registerFileRoutes(app: Hono, deps: ApiRouteDeps): void {
231
346
  const file = await rejectAndCleanObject(409, "file upload has expired", "expired");
232
347
  return c.json(CompleteFileUploadResponse.parse({ file }));
233
348
  }
234
- const head = await objectStorage.headFile(upload.file).catch((error) => {
235
- throw new HTTPException(409, {
236
- message: `uploaded object is not available: ${error instanceof Error ? error.message : String(error)}`,
237
- });
349
+ const head = await retryWhileMissing(async () => {
350
+ if (!(await objectStorage.fileExists(upload.file))) return null;
351
+ return await objectStorage.headFile(upload.file);
238
352
  });
353
+ if (!head) {
354
+ throw new HTTPException(409, { message: "uploaded object is not available" });
355
+ }
239
356
  if (Number(head.ContentLength ?? -1) !== upload.file.sizeBytes) {
240
357
  const file = await rejectAndCleanObject(
241
358
  422,
@@ -268,15 +385,53 @@ export function registerFileRoutes(app: Hono, deps: ApiRouteDeps): void {
268
385
  return c.json(CompleteFileUploadResponse.parse({ file }));
269
386
  });
270
387
 
271
- app.get("/v1/workspaces/:workspaceId/files/:fileId", async (c) => {
388
+ app.get("/v1/workspaces/:workspaceId/files", async (c) => {
272
389
  const workspaceId = c.req.param("workspaceId");
273
390
  const grant = await requireAccessGrant(c, deps, workspaceId, "files:read");
274
- const file = await requireFileForSubject(db, {
391
+ const parsed = FileListRequest.safeParse({
392
+ ...c.req.query(),
393
+ ...(c.req.query("limit") !== undefined ? { limit: Number(c.req.query("limit")) } : {}),
394
+ });
395
+ if (!parsed.success) throw new HTTPException(422, { message: "Invalid file list request" });
396
+ c.header("cache-control", "private, no-store");
397
+ return c.json(
398
+ FileListResponse.parse(
399
+ await listFilesForSubject(db, {
400
+ accountId: grant.accountId,
401
+ workspaceId,
402
+ subjectId: grant.subjectId,
403
+ ...parsed.data,
404
+ }).catch((error: unknown) => {
405
+ if (error instanceof ZodError)
406
+ throw new HTTPException(422, { message: "Invalid file cursor" });
407
+ throw error;
408
+ }),
409
+ ),
410
+ );
411
+ });
412
+
413
+ const readRequestedFile = async (
414
+ c: Context,
415
+ grant: Awaited<ReturnType<typeof requireAccessGrant>>,
416
+ ) => {
417
+ const fileId = c.req.param("fileId") ?? "";
418
+ const sessionId = c.req.query("sessionId");
419
+ if (sessionId) {
420
+ if (!/^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/iu.test(sessionId)) return null;
421
+ return (await readSessionAttachmentFiles(deps, grant, sessionId, [fileId]))[0] ?? null;
422
+ }
423
+ return requireFileForSubject(db, {
275
424
  accountId: grant.accountId,
276
- workspaceId,
425
+ workspaceId: grant.workspaceId,
277
426
  subjectId: grant.subjectId,
278
- fileId: c.req.param("fileId"),
427
+ fileId,
279
428
  }).catch(() => null);
429
+ };
430
+
431
+ app.get("/v1/workspaces/:workspaceId/files/:fileId", async (c) => {
432
+ const workspaceId = c.req.param("workspaceId");
433
+ const grant = await requireAccessGrant(c, deps, workspaceId, "files:read");
434
+ const file = await readRequestedFile(c, grant).catch(() => null);
280
435
  if (!file) {
281
436
  throw new HTTPException(404, { message: "file not found" });
282
437
  }
@@ -324,7 +479,7 @@ export function registerFileRoutes(app: Hono, deps: ApiRouteDeps): void {
324
479
 
325
480
  app.post("/v1/workspaces/:workspaceId/artifacts/:artifactId/playback-source", async (c) => {
326
481
  const workspaceId = c.req.param("workspaceId");
327
- await requireAccessGrant(c, deps, workspaceId, "files:read");
482
+ const grant = await requireAccessGrant(c, deps, workspaceId, "files:read");
328
483
  if (!objectStorage) {
329
484
  throw new HTTPException(503, { message: "object storage is not configured" });
330
485
  }
@@ -337,10 +492,29 @@ export function registerFileRoutes(app: Hono, deps: ApiRouteDeps): void {
337
492
  throw new HTTPException(409, { message: `generated video is ${retained.file.status}` });
338
493
  }
339
494
  const file = generatedVideoFileAsset(retained.file);
340
- if (!(await objectStorage.fileExists(file))) {
495
+ const videoPresent = await retryWhileMissing(async () =>
496
+ (await objectStorage.fileExists(file)) ? true : null,
497
+ );
498
+ if (!videoPresent) {
341
499
  throw new HTTPException(410, { message: "generated video bytes are unavailable" });
342
500
  }
343
- const signed = await objectStorage.createGetUrl({ key: file.objectKey });
501
+ const signed = await objectStorage.createGetUrl({
502
+ key: file.objectKey,
503
+ ...userContentSignedGetUrlOptions(file.contentType, file.filename),
504
+ });
505
+ await recordAuditEvent(db, {
506
+ accountId: grant.accountId,
507
+ workspaceId,
508
+ subjectId: grant.subjectId,
509
+ action: "file.signed_url.issued",
510
+ targetType: "retained_artifact",
511
+ targetId: artifactId,
512
+ metadata: {
513
+ artifactId,
514
+ kind: "video_playback",
515
+ expiresAt: signed.expiresAt.toISOString(),
516
+ },
517
+ });
344
518
  return c.json(
345
519
  VideoArtifactPlaybackSource.parse({
346
520
  schemaVersion: 1,
@@ -355,9 +529,40 @@ export function registerFileRoutes(app: Hono, deps: ApiRouteDeps): void {
355
529
  );
356
530
  });
357
531
 
532
+ // These two retained-screenshot routes are registered before the session
533
+ // route module, so its `/sessions/:sessionId/*` authorization middleware
534
+ // does not cover them: an agent attempt must pass the same core seam here
535
+ // (private sessions, Slack ownership, and the 0426 agent-access scope) so a
536
+ // known artifact id cannot read into a tree the attempt may not reach.
537
+ const requireAgentSessionAccess = async (
538
+ c: Context,
539
+ grant: Awaited<ReturnType<typeof requireAccessGrant>>,
540
+ ): Promise<void> => {
541
+ if (!grantHasAgentAttemptAuthority(grant)) return;
542
+ try {
543
+ await requireSessionAuthorization(deps, grant, {
544
+ sessionId: c.req.param("sessionId") ?? "",
545
+ operation: "session.read",
546
+ surface: "http",
547
+ });
548
+ } catch (error) {
549
+ if (error instanceof SessionAuthorizationDeniedError) {
550
+ throw new HTTPException(404, { message: "session not found", cause: error });
551
+ }
552
+ if (error instanceof SessionAuthorizationUnavailableError) {
553
+ throw new HTTPException(503, {
554
+ message: "session authorization is unavailable",
555
+ cause: error,
556
+ });
557
+ }
558
+ throw error;
559
+ }
560
+ };
561
+
358
562
  app.get("/v1/workspaces/:workspaceId/sessions/:sessionId/artifacts/:artifactId", async (c) => {
359
563
  const workspaceId = c.req.param("workspaceId");
360
- await requireAccessGrant(c, deps, workspaceId, "files:read");
564
+ const grant = await requireAccessGrant(c, deps, workspaceId, "files:read");
565
+ await requireAgentSessionAccess(c, grant);
361
566
  const artifactId = retainedArtifactId(c.req.param("artifactId"));
362
567
  const artifact = await getRetainedScreenshotArtifact(
363
568
  db,
@@ -375,7 +580,8 @@ export function registerFileRoutes(app: Hono, deps: ApiRouteDeps): void {
375
580
  "/v1/workspaces/:workspaceId/sessions/:sessionId/artifacts/:artifactId/content",
376
581
  async (c) => {
377
582
  const workspaceId = c.req.param("workspaceId");
378
- await requireAccessGrant(c, deps, workspaceId, "files:read");
583
+ const grant = await requireAccessGrant(c, deps, workspaceId, "files:read");
584
+ await requireAgentSessionAccess(c, grant);
379
585
  const artifactId = retainedArtifactId(c.req.param("artifactId"));
380
586
  const artifact = await getRetainedScreenshotArtifact(
381
587
  db,
@@ -404,19 +610,36 @@ export function registerFileRoutes(app: Hono, deps: ApiRouteDeps): void {
404
610
  message: "object storage is not configured",
405
611
  });
406
612
  }
407
- const file = await requireFileForSubject(db, {
408
- accountId: grant.accountId,
409
- workspaceId,
410
- subjectId: grant.subjectId,
411
- fileId: c.req.param("fileId"),
412
- }).catch(() => null);
613
+ const file = await readRequestedFile(c, grant).catch(() => null);
413
614
  if (!file) {
414
615
  throw new HTTPException(404, { message: "file not found" });
415
616
  }
416
617
  if (file.status !== "ready") {
417
618
  throw new HTTPException(409, { message: `file is ${file.status}` });
418
619
  }
419
- const signed = await objectStorage.createGetUrl({ key: file.objectKey });
620
+ const present = await retryWhileMissing(async () =>
621
+ (await objectStorage.fileExists(file)) ? true : null,
622
+ );
623
+ if (!present) {
624
+ throw new HTTPException(410, { message: "file bytes are unavailable" });
625
+ }
626
+ const signed = await objectStorage.createGetUrl({
627
+ key: file.objectKey,
628
+ ...userContentSignedGetUrlOptions(file.contentType, file.filename),
629
+ });
630
+ await recordAuditEvent(db, {
631
+ accountId: grant.accountId,
632
+ workspaceId,
633
+ subjectId: grant.subjectId,
634
+ action: "file.signed_url.issued",
635
+ targetType: "workspace_file",
636
+ targetId: file.id,
637
+ metadata: {
638
+ fileId: file.id,
639
+ kind: "download",
640
+ expiresAt: signed.expiresAt.toISOString(),
641
+ },
642
+ });
420
643
  return c.json(
421
644
  FileDownloadUrlResponse.parse({
422
645
  url: signed.url,
@@ -471,25 +694,33 @@ async function serveRetainedArtifactContent(
471
694
  );
472
695
  }
473
696
 
697
+ // Stored bytes and their content type are user- or agent-chosen; this origin
698
+ // also serves the console, so the response is a sandboxed, non-embeddable
699
+ // document and active markup downloads instead of rendering.
474
700
  const headers = {
475
701
  "Accept-Ranges": range.acceptRanges,
476
702
  "Cache-Control": "private, no-store",
477
703
  "Content-Length": String(range.length),
478
704
  "Content-Type": metadata.contentType,
479
- "X-Content-Type-Options": "nosniff",
705
+ ...userContentResponseHeaders(metadata.contentType, file.filename),
480
706
  ...(range.contentRange ? { "Content-Range": range.contentRange } : {}),
481
707
  };
482
708
  if (range.kind === "empty") {
483
- if (!(await objectStorage.fileExists(file))) {
709
+ const present = await retryWhileMissing(async () =>
710
+ (await objectStorage.fileExists(file)) ? true : null,
711
+ );
712
+ if (!present) {
484
713
  return c.json(retainedArtifactUnavailable(artifactId, "missing_storage"), 410);
485
714
  }
486
715
  return c.body(null, 200, headers);
487
716
  }
488
717
 
489
- const bytes = await objectStorage.getFileRange(file, {
490
- start: range.start,
491
- end: range.end,
492
- });
718
+ const bytes = await retryWhileMissing(async () =>
719
+ objectStorage.getFileRange(file, {
720
+ start: range.start,
721
+ end: range.end,
722
+ }),
723
+ );
493
724
  if (!bytes) {
494
725
  return c.json(retainedArtifactUnavailable(artifactId, "missing_storage"), 410);
495
726
  }
@@ -669,12 +900,15 @@ function retainedScreenshotMetadata(
669
900
  artifact.sessionId &&
670
901
  artifact.retentionExpiresAt.getTime() > Date.now()
671
902
  ) {
903
+ const kind = retainedSessionScreenshotKindFromObjectKey(artifact.file.objectKey);
904
+ if (!kind) return retainedArtifactUnavailable(artifact.artifactId, "invalid_content");
672
905
  const reference = retainedScreenshotReferenceFromFile({
673
906
  ...artifact.file,
674
907
  sessionId: artifact.sessionId,
675
908
  width: artifact.width,
676
909
  height: artifact.height,
677
910
  expiresAt: artifact.retentionExpiresAt.toISOString(),
911
+ kind,
678
912
  });
679
913
  if (reference) return reference;
680
914
  }
@@ -0,0 +1,162 @@
1
+ import type { GitHubInstallationBindingCandidate } from "@opengeni/contracts";
2
+
3
+ // GitHub redirects can land on an API-only host. Keep these pages self-contained
4
+ // while using the same dark palette and typography as @opengeni/react.
5
+ const pageStyle = `
6
+ :root{color-scheme:dark;--bg:oklch(.155 .012 260);--surface:oklch(.19 .014 260);--raised:oklch(.225 .015 260);--border:oklch(.3 .014 260);--border-strong:oklch(.42 .016 260);--fg:oklch(.955 .005 260);--muted:oklch(.73 .012 260);--blue:oklch(.54 .18 255);--blue-light:oklch(.72 .15 255)}
7
+ *{box-sizing:border-box}
8
+ html{min-height:100%;background:var(--bg)}
9
+ body{margin:0;min-height:100vh;min-height:100dvh;color:var(--fg);background:var(--bg);font-family:"Inter Variable",Inter,ui-sans-serif,system-ui,sans-serif;font-feature-settings:"cv11","ss01","ss03";-webkit-font-smoothing:antialiased}
10
+ .shell{width:min(100% - 48px,700px);margin:0 auto}
11
+ header{height:84px;display:flex;align-items:center;justify-content:space-between;gap:16px;border-bottom:1px solid var(--border)}
12
+ .brand{font-size:16px;font-weight:670;letter-spacing:-.045em;color:var(--fg);text-decoration:none}
13
+ .context{font-size:12px;color:var(--muted)}
14
+ main{padding:clamp(48px,10vh,100px) 0 96px}
15
+ h1{margin:0 0 14px;font-size:clamp(27px,4vw,36px);font-weight:640;letter-spacing:-.05em;line-height:1.17}
16
+ p{margin:0;color:var(--muted);font-size:14px;line-height:1.65;max-width:59ch}
17
+ .intro{margin-bottom:32px}
18
+ .options{display:grid;gap:10px;margin:0 0 24px;padding:0;border:0;max-height:min(390px,52vh);overflow:auto}
19
+ .option{display:flex;gap:14px;align-items:center;min-height:70px;padding:14px 16px;border:1px solid var(--border);border-radius:10px;background:var(--surface);cursor:pointer;transition:background .15s,border-color .15s}
20
+ .option:hover{background:var(--raised)}
21
+ .option:has(input:checked){border-color:var(--blue-light);background:var(--raised)}
22
+ .option:has(input:focus-visible){outline:2px solid var(--blue-light);outline-offset:3px}
23
+ .option input{margin:0;flex:none;width:17px;height:17px;accent-color:var(--blue-light)}
24
+ .option-text{min-width:0;display:grid;gap:3px}
25
+ .option strong{overflow-wrap:anywhere;font-size:14px;font-weight:600}
26
+ .option small{font-size:12px;color:var(--muted)}
27
+ .actions{display:flex;align-items:center;flex-wrap:wrap;gap:12px;margin-top:24px}
28
+ .button{display:inline-flex;min-height:42px;align-items:center;justify-content:center;padding:0 17px;border:1px solid var(--blue);border-radius:8px;background:var(--blue);color:#fff;font-family:inherit;font-size:13px;font-weight:600;text-decoration:none;cursor:pointer}
29
+ .button:hover{background:oklch(.6 .18 255)}
30
+ .button:disabled{cursor:wait;opacity:.7}
31
+ .secondary{border-color:var(--border);background:transparent;color:var(--fg)}
32
+ .secondary:hover{background:var(--raised)}
33
+ :is(button,a):focus-visible{outline:2px solid var(--blue-light);outline-offset:3px}
34
+ .note{margin-top:30px;padding-top:20px;border-top:1px solid var(--border);font-size:12px}
35
+ .env-header{display:flex;justify-content:space-between;align-items:center;gap:14px;margin-top:36px}
36
+ h2{margin:0;font-size:14px;font-weight:600}
37
+ pre{white-space:pre-wrap;overflow-wrap:anywhere;max-height:380px;overflow:auto;padding:18px;border:1px solid var(--border);border-radius:10px;background:var(--surface);font:12px/1.6 ui-monospace,SFMono-Regular,Menlo,monospace}
38
+ @media(max-width:480px){.shell{width:calc(100% - 32px)}header{height:70px}main{padding-top:56px}.actions{align-items:stretch;flex-direction:column}.actions form,.actions .button{width:100%}}
39
+ @media(prefers-reduced-motion:reduce){.option{transition:none}}
40
+ `;
41
+
42
+ function page(title: string, content: string): string {
43
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${escapeHtml(title)} · Opengeni</title><style>${pageStyle}</style></head><body><div class="shell"><header><span class="brand">Opengeni</span><span class="context">GitHub connection</span></header><main>${content}</main></div></body></html>`;
44
+ }
45
+
46
+ export function githubInstallationChooserHtml(
47
+ candidates: GitHubInstallationBindingCandidate[],
48
+ state: string,
49
+ workspaceId: string,
50
+ baseUrl: string,
51
+ ): string {
52
+ const action = `${baseUrl}/v1/workspaces/${encodeURIComponent(workspaceId)}/github/installations/select`;
53
+ const options = candidates
54
+ .map(({ installation, authorityKind }, index) => {
55
+ const account = escapeHtml(
56
+ installation.accountLogin ?? `installation ${installation.installationId}`,
57
+ );
58
+ const label = authorityKind === "personal_owner" ? "Personal account" : "Organization owner";
59
+ return `<label class="option"><input type="radio" name="installation_id" value="${installation.installationId}" required${candidates.length === 1 && index === 0 ? " checked" : ""}><span class="option-text"><strong>${account}</strong><small>${label}</small></span></label>`;
60
+ })
61
+ .join("");
62
+ return page(
63
+ "Choose a GitHub account",
64
+ `<h1>Choose a GitHub account</h1><p class="intro">Connect an account where the Opengeni GitHub App is already installed, or install it on another account.</p><form id="existing-account" method="get" action="${escapeHtml(action)}"><input type="hidden" name="state" value="${escapeHtml(state)}"><fieldset class="options" aria-label="Available GitHub accounts">${options}</fieldset></form><div class="actions"><button class="button" type="submit" form="existing-account">Connect selected account</button><form method="get" action="${escapeHtml(action)}"><input type="hidden" name="state" value="${escapeHtml(state)}"><input type="hidden" name="installation_id" value="new"><button class="button secondary" type="submit">Install on another account</button></form></div><p class="note">Only accounts GitHub has confirmed you own or manage appear here.</p>`,
65
+ );
66
+ }
67
+
68
+ export function githubSetupSuccessHtml(account: string, returnUrl: string): string {
69
+ return page(
70
+ "GitHub connected",
71
+ `<h1>GitHub connected</h1><p class="intro"><strong>${escapeHtml(account)}</strong> is now available in this Opengeni workspace. Opengeni can access only the repositories allowed for this installation.</p><a class="button" href="${escapeHtml(returnUrl)}">Back to Opengeni</a>`,
72
+ );
73
+ }
74
+
75
+ export function githubSetupPendingHtml(): string {
76
+ return page(
77
+ "GitHub approval pending",
78
+ `<h1>Waiting for an organization owner</h1><p>An owner needs to approve your GitHub App request before you can connect. Opengeni has not created a workspace binding.</p>`,
79
+ );
80
+ }
81
+
82
+ /**
83
+ * Why a GitHub browser step could not continue. Browser navigation (a stale
84
+ * page-load link, GitHub's Cancel button, a non-owner) must land on a readable
85
+ * page with a way back, never on a raw JSON error body.
86
+ */
87
+ export type GitHubConnectFailure =
88
+ | "expired"
89
+ | "cancelled"
90
+ | "not_owner"
91
+ | "forbidden"
92
+ | "policy_denied"
93
+ | "signed_out"
94
+ | "failed";
95
+
96
+ const GITHUB_CONNECT_FAILURE_COPY: Record<GitHubConnectFailure, { title: string; body: string }> = {
97
+ expired: {
98
+ title: "This GitHub link expired",
99
+ body: "GitHub connection links stay valid for 10 minutes, and each one works once. Go back to Opengeni and select Connect again to get a fresh link.",
100
+ },
101
+ cancelled: {
102
+ title: "GitHub connection cancelled",
103
+ body: "You cancelled on GitHub, so nothing was connected. Go back to Opengeni and select Connect when you are ready.",
104
+ },
105
+ not_owner: {
106
+ title: "An owner needs to connect this account",
107
+ body: "Only the owner of the GitHub account, or an owner of the GitHub organization, can connect it to Opengeni. Ask an owner to connect it, or install the app on an account you own.",
108
+ },
109
+ forbidden: {
110
+ title: "You can't manage GitHub here",
111
+ body: "Your Opengeni access doesn't allow connecting GitHub for this workspace. Ask a workspace admin to connect it.",
112
+ },
113
+ policy_denied: {
114
+ title: "GitHub is turned off for your organization",
115
+ body: "Your organization's integration policy doesn't allow connecting GitHub. Ask an organization admin to allow it, then select Connect again.",
116
+ },
117
+ signed_out: {
118
+ title: "Sign in to continue",
119
+ body: "Your Opengeni sign-in wasn't available when GitHub sent you back. Sign in to Opengeni and select Connect again.",
120
+ },
121
+ failed: {
122
+ title: "GitHub couldn't finish connecting",
123
+ body: "Nothing was connected. Go back to Opengeni and select Connect to try again.",
124
+ },
125
+ };
126
+
127
+ export function githubConnectFailureHtml(
128
+ failure: GitHubConnectFailure,
129
+ returnUrl: string,
130
+ detail?: string | null,
131
+ ): string {
132
+ const copy = GITHUB_CONNECT_FAILURE_COPY[failure];
133
+ const note = detail?.trim()
134
+ ? `<p class="note">Details: ${escapeHtml(detail.trim().slice(0, 300))}</p>`
135
+ : "";
136
+ return page(
137
+ copy.title,
138
+ `<h1>${escapeHtml(copy.title)}</h1><p class="intro">${escapeHtml(copy.body)}</p><a class="button" href="${escapeHtml(returnUrl)}">Back to Opengeni</a>${note}`,
139
+ );
140
+ }
141
+
142
+ export function githubSuccessHtml(envLines: string[]): string {
143
+ const escaped = escapeHtml(envLines.join("\n"));
144
+ return page(
145
+ "GitHub App created",
146
+ `<h1>GitHub App created</h1><p>Add these values to .env, then restart API and worker.</p><div class="env-header"><h2>Environment variables</h2><button class="button secondary" id="copy-env" type="button">Copy env</button></div><pre id="env-lines">${escaped}</pre><script>(()=>{const button=document.getElementById("copy-env");const env=document.getElementById("env-lines");async function copyText(text){if(navigator.clipboard&&window.isSecureContext){await navigator.clipboard.writeText(text);return;}const area=document.createElement("textarea");area.value=text;area.setAttribute("readonly","");area.style.position="fixed";area.style.inset="-9999px";document.body.append(area);area.select();document.execCommand("copy");area.remove();}button?.addEventListener("click",async()=>{try{await copyText(env?.textContent||"");button.textContent="Copied";setTimeout(()=>button.textContent="Copy env",1600);}catch{button.textContent="Copy failed";setTimeout(()=>button.textContent="Copy env",2200);}});})();</script>`,
147
+ );
148
+ }
149
+
150
+ function escapeHtml(value: string): string {
151
+ return value.replace(
152
+ /[&<>"']/g,
153
+ (char) =>
154
+ ({
155
+ "&": "&amp;",
156
+ "<": "&lt;",
157
+ ">": "&gt;",
158
+ '"': "&quot;",
159
+ "'": "&#39;",
160
+ })[char] ?? char,
161
+ );
162
+ }