@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
@@ -0,0 +1,193 @@
1
+ /**
2
+ * Markdown to Slack mrkdwn for model-authored text OpenGeni posts to Slack.
3
+ *
4
+ * Agents write ordinary Markdown because the same reply renders as Markdown in
5
+ * the OpenGeni console. Slack message text does not render Markdown headings,
6
+ * `**bold**`, or `[label](url)` links, so a reply that reads well in the
7
+ * console arrives in Slack full of literal syntax. This rewrites only that
8
+ * syntax, only for the bytes handed to Slack: stored session events, model
9
+ * history, and every other surface keep the model's exact text. It is
10
+ * formatting at a third-party sink, not content classification or redaction.
11
+ *
12
+ * Code is never rewritten: fenced code blocks keep every line between their
13
+ * fences, and inline code spans are copied as they are.
14
+ *
15
+ * The output is part of the Slack post and update ledgers' request digest, so a
16
+ * change to it changes the bytes an in-flight operation is bound to. Delivery
17
+ * (`deliverSlackModelText` in `slack-interactions.ts`) falls back only to the
18
+ * unformatted text; a later output change must keep the previous rendering
19
+ * reachable the same way, or operations started before the deploy conflict on
20
+ * every retry.
21
+ */
22
+
23
+ /**
24
+ * A private provider citation handle: U+E200 `cite`, one or more U+E202
25
+ * separated references, then U+E201. Codex web search can emit these without
26
+ * the annotation table that would make them resolvable, so they carry nothing a
27
+ * Slack reader can use. The OpenGeni timeline hides the same handles
28
+ * (`stripOpaqueCitationTokens` in `packages/react/src/timeline/projection.ts`).
29
+ */
30
+ const PROVIDER_CITATION_TOKEN =
31
+ /[ \t]*\u{E200}(?:cite|filecite)(?:\u{E202}[^\u{E200}-\u{E202}]*)*\u{E201}/gu;
32
+ /** Stray citation delimiters, for example from a token cut short upstream. */
33
+ const PROVIDER_CITATION_DELIMITER = /[\u{E200}-\u{E202}]/gu;
34
+
35
+ /**
36
+ * A fence at any indentation. Agents nest code under list items (a `10.` item
37
+ * or a nested bullet puts the fence four or more spaces in), and Slack has no
38
+ * indented code block, so a fence line is a fence wherever it starts.
39
+ */
40
+ const FENCE_OPEN = /^[ \t]*(`{3,}|~{3,})(.*)$/u;
41
+ const ATX_HEADING = /^ {0,3}#{1,6}[ \t]+(.*?)(?:[ \t]+#+)?[ \t]*$/u;
42
+ const BULLET = /^([ \t]*)[-*+][ \t]+(?=\S)/u;
43
+ const THEMATIC_BREAK = /^ {0,3}([-*_])(?:[ \t]*\1){2,}[ \t]*$/u;
44
+
45
+ /**
46
+ * Spans that must reach Slack exactly as written and are set aside before any
47
+ * link is read: inline code, and Slack's own `<...>` tokens (mentions, channel
48
+ * links, and links already in Slack form).
49
+ */
50
+ const CODE_SPAN_OR_SLACK_TOKEN =
51
+ /(`+)(?!`)[^\n]*?(?<!`)\1(?!`)|<(?:[@#!]|https?:|mailto:)[^<>\n]*>/gu;
52
+
53
+ /**
54
+ * A Markdown link or image whose target is a web or mail address. Other
55
+ * targets, such as OpenGeni's own `artifact:` and `sandbox:` links, mean
56
+ * nothing in Slack and are left as written rather than turned into dead links.
57
+ * One level of balanced parentheses is allowed in the URL (as in Wikipedia
58
+ * links), and an optional link title is dropped.
59
+ */
60
+ const MARKDOWN_LINK =
61
+ /!?\[((?:[^[\]\n]|\[[^[\]\n]*\])+)\]\(\s*((?:https?:\/\/|mailto:)(?:[^\s()<>|]|\([^\s()<>|]*\))+)(?:\s+(?:"[^"\n]*"|'[^'\n]*'|\([^()\n]*\)))?\s*\)/gu;
62
+
63
+ /** Bare URLs are set aside after links: they may legitimately contain `__` or `**`. */
64
+ const BARE_URL = /\bhttps?:\/\/[^\s<>]+/gu;
65
+
66
+ const BOLD_ITALIC_ASTERISK = /\*\*\*(?=[^\s*])((?:[^*\n]|\*(?!\*))+?)(?<=[^\s*])\*\*\*/gu;
67
+ const BOLD_ASTERISK = /\*\*(?=[^\s*])((?:[^*\n]|\*(?!\*))+?)(?<=[^\s*])\*\*/gu;
68
+ const BOLD_UNDERSCORE =
69
+ /(?<![\p{L}\p{N}_])__(?=[^\s_])((?:[^_\n]|_(?!_))+?)(?<=[^\s_])__(?![\p{L}\p{N}_])/gu;
70
+ const STRIKETHROUGH = /~~(?=[^\s~])((?:[^~\n]|~(?!~))+?)(?<=[^\s~])~~/gu;
71
+ const PLACEHOLDER = /\u0000(\d+)\u0000/gu;
72
+
73
+ /** Remove provider citation handles and any stray citation delimiters. */
74
+ export function stripProviderCitationMarkers(text: string): string {
75
+ return text.replace(PROVIDER_CITATION_TOKEN, "").replace(PROVIDER_CITATION_DELIMITER, "");
76
+ }
77
+
78
+ /**
79
+ * Rewrite common Markdown as Slack mrkdwn.
80
+ *
81
+ * - `# Heading` (any level) becomes a bold line.
82
+ * - `**bold**` and `__bold__` become `*bold*`, `***both***` becomes `*_both_*`,
83
+ * and `~~struck~~` becomes `~struck~`.
84
+ * - `[label](https://...)` becomes `<https://...|label>`.
85
+ * - `-`, `*`, and `+` bullets become Slack's `•` bullet, keeping indentation;
86
+ * numbered lists and block quotes are already Slack syntax and stay as they are.
87
+ * - Fenced code blocks, at any indentation, keep their contents byte for byte.
88
+ * The opening fence loses its language tag, which Slack would otherwise print
89
+ * as the first code line, and an unclosed block is closed so Slack still
90
+ * renders it as code.
91
+ * - Provider citation handles are removed.
92
+ *
93
+ * Text that uses none of this syntax comes back unchanged.
94
+ */
95
+ export function slackMrkdwnFromMarkdown(markdown: string): string {
96
+ const lines = stripProviderCitationMarkers(markdown).split("\n");
97
+ const output: string[] = [];
98
+ let fence: { character: string; length: number } | null = null;
99
+ for (const line of lines) {
100
+ if (fence) {
101
+ if (closesFence(line, fence)) {
102
+ output.push("```");
103
+ fence = null;
104
+ } else {
105
+ output.push(line);
106
+ }
107
+ continue;
108
+ }
109
+ const opening = FENCE_OPEN.exec(line);
110
+ // A backtick fence's info string cannot contain a backtick; such a line is
111
+ // inline code, not a fence.
112
+ if (opening && !(opening[1]!.startsWith("`") && opening[2]!.includes("`"))) {
113
+ fence = { character: opening[1]![0]!, length: opening[1]!.length };
114
+ output.push("```");
115
+ continue;
116
+ }
117
+ output.push(slackLine(line));
118
+ }
119
+ if (fence) output.push("```");
120
+ return output.join("\n");
121
+ }
122
+
123
+ function closesFence(line: string, fence: { character: string; length: number }): boolean {
124
+ const trimmed = line.trim();
125
+ return (
126
+ trimmed.length >= fence.length &&
127
+ [...trimmed].every((character) => character === fence.character)
128
+ );
129
+ }
130
+
131
+ function slackLine(line: string): string {
132
+ if (THEMATIC_BREAK.test(line)) return line;
133
+ const heading = ATX_HEADING.exec(line);
134
+ if (heading) {
135
+ const content = slackInline(heading[1]!, { heading: true }).trim();
136
+ return content ? `*${content}*` : line;
137
+ }
138
+ const bullet = BULLET.exec(line);
139
+ if (bullet) {
140
+ return `${bullet[1]}• ${slackInline(line.slice(bullet[0].length), { heading: false })}`;
141
+ }
142
+ return slackInline(line, { heading: false });
143
+ }
144
+
145
+ function slackInline(text: string, options: { heading: boolean }): string {
146
+ const kept: string[] = [];
147
+ const keep = (value: string) => {
148
+ kept.push(value);
149
+ return `\u0000${kept.length - 1}\u0000`;
150
+ };
151
+ // The placeholders are NUL-delimited, so a NUL in the model text itself
152
+ // could collide with one. Slack does not render NUL; drop it.
153
+ let working = text.replace(/\u0000/gu, "");
154
+ working = working.replace(CODE_SPAN_OR_SLACK_TOKEN, keep);
155
+ working = working.replace(MARKDOWN_LINK, (_match, label: string, url: string) =>
156
+ keep(slackLink(restore(label, kept), url)),
157
+ );
158
+ working = working.replace(BARE_URL, keep);
159
+ if (options.heading) {
160
+ // The whole heading becomes one bold span, so emphasis inside it would
161
+ // nest and break Slack's parser. Drop the emphasis markers instead.
162
+ working = working
163
+ .replace(BOLD_ASTERISK, "$1")
164
+ .replace(BOLD_UNDERSCORE, "$1")
165
+ .replace(/\*/gu, "");
166
+ } else {
167
+ working = working
168
+ .replace(BOLD_ITALIC_ASTERISK, "*_$1_*")
169
+ .replace(BOLD_ASTERISK, "*$1*")
170
+ .replace(BOLD_UNDERSCORE, "*$1*");
171
+ }
172
+ working = working.replace(STRIKETHROUGH, "~$1~");
173
+ return restore(working, kept);
174
+ }
175
+
176
+ function restore(text: string, kept: readonly string[]): string {
177
+ return text.replace(PLACEHOLDER, (_match, index: string) => kept[Number(index)] ?? "");
178
+ }
179
+
180
+ function slackLink(label: string, url: string): string {
181
+ const text = label
182
+ .replace(BOLD_ASTERISK, "$1")
183
+ .replace(BOLD_UNDERSCORE, "$1")
184
+ .replace(/`/gu, "")
185
+ .trim();
186
+ if (!text || text === url) return `<${url}>`;
187
+ return `<${url}|${escapeSlackLinkLabel(text)}>`;
188
+ }
189
+
190
+ /** Slack reads `&`, `<`, and `>` as control characters inside a link. */
191
+ function escapeSlackLinkLabel(label: string): string {
192
+ return label.replace(/&/gu, "&amp;").replace(/</gu, "&lt;").replace(/>/gu, "&gt;");
193
+ }
@@ -0,0 +1,337 @@
1
+ /**
2
+ * Per-channel and per-DM Slack workspace routing.
3
+ *
4
+ * Routing is decided BEFORE any agent runs, from durable facts only, so it can
5
+ * never be a model judgement. The resolver is pure with respect to the database
6
+ * reads it is handed: it returns a decision, and the caller persists and acts on
7
+ * it.
8
+ *
9
+ * Two rules are load-bearing and easy to break by accident:
10
+ *
11
+ * A mapped thread keeps the workspace it was created in, unconditionally,
12
+ * including after the channel has been re-pointed. Otherwise a live
13
+ * conversation would silently change tenants mid-thread.
14
+ *
15
+ * A subject who lacks access to the routed workspace is NEVER quietly served
16
+ * from another workspace they happen to belong to. Every failure is explicit
17
+ * and creates no session.
18
+ */
19
+ import type { AccessGrant } from "@opengeni/contracts";
20
+ import type {
21
+ SlackChannelRoute,
22
+ SlackInteractionInboxEntry,
23
+ SlackRoutableWorkspace,
24
+ SlackUserDmRoute,
25
+ } from "@opengeni/db";
26
+
27
+ export type SlackRouteTenancy = { accountId: string; workspaceId: string };
28
+
29
+ export type SlackRouteResolution =
30
+ | (SlackRouteTenancy & {
31
+ kind: "resolved";
32
+ label: string | null;
33
+ source:
34
+ | "thread"
35
+ | "prefix"
36
+ | "channel"
37
+ | "dm_route"
38
+ | "dm_personal"
39
+ | "sole_candidate"
40
+ | "installation";
41
+ })
42
+ | { kind: "ask"; candidates: readonly SlackRoutableWorkspace[] }
43
+ | {
44
+ kind: "denied";
45
+ reason: "no_access_to_named" | "no_access_to_route" | "no_candidates";
46
+ requested: string | null;
47
+ candidates: readonly SlackRoutableWorkspace[];
48
+ };
49
+
50
+ /**
51
+ * A Slack direct message to the bot, by any trigger that can carry one.
52
+ *
53
+ * `isDirectMessageShortcut` is deliberately narrower: it means "a message
54
+ * shortcut invoked inside a DM", which is a private-handoff concern. Routing
55
+ * cares about the broader question of whether this conversation is one human's
56
+ * private channel with the bot, which is also true of an ordinary `dm` event.
57
+ */
58
+ export function isSlackDirectMessageConversation(
59
+ entry: Pick<SlackInteractionInboxEntry, "triggerKind" | "slackChannelId">,
60
+ ): boolean {
61
+ return entry.triggerKind === "dm" || entry.slackChannelId.startsWith("D");
62
+ }
63
+
64
+ const PREFIX = "in ";
65
+
66
+ /**
67
+ * Trigger kinds whose text the invoking human wrote themselves. A message
68
+ * shortcut and a reaction both act on another person's message.
69
+ */
70
+ const AUTHORED_BY_INVOKER: ReadonlySet<string> = new Set([
71
+ "app_mention",
72
+ "dm",
73
+ "slash_command",
74
+ "thread_reply",
75
+ ]);
76
+
77
+ /**
78
+ * Split a leading bot mention off the message text.
79
+ *
80
+ * Slack delivers an `app_mention` with the mention still in the text
81
+ * (`<@U123> do the thing`), and OpenGeni stores it verbatim. The workspace
82
+ * prefix is only an override when it is the first thing the person typed, so it
83
+ * is parsed after the mention rather than at byte 0 of the raw text. Everything
84
+ * split off here is put back, so the message the model sees is unchanged apart
85
+ * from the addressing the person used to route it.
86
+ */
87
+ export function splitSlackLeadingMention(
88
+ text: string,
89
+ botUserId: string | null,
90
+ ): { lead: string; rest: string } {
91
+ if (!botUserId || !/^[UWB][A-Z0-9]{1,63}$/u.test(botUserId)) return { lead: "", rest: text };
92
+ const mention = `<@${botUserId}>`;
93
+ if (!text.startsWith(mention)) return { lead: "", rest: text };
94
+ const remainder = text.slice(mention.length);
95
+ const trimmed = remainder.replace(/^[ \t]+/u, "");
96
+ return { lead: text.slice(0, text.length - trimmed.length), rest: trimmed };
97
+ }
98
+
99
+ /**
100
+ * The strict `in <workspace>: ...` override.
101
+ *
102
+ * Parsed only at byte 0, so ordinary prose that happens to contain the word
103
+ * cannot trigger it, and matched case-insensitively against the exact label of a
104
+ * workspace the subject can already start work in. A prefix that names nothing
105
+ * recognizable is NOT a suggestion that falls through: it is a refusal, because
106
+ * silently ignoring an explicit override is how a message lands somewhere the
107
+ * person did not intend.
108
+ */
109
+ export function parseSlackWorkspacePrefix(text: string): {
110
+ requested: string;
111
+ remainder: string;
112
+ } | null {
113
+ if (!text.toLowerCase().startsWith(PREFIX)) return null;
114
+ const separator = text.indexOf(":");
115
+ if (separator <= PREFIX.length) return null;
116
+ const requested = text.slice(PREFIX.length, separator).trim();
117
+ if (requested.length === 0) return null;
118
+ // A bare address with nothing after it is not a request.
119
+ if (text.slice(separator + 1).trim().length === 0) return null;
120
+ // A label is one line. A colon further down a multi-line message is not a
121
+ // prefix, it is punctuation.
122
+ if (/[\r\n]/u.test(requested)) return null;
123
+ return { requested, remainder: text.slice(separator + 1).replace(/^[ \t]+/u, "") };
124
+ }
125
+
126
+ function matchCandidate(
127
+ candidates: readonly SlackRoutableWorkspace[],
128
+ requested: string,
129
+ ): SlackRoutableWorkspace | null | "ambiguous" {
130
+ const wanted = requested.toLowerCase();
131
+ const matches = candidates.filter((candidate) => candidate.label.toLowerCase() === wanted);
132
+ // Two workspaces whose labels differ only by case are not a tie to break
133
+ // silently: guessing one is the same failure as ignoring the override.
134
+ if (matches.length > 1) return "ambiguous";
135
+ return matches[0] ?? null;
136
+ }
137
+
138
+ export type SlackRouteInputs = {
139
+ /** The installation binding's tenancy. Always the fallback, never a silent one. */
140
+ home: SlackRouteTenancy;
141
+ entry: Pick<
142
+ SlackInteractionInboxEntry,
143
+ "triggerKind" | "slackChannelId" | "slackUserId" | "text"
144
+ >;
145
+ /**
146
+ * The tenancy of an interaction already mapped to this thread, if any. A
147
+ * mapped thread wins unconditionally.
148
+ */
149
+ threadTenancy: SlackRouteTenancy | null;
150
+ channelRoute: SlackChannelRoute | null;
151
+ dmRoute: SlackUserDmRoute | null;
152
+ /** The subject's own personal workspace in the home organization, if any. */
153
+ personalWorkspaceId: string | null;
154
+ /** Workspaces this subject may actually start work in, ordered stably. */
155
+ candidates: readonly SlackRoutableWorkspace[];
156
+ /** The installation's bot user, so a mention does not hide the prefix. */
157
+ botUserId: string | null;
158
+ /** `OPENGENI_SLACK_WORKSPACE_ROUTING_ENABLED`. */
159
+ routingEnabled: boolean;
160
+ /** Whether the first-use picker exists yet. Until it does, ambiguity keeps home. */
161
+ askEnabled: boolean;
162
+ };
163
+
164
+ function labelFor(
165
+ candidates: readonly SlackRoutableWorkspace[],
166
+ workspaceId: string,
167
+ ): string | null {
168
+ return candidates.find((candidate) => candidate.workspaceId === workspaceId)?.label ?? null;
169
+ }
170
+
171
+ /**
172
+ * Decide which workspace this Slack message starts work in.
173
+ *
174
+ * First match wins, and the order is deliberate: continuity beats an explicit
175
+ * override beats configuration beats derivation beats asking.
176
+ */
177
+ export function resolveSlackWorkspaceRoute(input: SlackRouteInputs): SlackRouteResolution {
178
+ // A personal workspace is only ever a destination for that person's own bot
179
+ // DM. Offering it in a channel would be wrong twice over: routing a shared
180
+ // conversation into one member's private space hides it from everyone else
181
+ // in the channel, and - because managed tenancy provisions a personal
182
+ // workspace for every member - counting it as a candidate means nobody ever
183
+ // has exactly one. That defeats the sole-candidate rule below, so an
184
+ // organization with a single shared workspace would be asked to choose in
185
+ // every channel despite having no choice to make.
186
+ const directMessage = isSlackDirectMessageConversation(input.entry);
187
+ const candidates = directMessage
188
+ ? input.candidates
189
+ : input.candidates.filter((candidate) => !candidate.personal);
190
+
191
+ const installation = {
192
+ kind: "resolved" as const,
193
+ accountId: input.home.accountId,
194
+ workspaceId: input.home.workspaceId,
195
+ label: labelFor(candidates, input.home.workspaceId),
196
+ source: "installation" as const,
197
+ };
198
+
199
+ // 0. With routing off no routing read is consulted, so an existing install is
200
+ // byte-identical. A thread that a previous flag-on window already mapped is
201
+ // still honoured, because its interaction, session and events genuinely
202
+ // live there: turning the flag off must stop new routing, not strand a
203
+ // conversation by addressing it in a workspace it is not in.
204
+ if (!input.routingEnabled) {
205
+ return input.threadTenancy
206
+ ? {
207
+ kind: "resolved",
208
+ accountId: input.threadTenancy.accountId,
209
+ workspaceId: input.threadTenancy.workspaceId,
210
+ label: null,
211
+ source: "thread",
212
+ }
213
+ : installation;
214
+ }
215
+
216
+ // 1. A mapped thread keeps its workspace, even if the channel moved since.
217
+ if (input.threadTenancy) {
218
+ return {
219
+ kind: "resolved",
220
+ accountId: input.threadTenancy.accountId,
221
+ workspaceId: input.threadTenancy.workspaceId,
222
+ label: labelFor(candidates, input.threadTenancy.workspaceId),
223
+ source: "thread",
224
+ };
225
+ }
226
+
227
+ // 2. The strict prefix override. It applies to this message only and never
228
+ // writes a route row: an override is not a decision about the channel.
229
+ //
230
+ // Only text the invoking human actually typed can address anything. A
231
+ // message shortcut and a reaction both carry SOMEONE ELSE'S message, so a
232
+ // prefix found there was never an instruction to OpenGeni.
233
+ const prefix = AUTHORED_BY_INVOKER.has(input.entry.triggerKind)
234
+ ? parseSlackWorkspacePrefix(splitSlackLeadingMention(input.entry.text, input.botUserId).rest)
235
+ : null;
236
+ if (prefix) {
237
+ const named = matchCandidate(candidates, prefix.requested);
238
+ if (named === "ambiguous" || !named) {
239
+ return {
240
+ kind: "denied",
241
+ reason: "no_access_to_named",
242
+ requested: prefix.requested,
243
+ candidates: candidates,
244
+ };
245
+ }
246
+ return {
247
+ kind: "resolved",
248
+ accountId: named.accountId,
249
+ workspaceId: named.workspaceId,
250
+ label: named.label,
251
+ source: "prefix",
252
+ };
253
+ }
254
+
255
+ // 3. The channel's remembered answer.
256
+ if (input.channelRoute) {
257
+ return {
258
+ kind: "resolved",
259
+ accountId: input.channelRoute.targetAccountId,
260
+ workspaceId: input.channelRoute.targetWorkspaceId,
261
+ label: labelFor(candidates, input.channelRoute.targetWorkspaceId),
262
+ source: "channel",
263
+ };
264
+ }
265
+
266
+ // 4. A direct message is this human's own conversation with the bot, so it
267
+ // lands in their own workspace unless they chose otherwise. The personal
268
+ // workspace id is DERIVED from an active organization membership pointer;
269
+ // it is never accepted from a Slack payload or a route row.
270
+ if (directMessage) {
271
+ if (input.dmRoute) {
272
+ return {
273
+ kind: "resolved",
274
+ accountId: input.dmRoute.targetAccountId,
275
+ workspaceId: input.dmRoute.targetWorkspaceId,
276
+ label: labelFor(candidates, input.dmRoute.targetWorkspaceId),
277
+ source: "dm_route",
278
+ };
279
+ }
280
+ if (input.personalWorkspaceId) {
281
+ return {
282
+ kind: "resolved",
283
+ accountId: input.home.accountId,
284
+ workspaceId: input.personalWorkspaceId,
285
+ label: labelFor(candidates, input.personalWorkspaceId),
286
+ source: "dm_personal",
287
+ };
288
+ }
289
+ // No workspace of their own is not the same as no workspace at all. Fall
290
+ // through to the ordinary rules so a member of exactly one shared workspace
291
+ // can still work in their bot DM.
292
+ }
293
+
294
+ // 5. One workspace is not a choice. This is what keeps the flag quiet for
295
+ // installs that only ever had one workspace.
296
+ const sole = candidates[0];
297
+ if (candidates.length === 1 && sole) {
298
+ return {
299
+ kind: "resolved",
300
+ accountId: sole.accountId,
301
+ workspaceId: sole.workspaceId,
302
+ label: sole.label,
303
+ source: "sole_candidate",
304
+ };
305
+ }
306
+
307
+ if (candidates.length === 0) {
308
+ return { kind: "denied", reason: "no_candidates", requested: null, candidates: [] };
309
+ }
310
+
311
+ // 6. Genuinely ambiguous. Until the picker exists, keep the installation's
312
+ // workspace rather than inventing an answer.
313
+ return input.askEnabled ? { kind: "ask", candidates: candidates } : installation;
314
+ }
315
+
316
+ /**
317
+ * The message text the model actually sees.
318
+ *
319
+ * A prefix override is addressing information, not part of the request, so it is
320
+ * stripped exactly once and only when it matched.
321
+ */
322
+ export function slackRoutedRequestText(
323
+ text: string,
324
+ resolution: SlackRouteResolution,
325
+ botUserId: string | null,
326
+ ): string {
327
+ if (resolution.kind !== "resolved" || resolution.source !== "prefix") return text;
328
+ const { lead, rest } = splitSlackLeadingMention(text, botUserId);
329
+ // Reached only for a `prefix` resolution, which the authorship rule above
330
+ // already restricted to text the invoking human wrote.
331
+ const parsed = parseSlackWorkspacePrefix(rest);
332
+ return parsed ? `${lead}${parsed.remainder}` : text;
333
+ }
334
+
335
+ export type SlackRouteAuthorization =
336
+ | { kind: "authorized"; grant: AccessGrant; tenancy: SlackRouteTenancy; label: string | null }
337
+ | { kind: "denied"; reason: "no_access_to_route"; tenancy: SlackRouteTenancy };