@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.
- package/dist/access-grant-rls.d.ts +3 -0
- package/dist/admission-trace.d.ts +8 -0
- package/dist/api-websocket.d.ts +18 -0
- package/dist/app.d.ts +50 -2
- package/dist/app.js +12 -1
- package/dist/artifact-catalog-cursor.d.ts +27 -0
- package/dist/auth/managed-auth-attempt-context.d.ts +10 -0
- package/dist/auth/managed-auth-rate-limits.d.ts +167 -0
- package/dist/auth/managed-auth-session-adapter.d.ts +6 -0
- package/dist/auth/managed-auth.d.ts +102 -3
- package/dist/auth/managed-email.d.ts +29 -0
- package/dist/auth/managed-sign-in-notifications.d.ts +4 -0
- package/dist/auth/new-signups-gate.d.ts +23 -0
- package/dist/auth/organization-user-setup.d.ts +80 -0
- package/dist/auth/signup-funnel-metrics.d.ts +44 -0
- package/dist/browser-controller-authority.d.ts +1 -61
- package/dist/browser-ephemeral.d.ts +5 -0
- package/dist/browser-state-authority.d.ts +1 -27
- package/dist/chunk-6TZ5LG3O.js +123948 -0
- package/dist/chunk-6TZ5LG3O.js.map +1 -0
- package/dist/chunk-OGQDXVXY.js +18 -0
- package/dist/chunk-OGQDXVXY.js.map +1 -0
- package/dist/claude-subscription-account-conflicts.d.ts +1 -0
- package/dist/claude-subscription-account-usage.d.ts +39 -0
- package/dist/claude-subscription-oauth.d.ts +25 -0
- package/dist/claude-subscription-profile.d.ts +9 -0
- package/dist/claude-subscription-usage.d.ts +44 -0
- package/dist/claude-workspace-connection.d.ts +15 -0
- package/dist/codemode.d.ts +32 -1
- package/dist/company-brain-okf.d.ts +24 -0
- package/dist/connected-machine-computer-access.d.ts +11 -0
- package/dist/connection-ownership.d.ts +54 -0
- package/dist/controller-data-plane.d.ts +32 -0
- package/dist/editable-artifact-source-session.d.ts +3 -0
- package/dist/editable-artifact-websocket.d.ts +10 -18
- package/dist/editable-artifact-workspace-files.d.ts +1 -1
- package/dist/fatal-process-boundary.d.ts +25 -0
- package/dist/github-access.d.ts +31 -2
- package/dist/http/acting-person.d.ts +24 -0
- package/dist/http/api-contract-compat.d.ts +19 -0
- package/dist/http/api-error.d.ts +27 -1
- package/dist/http/cors.d.ts +17 -0
- package/dist/http/deprecation.d.ts +42 -0
- package/dist/http/interaction-control-error.d.ts +13 -0
- package/dist/http/keyed-admission.d.ts +12 -0
- package/dist/http/local-browser-boundary.d.ts +72 -0
- package/dist/http/metrics-listener.d.ts +22 -0
- package/dist/http/path-identifier.d.ts +17 -0
- package/dist/http/registered-route-label.d.ts +16 -0
- package/dist/http/request-body.d.ts +27 -0
- package/dist/http/request-source.d.ts +52 -0
- package/dist/http/sse.d.ts +17 -6
- package/dist/http/unmatched-route.d.ts +18 -0
- package/dist/http/user-content.d.ts +36 -0
- package/dist/http/websocket-upgrade-dispatch.d.ts +18 -0
- package/dist/index.d.ts +10 -3
- package/dist/index.js +1091 -162
- package/dist/index.js.map +1 -1
- package/dist/integration-endpoint-test.d.ts +20 -0
- package/dist/integrations/api-integrations.d.ts +2 -2
- package/dist/integrations/atlassian.d.ts +29 -3
- package/dist/integrations/connect-authority.d.ts +1 -0
- package/dist/integrations/connect-callback-return.d.ts +6 -0
- package/dist/integrations/fiken.d.ts +16 -2
- package/dist/integrations/github-app-connect.d.ts +47 -0
- package/dist/integrations/github-installation-proof.d.ts +3 -0
- package/dist/integrations/github-lens-connect.d.ts +116 -0
- package/dist/integrations/github-skill-source.d.ts +1 -5
- package/dist/integrations/google-drive.d.ts +24 -0
- package/dist/integrations/integration-commit-authority.d.ts +13 -0
- package/dist/integrations/marketplace-plugin.d.ts +2691 -0
- package/dist/integrations/oauth-client.d.ts +178 -18
- package/dist/integrations/oauth-profiles.d.ts +222 -0
- package/dist/integrations/oauth-return-path.d.ts +1 -0
- package/dist/integrations/personal-github-repositories.d.ts +84 -0
- package/dist/integrations/personal-github.d.ts +42 -0
- package/dist/integrations/pr-review-provider.d.ts +22 -0
- package/dist/integrations/provider-oauth.d.ts +19 -1
- package/dist/integrations/slack-app-home.d.ts +24 -0
- package/dist/integrations/slack-bot.d.ts +367 -12
- package/dist/integrations/slack-file-upload-flow.d.ts +62 -0
- package/dist/integrations/slack-install.d.ts +20 -0
- package/dist/integrations/slack-interactions.d.ts +107 -9
- package/dist/integrations/slack-mrkdwn.d.ts +41 -0
- package/dist/integrations/slack-routing.d.ts +120 -0
- package/dist/integrations/slack-session-defaults.d.ts +58 -0
- package/dist/integrations/slack-task-file-upload.d.ts +36 -0
- package/dist/integrations/social-oauth.d.ts +7 -0
- package/dist/interaction-agent-access.d.ts +15 -0
- package/dist/interaction-frame-proxy.d.ts +68 -0
- package/dist/interaction-metrics.d.ts +2 -0
- package/dist/mcp/action-catalog-types.d.ts +9 -0
- package/dist/mcp/action-catalog.gen.d.ts +2 -0
- package/dist/mcp/capability-account-readiness.d.ts +7 -0
- package/dist/mcp/child-read-evidence.d.ts +9 -0
- package/dist/mcp/command-read.d.ts +24 -0
- package/dist/mcp/company-profile-agent-admin.d.ts +44 -0
- package/dist/mcp/contract-input.d.ts +11 -0
- package/dist/mcp/documents.d.ts +7 -5
- package/dist/mcp/files.d.ts +2 -2
- package/dist/mcp/first-party-tool-permissions.d.ts +650 -0
- package/dist/mcp/knowledge-entries.d.ts +5 -0
- package/dist/mcp/orchestration-failure-diagnostic.d.ts +43 -0
- package/dist/mcp/receipts.d.ts +9 -0
- package/dist/mcp/request-abort.d.ts +19 -0
- package/dist/mcp/scheduled-task-input.d.ts +1189 -0
- package/dist/mcp/scheduled-task-view.d.ts +22 -4
- package/dist/mcp/server.d.ts +255 -3
- package/dist/mcp/session-event-view.d.ts +94 -0
- package/dist/mcp/session-view.d.ts +66 -2
- package/dist/mcp/session-wait.d.ts +174 -0
- package/dist/mcp-oauth.d.ts +52 -0
- package/dist/model-catalog.d.ts +11 -31
- package/dist/observability.d.ts +3 -1
- package/dist/organization-mcp.d.ts +52 -0
- package/dist/organization-recovery-notifications.d.ts +41 -0
- package/dist/routes/analytics-consent.d.ts +26 -0
- package/dist/routes/api-integrations.d.ts +25 -0
- package/dist/routes/api-keys.d.ts +17 -0
- package/dist/routes/artifact-catalog.d.ts +6 -0
- package/dist/routes/automations.d.ts +19 -0
- package/dist/routes/billing.d.ts +49 -0
- package/dist/routes/browser-sessions.d.ts +59 -2
- package/dist/routes/claude-subscription-accounts.d.ts +3 -0
- package/dist/routes/claude-subscription-oauth.d.ts +3 -0
- package/dist/routes/client-errors.d.ts +146 -0
- package/dist/routes/codex.d.ts +76 -2
- package/dist/routes/company-brain.d.ts +3 -0
- package/dist/routes/company-profile.d.ts +2 -1
- package/dist/routes/computer-sessions.d.ts +14 -0
- package/dist/routes/connect.d.ts +5 -0
- package/dist/routes/editable-artifacts.d.ts +2 -0
- package/dist/routes/external-identity-links.d.ts +6 -0
- package/dist/routes/feedback.d.ts +3 -0
- package/dist/routes/github-browser-pages.d.ts +12 -0
- package/dist/routes/github.d.ts +2 -0
- package/dist/routes/insights-response-cache.d.ts +10 -0
- package/dist/routes/insights-usage.d.ts +12 -0
- package/dist/routes/insights.d.ts +30 -1
- package/dist/routes/knowledge.d.ts +3 -0
- package/dist/routes/managed-auth-session-sets.d.ts +26 -0
- package/dist/routes/managed-onboarding.d.ts +29 -0
- package/dist/routes/managed-sign-in-methods.d.ts +6 -0
- package/dist/routes/model-connection-access.d.ts +3 -0
- package/dist/routes/organization-integration-policy.d.ts +3 -0
- package/dist/routes/organization-integrations.d.ts +6 -0
- package/dist/routes/organization-mcp-connections.d.ts +3 -0
- package/dist/routes/organization-model-providers.d.ts +3 -0
- package/dist/routes/organization-recovery.d.ts +19 -0
- package/dist/routes/organization-service-accounts.d.ts +3 -0
- package/dist/routes/organization-sessions.d.ts +26 -0
- package/dist/routes/personal-github-git-broker.d.ts +29 -0
- package/dist/routes/personal-github.d.ts +3 -0
- package/dist/routes/pr-review-github.d.ts +3 -0
- package/dist/routes/pr-review.d.ts +3 -0
- package/dist/routes/session-artifact-associations.d.ts +4 -0
- package/dist/routes/session-history-imports.d.ts +9 -0
- package/dist/routes/sessions.d.ts +44 -6
- package/dist/routes/skill-content.d.ts +4 -0
- package/dist/routes/skill-install-authority.d.ts +6 -0
- package/dist/routes/subscription-account-pools.d.ts +34 -0
- package/dist/routes/subscription-pool-access.d.ts +16 -0
- package/dist/routes/supergrok.d.ts +6 -1
- package/dist/routes/usage-allowances.d.ts +12 -0
- package/dist/routes/user-resource-authorities.d.ts +3 -0
- package/dist/routes/workspace-artifacts.d.ts +2 -1
- package/dist/routes/workspace-capture.d.ts +10 -2
- package/dist/routes/workspace-integrations.d.ts +35 -0
- package/dist/routes/workspace-learning.d.ts +3 -0
- package/dist/routes/workspace-model-providers.d.ts +4 -0
- package/dist/routes/workspaces.d.ts +29 -0
- package/dist/sandbox/auth-callout.d.ts +8 -0
- package/dist/sandbox/channel-a.d.ts +19 -1
- package/dist/sandbox/connection-authority.d.ts +3 -0
- package/dist/sandbox/enrollment.d.ts +9 -1
- package/dist/sandbox/machines.d.ts +5 -2
- package/dist/sandbox/metrics-ingestion.d.ts +28 -20
- package/dist/sandbox/rematerialize.d.ts +2 -0
- package/dist/sandbox/session-attach-variable-sets.d.ts +30 -0
- package/dist/sandbox/viewer.d.ts +26 -1
- package/dist/sandbox-file-artifacts.d.ts +50 -0
- package/dist/scheduled-task-deletion.d.ts +13 -0
- package/dist/session-create-observability.d.ts +5 -0
- package/dist/session-event-view-MPS2OZD3.js +292 -0
- package/dist/session-event-view-MPS2OZD3.js.map +1 -0
- package/dist/site-session-origin.d.ts +13 -0
- package/dist/site-uploads.d.ts +41 -0
- package/dist/slack-reaction-files.d.ts +1 -1
- package/dist/temporal-schedule-cleanup.d.ts +1 -0
- package/dist/temporal-schedule-sync.d.ts +26 -0
- package/dist/transcription/providers/xai-subscription.d.ts +1 -0
- package/dist/transcription/service.d.ts +2 -1
- package/dist/work-discovery-observability.d.ts +33 -0
- package/dist/workspace-artifact-content.d.ts +28 -0
- package/dist/workspace-artifact-provenance.d.ts +7 -0
- package/dist/workspace-delete-observability.d.ts +10 -0
- package/dist/workspace-deletion.d.ts +6 -0
- package/dist/workspace-state-projection.d.ts +3 -5
- package/dist/workspace-tool-gateway-observability.d.ts +17 -0
- package/dist/workspace-tool-gateway.d.ts +123 -0
- package/dist/workspace-webhook-dispatch.d.ts +26 -0
- package/package.json +23 -19
- package/src/access-grant-rls.ts +75 -0
- package/src/admission-trace.ts +14 -0
- package/src/api-websocket.ts +23 -0
- package/src/app.ts +1967 -252
- package/src/artifact-catalog-cursor.ts +60 -0
- package/src/auth/managed-auth-attempt-context.ts +70 -0
- package/src/auth/managed-auth-rate-limits.ts +252 -0
- package/src/auth/managed-auth-session-adapter.ts +222 -0
- package/src/auth/managed-auth.ts +536 -60
- package/src/auth/managed-email.ts +174 -0
- package/src/auth/managed-sign-in-notifications.ts +81 -0
- package/src/auth/new-signups-gate.ts +49 -0
- package/src/auth/organization-user-setup.ts +307 -0
- package/src/auth/signup-funnel-metrics.ts +164 -0
- package/src/browser-controller-authority.ts +1 -186
- package/src/browser-ephemeral.ts +39 -0
- package/src/browser-state-authority.ts +1 -236
- package/src/claude-subscription-account-conflicts.ts +12 -0
- package/src/claude-subscription-account-usage.ts +48 -0
- package/src/claude-subscription-oauth.ts +362 -0
- package/src/claude-subscription-profile.ts +73 -0
- package/src/claude-subscription-usage.ts +97 -0
- package/src/claude-workspace-connection.ts +82 -0
- package/src/codemode.ts +194 -30
- package/src/codex-realtime.ts +8 -2
- package/src/company-brain-okf.ts +340 -0
- package/src/connected-machine-computer-access.ts +33 -0
- package/src/connection-ownership.ts +199 -0
- package/src/controller-data-plane.ts +81 -0
- package/src/editable-artifact-native-kernel.ts +26 -15
- package/src/editable-artifact-office-import.ts +28 -3
- package/src/editable-artifact-production.ts +23 -7
- package/src/editable-artifact-source-session.ts +123 -0
- package/src/editable-artifact-websocket.ts +27 -21
- package/src/editable-artifact-workspace-files.ts +31 -6
- package/src/fatal-process-boundary.ts +246 -0
- package/src/gateway-realtime.ts +1 -1
- package/src/github-access.ts +147 -5
- package/src/http/acting-person.ts +71 -0
- package/src/http/api-contract-compat.ts +49 -0
- package/src/http/api-error.ts +115 -1
- package/src/http/auth.ts +48 -0
- package/src/http/cors.ts +67 -0
- package/src/http/deprecation.ts +118 -0
- package/src/http/interaction-control-error.ts +164 -0
- package/src/http/keyed-admission.ts +29 -0
- package/src/http/local-browser-boundary.ts +343 -0
- package/src/http/metrics-listener.ts +57 -0
- package/src/http/path-identifier.ts +54 -0
- package/src/http/registered-route-label.ts +34 -0
- package/src/http/request-body.ts +127 -0
- package/src/http/request-source.ts +178 -0
- package/src/http/sse.ts +486 -132
- package/src/http/unmatched-route.ts +60 -0
- package/src/http/user-content.ts +158 -0
- package/src/http/websocket-upgrade-dispatch.ts +29 -0
- package/src/index.ts +157 -52
- package/src/integration-endpoint-test.ts +291 -0
- package/src/integrations/api-integrations.ts +98 -11
- package/src/integrations/atlassian.ts +474 -136
- package/src/integrations/connect-authority.ts +2 -0
- package/src/integrations/connect-callback-return.ts +86 -0
- package/src/integrations/fiken.ts +354 -64
- package/src/integrations/github-app-connect.ts +435 -0
- package/src/integrations/github-installation-proof.ts +60 -0
- package/src/integrations/github-lens-connect.ts +76 -0
- package/src/integrations/github-skill-source.ts +2 -142
- package/src/integrations/google-drive.ts +717 -225
- package/src/integrations/integration-commit-authority.ts +69 -0
- package/src/integrations/marketplace-plugin.ts +48 -0
- package/src/integrations/oauth-client.ts +1356 -459
- package/src/integrations/oauth-profiles.ts +537 -0
- package/src/integrations/oauth-return-path.ts +18 -0
- package/src/integrations/personal-github-repositories.ts +674 -0
- package/src/integrations/personal-github.ts +825 -0
- package/src/integrations/pr-review-provider.ts +246 -0
- package/src/integrations/provider-oauth.ts +250 -22
- package/src/integrations/slack-app-home.ts +300 -0
- package/src/integrations/slack-bot.ts +963 -239
- package/src/integrations/slack-file-upload-flow.ts +147 -0
- package/src/integrations/slack-install.ts +83 -0
- package/src/integrations/slack-interactions.ts +3167 -474
- package/src/integrations/slack-mrkdwn.ts +193 -0
- package/src/integrations/slack-routing.ts +337 -0
- package/src/integrations/slack-session-defaults.ts +224 -0
- package/src/integrations/slack-task-file-upload.ts +323 -0
- package/src/integrations/social-oauth.ts +213 -15
- package/src/interaction-agent-access.ts +48 -0
- package/src/interaction-frame-proxy.ts +409 -0
- package/src/interaction-metrics.ts +30 -0
- package/src/mcp/action-catalog-types.ts +9 -0
- package/src/mcp/action-catalog.gen.ts +6696 -0
- package/src/mcp/capability-account-readiness.ts +17 -0
- package/src/mcp/child-read-evidence.ts +19 -0
- package/src/mcp/command-read.ts +109 -0
- package/src/mcp/company-profile-agent-admin.ts +198 -0
- package/src/mcp/contract-input.ts +141 -0
- package/src/mcp/documents.ts +65 -377
- package/src/mcp/editable-artifacts.ts +12 -6
- package/src/mcp/files.ts +80 -43
- package/src/mcp/first-party-tool-permissions.ts +305 -0
- package/src/mcp/knowledge-entries.ts +322 -0
- package/src/mcp/orchestration-failure-diagnostic.ts +43 -0
- package/src/mcp/receipts.ts +34 -0
- package/src/mcp/request-abort.ts +44 -0
- package/src/mcp/scheduled-task-input.ts +76 -0
- package/src/mcp/scheduled-task-view.ts +44 -3
- package/src/mcp/server.ts +3030 -1267
- package/src/mcp/session-event-view.ts +383 -0
- package/src/mcp/session-view.ts +84 -3
- package/src/mcp/session-wait.ts +665 -0
- package/src/mcp-oauth.ts +1123 -0
- package/src/model-catalog.ts +91 -333
- package/src/observability.ts +15 -1
- package/src/organization-mcp.ts +397 -0
- package/src/organization-recovery-notifications.ts +103 -0
- package/src/routes/analytics-consent.ts +123 -0
- package/src/routes/api-integrations.ts +364 -74
- package/src/routes/api-keys.ts +336 -10
- package/src/routes/artifact-catalog.ts +279 -0
- package/src/routes/automations.ts +768 -0
- package/src/routes/billing.ts +461 -46
- package/src/routes/browser-sessions.ts +780 -112
- package/src/routes/canonical-human-identities.ts +90 -14
- package/src/routes/capabilities.ts +107 -6
- package/src/routes/channels.ts +20 -3
- package/src/routes/claude-subscription-accounts.ts +334 -0
- package/src/routes/claude-subscription-oauth.ts +142 -0
- package/src/routes/client-errors.ts +362 -0
- package/src/routes/codex.ts +833 -116
- package/src/routes/company-brain.ts +322 -0
- package/src/routes/company-profile.ts +65 -1
- package/src/routes/computer-sessions.ts +557 -106
- package/src/routes/connect.ts +1938 -0
- package/src/routes/connections.ts +1039 -349
- package/src/routes/documents.ts +488 -287
- package/src/routes/editable-artifacts.ts +79 -9
- package/src/routes/enrollments.ts +135 -39
- package/src/routes/environments.ts +154 -58
- package/src/routes/external-identity-links.ts +201 -0
- package/src/routes/feedback.ts +113 -0
- package/src/routes/files.ts +267 -33
- package/src/routes/github-browser-pages.ts +162 -0
- package/src/routes/github.ts +446 -144
- package/src/routes/insights-response-cache.ts +83 -0
- package/src/routes/insights-usage.ts +345 -0
- package/src/routes/insights.ts +153 -19
- package/src/routes/install.ts +133 -38
- package/src/routes/integration-facets.ts +107 -33
- package/src/routes/interaction-resources.ts +30 -3
- package/src/routes/knowledge.ts +488 -0
- package/src/routes/machines.ts +313 -15
- package/src/routes/managed-auth-session-sets.ts +1218 -0
- package/src/routes/managed-onboarding.ts +337 -0
- package/src/routes/managed-sign-in-methods.ts +433 -0
- package/src/routes/memory-slack-publications.ts +3 -2
- package/src/routes/model-connection-access.ts +208 -0
- package/src/routes/organization-integration-policy.ts +116 -0
- package/src/routes/organization-integrations.ts +326 -0
- package/src/routes/organization-mcp-connections.ts +178 -0
- package/src/routes/organization-memberships.ts +1019 -27
- package/src/routes/organization-model-providers.ts +293 -0
- package/src/routes/organization-recovery.ts +325 -0
- package/src/routes/organization-service-accounts.ts +128 -0
- package/src/routes/organization-sessions.ts +350 -0
- package/src/routes/personal-github-git-broker.ts +785 -0
- package/src/routes/personal-github.ts +370 -0
- package/src/routes/plugins.ts +251 -22
- package/src/routes/pr-review-github.ts +881 -0
- package/src/routes/pr-review.ts +622 -0
- package/src/routes/preference-registry.ts +12 -93
- package/src/routes/rigs.ts +167 -50
- package/src/routes/scheduled-tasks.ts +346 -41
- package/src/routes/session-artifact-associations.ts +87 -0
- package/src/routes/session-history-imports.ts +280 -0
- package/src/routes/sessions.ts +2306 -264
- package/src/routes/skill-content.ts +297 -0
- package/src/routes/skill-install-authority.ts +64 -0
- package/src/routes/skills.ts +62 -9
- package/src/routes/social.ts +46 -18
- package/src/routes/subscription-account-pools.ts +436 -0
- package/src/routes/subscription-pool-access.ts +126 -0
- package/src/routes/supergrok.ts +219 -303
- package/src/routes/transcription-recordings.ts +38 -3
- package/src/routes/transcriptions.ts +4 -0
- package/src/routes/usage-allowances.ts +378 -0
- package/src/routes/user-resource-authorities.ts +138 -0
- package/src/routes/video-generation.ts +14 -10
- package/src/routes/workspace-artifacts.ts +275 -69
- package/src/routes/workspace-capture.ts +21 -0
- package/src/routes/workspace-integrations.ts +576 -0
- package/src/routes/workspace-learning.ts +159 -0
- package/src/routes/workspace-model-providers.ts +148 -0
- package/src/routes/workspace-state.ts +38 -25
- package/src/routes/workspaces.ts +1004 -127
- package/src/sandbox/auth-callout.ts +96 -19
- package/src/sandbox/channel-a.ts +323 -24
- package/src/sandbox/connection-authority.ts +3 -0
- package/src/sandbox/enrollment.ts +81 -6
- package/src/sandbox/machines.ts +218 -76
- package/src/sandbox/metrics-ingestion.ts +411 -62
- package/src/sandbox/rematerialize.ts +121 -19
- package/src/sandbox/session-attach-variable-sets.ts +131 -0
- package/src/sandbox/viewer.ts +280 -25
- package/src/sandbox-file-artifacts.ts +413 -0
- package/src/scheduled-task-deletion.ts +138 -0
- package/src/session-create-observability.ts +46 -0
- package/src/session-realtime-context.ts +6 -3
- package/src/site-session-origin.ts +48 -0
- package/src/site-uploads.ts +224 -0
- package/src/slack-reaction-files.ts +16 -5
- package/src/temporal-schedule-cleanup.ts +13 -0
- package/src/temporal-schedule-sync.ts +74 -0
- package/src/transcription/providers/codex-subscription.ts +2 -0
- package/src/transcription/providers/openai.ts +1 -0
- package/src/transcription/providers/xai-subscription.ts +49 -3
- package/src/transcription/segmenter.ts +8 -2
- package/src/transcription/service.ts +43 -6
- package/src/work-discovery-observability.ts +121 -0
- package/src/workspace-artifact-content.ts +144 -0
- package/src/workspace-artifact-provenance.ts +104 -0
- package/src/workspace-delete-observability.ts +75 -0
- package/src/workspace-deletion.ts +73 -0
- package/src/workspace-state-projection.ts +18 -148
- package/src/workspace-tool-gateway-observability.ts +100 -0
- package/src/workspace-tool-gateway.ts +870 -0
- package/src/workspace-webhook-dispatch.ts +190 -0
- package/src/xai-subscription-auth.ts +9 -2
- package/dist/chunk-WCXHI3FF.js +0 -49920
- package/dist/chunk-WCXHI3FF.js.map +0 -1
- package/dist/routes/packs.d.ts +0 -3
- package/src/routes/packs.ts +0 -729
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resourceMountPath,
|
|
3
|
+
resourceMountPathCollisionKey,
|
|
4
|
+
type AccessGrant,
|
|
5
|
+
type GitHubRepository,
|
|
6
|
+
type RepositoryResourceRef,
|
|
7
|
+
} from "@opengeni/contracts";
|
|
8
|
+
import { listRecentSessionRepositoryResources } from "@opengeni/db";
|
|
9
|
+
import { hasPermission, type ApiRouteDeps } from "@opengeni/core";
|
|
10
|
+
import { githubRepositoryResourceRef, listWorkspaceGitHubRepositories } from "../github-access";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A Slack task starts with at most this many repositories. Each one is a
|
|
14
|
+
* shallow, blob-filtered fetch before the first command, so the set stays
|
|
15
|
+
* small: the person's own most recently used repositories, not a catalog.
|
|
16
|
+
*/
|
|
17
|
+
export const SLACK_SESSION_RECENT_REPOSITORY_LIMIT = 5;
|
|
18
|
+
|
|
19
|
+
/** How far back the person's own sessions count as recent use. */
|
|
20
|
+
export const SLACK_SESSION_RECENT_REPOSITORY_WINDOW_MS = 30 * 24 * 60 * 60 * 1000;
|
|
21
|
+
|
|
22
|
+
function githubUriKey(uri: string): string | null {
|
|
23
|
+
try {
|
|
24
|
+
const url = new URL(uri);
|
|
25
|
+
const path = url.pathname
|
|
26
|
+
.replace(/^\/+|\/+$/gu, "")
|
|
27
|
+
.replace(/\.git$/iu, "")
|
|
28
|
+
.toLowerCase();
|
|
29
|
+
return path ? `${url.host.toLowerCase()}/${path}` : null;
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function positiveRepositoryId(value: unknown): number | null {
|
|
36
|
+
if (typeof value === "number" && Number.isSafeInteger(value) && value > 0) return value;
|
|
37
|
+
if (typeof value === "string" && /^[1-9]\d{0,15}$/u.test(value)) {
|
|
38
|
+
const parsed = Number(value);
|
|
39
|
+
return Number.isSafeInteger(parsed) ? parsed : null;
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Pick a Slack task's repositories: the person's recently used repositories,
|
|
46
|
+
* in recency order, that the workspace GitHub App catalog still offers them.
|
|
47
|
+
*
|
|
48
|
+
* `recent` is a usage signal only, and only repositories the person attached
|
|
49
|
+
* themselves count: an automatically attached (`optional`) one never does. A
|
|
50
|
+
* repository reaches the task only through its current catalog entry (the
|
|
51
|
+
* same catalog and `github:use` permission as the website picker), on its
|
|
52
|
+
* default branch, so a Slack task never reaches a repository the person could
|
|
53
|
+
* not attach on the website today. Archived and
|
|
54
|
+
* empty repositories are skipped when GitHub reported that, because a clone of
|
|
55
|
+
* an empty repository has nothing to check out. Every returned resource is
|
|
56
|
+
* best effort (`optional`): one failed clone warns instead of failing setup.
|
|
57
|
+
*/
|
|
58
|
+
export function selectRecentRepositoryResources(
|
|
59
|
+
recent: readonly RepositoryResourceRef[],
|
|
60
|
+
catalog: readonly GitHubRepository[],
|
|
61
|
+
limit = SLACK_SESSION_RECENT_REPOSITORY_LIMIT,
|
|
62
|
+
): RepositoryResourceRef[] {
|
|
63
|
+
const byId = new Map<number, GitHubRepository[]>();
|
|
64
|
+
const byUri = new Map<string, GitHubRepository>();
|
|
65
|
+
const ordered = [...catalog].sort(
|
|
66
|
+
(left, right) => left.installationId - right.installationId || left.id - right.id,
|
|
67
|
+
);
|
|
68
|
+
for (const repository of ordered) {
|
|
69
|
+
byId.set(repository.id, [...(byId.get(repository.id) ?? []), repository]);
|
|
70
|
+
const key = githubUriKey(repository.cloneUrl);
|
|
71
|
+
if (key && !byUri.has(key)) byUri.set(key, repository);
|
|
72
|
+
}
|
|
73
|
+
const chosen = new Set<string>();
|
|
74
|
+
const mountKeys = new Set<string>();
|
|
75
|
+
const resources: RepositoryResourceRef[] = [];
|
|
76
|
+
for (const used of recent) {
|
|
77
|
+
if (resources.length >= limit) break;
|
|
78
|
+
// A repository OpenGeni attached automatically is not a choice the person
|
|
79
|
+
// made; counting it would keep re-attaching it to every later Slack task.
|
|
80
|
+
if (used.optional === true) continue;
|
|
81
|
+
const repositoryId = positiveRepositoryId(
|
|
82
|
+
used.githubRepositoryId ?? (used.provider === "github" ? used.repositoryId : undefined),
|
|
83
|
+
);
|
|
84
|
+
const installationId = positiveRepositoryId(
|
|
85
|
+
used.githubInstallationId ?? (used.provider === "github" ? used.installationId : undefined),
|
|
86
|
+
);
|
|
87
|
+
const candidates = repositoryId !== null ? (byId.get(repositoryId) ?? []) : [];
|
|
88
|
+
const uriKey = githubUriKey(used.uri);
|
|
89
|
+
const match =
|
|
90
|
+
candidates.find((candidate) => candidate.installationId === installationId) ??
|
|
91
|
+
candidates[0] ??
|
|
92
|
+
(uriKey ? byUri.get(uriKey) : undefined);
|
|
93
|
+
if (!match) continue;
|
|
94
|
+
const identity = `${match.installationId}:${match.id}`;
|
|
95
|
+
if (chosen.has(identity)) continue;
|
|
96
|
+
chosen.add(identity);
|
|
97
|
+
if (match.archived === true || match.sizeKb === 0) continue;
|
|
98
|
+
let resource: RepositoryResourceRef;
|
|
99
|
+
try {
|
|
100
|
+
resource = { ...githubRepositoryResourceRef(match), optional: true };
|
|
101
|
+
} catch {
|
|
102
|
+
// A malformed provider URL is one unusable repository, not a failed task.
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const mountKey = resourceMountPathCollisionKey(resourceMountPath(resource));
|
|
106
|
+
if (mountKeys.has(mountKey)) continue;
|
|
107
|
+
mountKeys.add(mountKey);
|
|
108
|
+
resources.push(resource);
|
|
109
|
+
}
|
|
110
|
+
return resources;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* The repositories a new Slack task starts with: the person's own recently
|
|
115
|
+
* used repositories in this workspace (their own top-level sessions active in
|
|
116
|
+
* the last 30 days, most recent first, at most five), limited to what the
|
|
117
|
+
* workspace GitHub App catalog offers them now. None when they have none; the
|
|
118
|
+
* agent can still find and clone repositories through its GitHub tools.
|
|
119
|
+
*
|
|
120
|
+
* GitHub is asked only when there is something to look up. A GitHub outage or
|
|
121
|
+
* an unconfigured App starts the task without repositories instead of failing
|
|
122
|
+
* it, with a log line for diagnosis.
|
|
123
|
+
*/
|
|
124
|
+
export async function slackRecentRepositoryResources(
|
|
125
|
+
deps: ApiRouteDeps,
|
|
126
|
+
grant: Pick<AccessGrant, "permissions" | "subjectId">,
|
|
127
|
+
workspaceId: string,
|
|
128
|
+
now: Date = new Date(),
|
|
129
|
+
): Promise<RepositoryResourceRef[]> {
|
|
130
|
+
if (!hasPermission(grant.permissions, "github:use")) return [];
|
|
131
|
+
const recent = await listRecentSessionRepositoryResources(deps.db, {
|
|
132
|
+
workspaceId,
|
|
133
|
+
subjectId: grant.subjectId,
|
|
134
|
+
since: new Date(now.getTime() - SLACK_SESSION_RECENT_REPOSITORY_WINDOW_MS),
|
|
135
|
+
});
|
|
136
|
+
if (!recent.some((resource) => resource.optional !== true)) return [];
|
|
137
|
+
let catalog: GitHubRepository[];
|
|
138
|
+
try {
|
|
139
|
+
catalog = await listWorkspaceGitHubRepositories(deps, workspaceId);
|
|
140
|
+
} catch (error) {
|
|
141
|
+
console.error("[slack-interactions] workspace repositories unavailable", {
|
|
142
|
+
workspaceId,
|
|
143
|
+
errorCode: (error instanceof Error ? error.name : "unknown")
|
|
144
|
+
.toLowerCase()
|
|
145
|
+
.replace(/[^a-z0-9_-]/gu, "_")
|
|
146
|
+
.slice(0, 128),
|
|
147
|
+
});
|
|
148
|
+
return [];
|
|
149
|
+
}
|
|
150
|
+
return selectRecentRepositoryResources(recent, catalog);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const OPEN_GENI_LINK_PATTERN =
|
|
154
|
+
/https?:\/\/([^\s/<>|?#]+)(\/workspaces\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}(?:\/sessions\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})?)/giu;
|
|
155
|
+
|
|
156
|
+
const MAX_OTHER_DEPLOYMENT_LINKS = 5;
|
|
157
|
+
|
|
158
|
+
export type OtherDeploymentLink = { url: string; host: string };
|
|
159
|
+
|
|
160
|
+
function siteOf(hostname: string): string | null {
|
|
161
|
+
if (/^[\d.]+$/u.test(hostname) || hostname.includes(":")) return null;
|
|
162
|
+
const labels = hostname.split(".").filter(Boolean);
|
|
163
|
+
return labels.length >= 2 ? labels.slice(-2).join(".") : null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* OpenGeni workspace or session links in `text` that point at a sibling
|
|
168
|
+
* deployment of this one (for example staging versus production).
|
|
169
|
+
*
|
|
170
|
+
* A sibling is a different host under the same parent domain as this
|
|
171
|
+
* deployment's web origin, carrying OpenGeni's own route shape. Anything else
|
|
172
|
+
* is left alone: an unrelated product's URL must never be described as
|
|
173
|
+
* another OpenGeni.
|
|
174
|
+
*/
|
|
175
|
+
export function otherDeploymentLinks(
|
|
176
|
+
text: string,
|
|
177
|
+
webBaseUrl: string | null | undefined,
|
|
178
|
+
): OtherDeploymentLink[] {
|
|
179
|
+
if (!webBaseUrl) return [];
|
|
180
|
+
let own: URL;
|
|
181
|
+
try {
|
|
182
|
+
own = new URL(webBaseUrl);
|
|
183
|
+
} catch {
|
|
184
|
+
return [];
|
|
185
|
+
}
|
|
186
|
+
const ownSite = siteOf(own.hostname.toLowerCase());
|
|
187
|
+
if (!ownSite) return [];
|
|
188
|
+
const found = new Map<string, OtherDeploymentLink>();
|
|
189
|
+
for (const match of text.matchAll(OPEN_GENI_LINK_PATTERN)) {
|
|
190
|
+
let url: URL;
|
|
191
|
+
try {
|
|
192
|
+
url = new URL(`https://${match[1]}${match[2]}`);
|
|
193
|
+
} catch {
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
const host = url.host.toLowerCase();
|
|
197
|
+
if (host === own.host.toLowerCase()) continue;
|
|
198
|
+
if (siteOf(url.hostname.toLowerCase()) !== ownSite) continue;
|
|
199
|
+
const canonical = `${match[0].split("://", 1)[0]!.toLowerCase()}://${host}${match[2]!.toLowerCase()}`;
|
|
200
|
+
if (!found.has(canonical)) found.set(canonical, { url: canonical, host });
|
|
201
|
+
if (found.size >= MAX_OTHER_DEPLOYMENT_LINKS) break;
|
|
202
|
+
}
|
|
203
|
+
return [...found.values()];
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Model context for links to another OpenGeni deployment. A session id from
|
|
208
|
+
* one deployment does not exist in another, so without this the agent reports
|
|
209
|
+
* "Session not found or access denied" and asks for access that no grant in
|
|
210
|
+
* this deployment can give.
|
|
211
|
+
*/
|
|
212
|
+
export function otherDeploymentLinkContext(
|
|
213
|
+
text: string,
|
|
214
|
+
webBaseUrl: string | null | undefined,
|
|
215
|
+
): string | null {
|
|
216
|
+
const links = otherDeploymentLinks(text, webBaseUrl);
|
|
217
|
+
if (links.length === 0 || !webBaseUrl) return null;
|
|
218
|
+
const ownHost = new URL(webBaseUrl).host.toLowerCase();
|
|
219
|
+
return [
|
|
220
|
+
`Links to a different Opengeni deployment (this one is ${ownHost}):`,
|
|
221
|
+
...links.map((link) => `- ${link.url} is on ${link.host}.`),
|
|
222
|
+
`Workspaces, sessions and files from another deployment do not exist here, so looking up their ids in this deployment always fails as not found. Tell the user the link is for ${[...new Set(links.map((link) => link.host))].join(" and ")}, not ${ownHost}, instead of reporting the session as missing or asking for access to it.`,
|
|
223
|
+
].join("\n");
|
|
224
|
+
}
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
hasOpenGeniSlackFileUploadScope,
|
|
4
|
+
OPENGENI_SLACK_BOT_CREDENTIAL_LABEL,
|
|
5
|
+
OPENGENI_SLACK_BOT_CREDENTIAL_ROLE,
|
|
6
|
+
type AccessGrant,
|
|
7
|
+
} from "@opengeni/contracts";
|
|
8
|
+
import {
|
|
9
|
+
fileOwnerContextForAgent,
|
|
10
|
+
hasPermission,
|
|
11
|
+
requireOpenGeniSlackBotConnection,
|
|
12
|
+
requirePermission,
|
|
13
|
+
type ApiRouteDeps,
|
|
14
|
+
} from "@opengeni/core";
|
|
15
|
+
import {
|
|
16
|
+
checkpointSlackFileUpload,
|
|
17
|
+
claimSlackFileUpload,
|
|
18
|
+
getSlackBotUserLink,
|
|
19
|
+
getSlackInteractionForSession,
|
|
20
|
+
recordAuditEvent,
|
|
21
|
+
releaseSlackFileUploadClaim,
|
|
22
|
+
renewSlackFileUploadClaim,
|
|
23
|
+
requireFileForSubject,
|
|
24
|
+
resolveSlackInstallationRoute,
|
|
25
|
+
resolveSlackTargetAuthority,
|
|
26
|
+
withSessionRlsActorContext,
|
|
27
|
+
type SlackInteraction,
|
|
28
|
+
} from "@opengeni/db";
|
|
29
|
+
import { HTTPException } from "hono/http-exception";
|
|
30
|
+
import { createOpenGeniSlackBotInteractionClient } from "./slack-bot";
|
|
31
|
+
import { runSlackFileUpload, SlackFileUploadClaimLostError } from "./slack-file-upload-flow";
|
|
32
|
+
|
|
33
|
+
export const SLACK_TASK_FILE_UPLOAD_MAX_BYTES = 25 * 1024 * 1024;
|
|
34
|
+
const CLAIM_LEASE_MS = 60_000;
|
|
35
|
+
|
|
36
|
+
/** The source and destination are canonical authorities, never a URL or channel supplied by the model. */
|
|
37
|
+
export async function uploadSlackTaskFile(
|
|
38
|
+
deps: ApiRouteDeps,
|
|
39
|
+
input: {
|
|
40
|
+
grant: AccessGrant;
|
|
41
|
+
sessionId: string;
|
|
42
|
+
fileId: string;
|
|
43
|
+
operationId: string;
|
|
44
|
+
authorize: () => Promise<void>;
|
|
45
|
+
},
|
|
46
|
+
) {
|
|
47
|
+
await input.authorize();
|
|
48
|
+
const { grant } = input;
|
|
49
|
+
requirePermission(grant, "connections:read");
|
|
50
|
+
const actor = await fileOwnerContextForAgent(deps, grant, "files:read");
|
|
51
|
+
const scope = { accountId: grant.accountId, workspaceId: grant.workspaceId };
|
|
52
|
+
const resolveTarget = async () => {
|
|
53
|
+
const interaction = await getSlackInteractionForSession(deps.db, {
|
|
54
|
+
...scope,
|
|
55
|
+
sessionId: input.sessionId,
|
|
56
|
+
});
|
|
57
|
+
if (
|
|
58
|
+
!interaction?.initiatingSlackUserId ||
|
|
59
|
+
!interaction.slackThreadTs ||
|
|
60
|
+
!/^\d{1,20}\.\d{1,12}$/.test(interaction.slackThreadTs)
|
|
61
|
+
) {
|
|
62
|
+
throw new HTTPException(422, {
|
|
63
|
+
message: "File delivery requires this session's existing Slack task thread",
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
if (
|
|
67
|
+
interaction.visibility === "private" &&
|
|
68
|
+
actor.initiatingHumanSubjectId !== interaction.owningSubjectId
|
|
69
|
+
) {
|
|
70
|
+
throw new HTTPException(403, {
|
|
71
|
+
message: "Private Slack file delivery requires the task owner's current attempt",
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
assertSlackTaskUploadTarget(interaction);
|
|
75
|
+
const home = await resolveSlackInstallationRoute(deps.db, interaction.slackTeamId);
|
|
76
|
+
if (
|
|
77
|
+
!home ||
|
|
78
|
+
home.accountId !== grant.accountId ||
|
|
79
|
+
home.connectionId !== interaction.connectionId
|
|
80
|
+
) {
|
|
81
|
+
throw new HTTPException(403, { message: "Slack task installation authority changed" });
|
|
82
|
+
}
|
|
83
|
+
const link = await getSlackBotUserLink(
|
|
84
|
+
deps.db,
|
|
85
|
+
home.workspaceId,
|
|
86
|
+
interaction.connectionId,
|
|
87
|
+
interaction.initiatingSlackUserId,
|
|
88
|
+
);
|
|
89
|
+
if (!link || link.subjectId !== interaction.owningSubjectId) {
|
|
90
|
+
throw new HTTPException(403, { message: "Slack task requester is no longer linked" });
|
|
91
|
+
}
|
|
92
|
+
const requester = await resolveSlackTargetAuthority(deps.db, {
|
|
93
|
+
subjectId: link.subjectId,
|
|
94
|
+
targetAccountId: grant.accountId,
|
|
95
|
+
targetWorkspaceId: grant.workspaceId,
|
|
96
|
+
});
|
|
97
|
+
if (!requester || !slackTaskRequesterCanReceiveFile(requester.permissions)) {
|
|
98
|
+
throw new HTTPException(403, {
|
|
99
|
+
message: "Slack task requester no longer has file delivery access",
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
return { interaction, home };
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
return withSessionRlsActorContext(actor, async () => {
|
|
106
|
+
const target = await resolveTarget();
|
|
107
|
+
const file = await requireFileForSubject(deps.db, {
|
|
108
|
+
...scope,
|
|
109
|
+
subjectId: grant.subjectId,
|
|
110
|
+
fileId: input.fileId,
|
|
111
|
+
});
|
|
112
|
+
if (file.workspaceId !== grant.workspaceId) {
|
|
113
|
+
throw new HTTPException(422, {
|
|
114
|
+
message: "Select a retained file from this Slack task's workspace",
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
assertSlackTaskUploadFile(file, target.interaction.visibility);
|
|
118
|
+
const connection = await requireOpenGeniSlackBotConnection(
|
|
119
|
+
deps.db,
|
|
120
|
+
target.home.workspaceId,
|
|
121
|
+
target.interaction.connectionId,
|
|
122
|
+
);
|
|
123
|
+
if (!hasOpenGeniSlackFileUploadScope(connection.grantedScopes)) {
|
|
124
|
+
throw new HTTPException(422, {
|
|
125
|
+
message:
|
|
126
|
+
"A Slack administrator must apply the Opengeni bot manifest and reinstall the bot to grant files:write. A personal Slack connection is not needed.",
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
if (!deps.objectStorage)
|
|
130
|
+
throw new HTTPException(503, { message: "Retained file storage is unavailable" });
|
|
131
|
+
let stored;
|
|
132
|
+
try {
|
|
133
|
+
stored = await deps.objectStorage.getObjectBytes(file.objectKey);
|
|
134
|
+
} catch {
|
|
135
|
+
throw new HTTPException(503, { message: "Retained file content is unavailable" });
|
|
136
|
+
}
|
|
137
|
+
if (
|
|
138
|
+
!stored ||
|
|
139
|
+
stored.bytes.byteLength !== file.sizeBytes ||
|
|
140
|
+
createHash("sha256").update(stored.bytes).digest("hex") !== file.sha256
|
|
141
|
+
) {
|
|
142
|
+
throw new HTTPException(409, {
|
|
143
|
+
message: "Retained file content does not match its immutable identity",
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
const filename = file.safeFilename.slice(0, 200);
|
|
147
|
+
if (!filename || /[\r\n\0]/.test(filename))
|
|
148
|
+
throw new HTTPException(422, { message: "Invalid retained filename" });
|
|
149
|
+
const binding = {
|
|
150
|
+
...scope,
|
|
151
|
+
sessionId: input.sessionId,
|
|
152
|
+
interactionId: target.interaction.id,
|
|
153
|
+
connectionId: target.interaction.connectionId,
|
|
154
|
+
fileId: input.fileId,
|
|
155
|
+
subjectId: actor.initiatingHumanSubjectId ?? grant.subjectId,
|
|
156
|
+
operationId: input.operationId,
|
|
157
|
+
requestDigest: createHash("sha256")
|
|
158
|
+
.update(
|
|
159
|
+
JSON.stringify({
|
|
160
|
+
fileId: file.id,
|
|
161
|
+
sha256: file.sha256,
|
|
162
|
+
sizeBytes: file.sizeBytes,
|
|
163
|
+
contentType: file.contentType,
|
|
164
|
+
filename,
|
|
165
|
+
channelId: target.interaction.slackChannelId,
|
|
166
|
+
threadTimestamp: target.interaction.slackThreadTs,
|
|
167
|
+
connectionId: target.interaction.connectionId,
|
|
168
|
+
slackTeamId: target.interaction.slackTeamId,
|
|
169
|
+
}),
|
|
170
|
+
)
|
|
171
|
+
.digest("hex"),
|
|
172
|
+
};
|
|
173
|
+
const claimHolderId = crypto.randomUUID();
|
|
174
|
+
const claim = await claimSlackFileUpload(deps.db, {
|
|
175
|
+
...binding,
|
|
176
|
+
claimHolderId,
|
|
177
|
+
leaseMs: CLAIM_LEASE_MS,
|
|
178
|
+
});
|
|
179
|
+
if (claim.status === "conflict")
|
|
180
|
+
throw new HTTPException(409, {
|
|
181
|
+
message: "operationId is already bound to a different Slack file delivery",
|
|
182
|
+
});
|
|
183
|
+
if (claim.status === "busy")
|
|
184
|
+
throw new HTTPException(409, {
|
|
185
|
+
message: "Slack file delivery is already in progress; retry with the same operationId",
|
|
186
|
+
});
|
|
187
|
+
const claimScope = {
|
|
188
|
+
...scope,
|
|
189
|
+
sessionId: input.sessionId,
|
|
190
|
+
operationId: input.operationId,
|
|
191
|
+
claimHolderId,
|
|
192
|
+
};
|
|
193
|
+
const authorizeProviderRequest = async () => {
|
|
194
|
+
await input.authorize();
|
|
195
|
+
await fileOwnerContextForAgent(deps, grant, "files:read");
|
|
196
|
+
const currentTarget = await resolveTarget();
|
|
197
|
+
if (
|
|
198
|
+
currentTarget.interaction.id !== target.interaction.id ||
|
|
199
|
+
currentTarget.interaction.connectionId !== target.interaction.connectionId ||
|
|
200
|
+
currentTarget.interaction.slackChannelId !== target.interaction.slackChannelId ||
|
|
201
|
+
currentTarget.interaction.slackThreadTs !== target.interaction.slackThreadTs ||
|
|
202
|
+
currentTarget.interaction.visibility !== target.interaction.visibility ||
|
|
203
|
+
currentTarget.home.workspaceId !== target.home.workspaceId
|
|
204
|
+
)
|
|
205
|
+
throw new HTTPException(403, { message: "Slack file delivery target changed" });
|
|
206
|
+
const currentFile = await requireFileForSubject(deps.db, {
|
|
207
|
+
...scope,
|
|
208
|
+
subjectId: grant.subjectId,
|
|
209
|
+
fileId: file.id,
|
|
210
|
+
});
|
|
211
|
+
assertSlackTaskUploadFile(currentFile, currentTarget.interaction.visibility);
|
|
212
|
+
if (
|
|
213
|
+
currentFile.sha256 !== file.sha256 ||
|
|
214
|
+
currentFile.sizeBytes !== file.sizeBytes ||
|
|
215
|
+
currentFile.objectKey !== file.objectKey
|
|
216
|
+
) {
|
|
217
|
+
throw new HTTPException(409, { message: "Slack file delivery source changed" });
|
|
218
|
+
}
|
|
219
|
+
if (!(await renewSlackFileUploadClaim(deps.db, { ...claimScope, leaseMs: CLAIM_LEASE_MS })))
|
|
220
|
+
throw new SlackFileUploadClaimLostError();
|
|
221
|
+
};
|
|
222
|
+
try {
|
|
223
|
+
const client = await createOpenGeniSlackBotInteractionClient(
|
|
224
|
+
{ ...deps, authorizeProviderRequest },
|
|
225
|
+
{
|
|
226
|
+
accountId: target.home.accountId,
|
|
227
|
+
workspaceId: target.home.workspaceId,
|
|
228
|
+
connectionId: target.interaction.connectionId,
|
|
229
|
+
subjectId: grant.subjectId,
|
|
230
|
+
sessionId: input.sessionId,
|
|
231
|
+
},
|
|
232
|
+
);
|
|
233
|
+
const result = await runSlackFileUpload({
|
|
234
|
+
state: claim.operation,
|
|
235
|
+
provider: client,
|
|
236
|
+
checkpoint: (update) => checkpointSlackFileUpload(deps.db, { ...claimScope, ...update }),
|
|
237
|
+
channelId: target.interaction.slackChannelId,
|
|
238
|
+
threadTimestamp: target.interaction.slackThreadTs!,
|
|
239
|
+
...(target.interaction.visibility === "private"
|
|
240
|
+
? { privateRecipientSlackUserId: target.interaction.initiatingSlackUserId! }
|
|
241
|
+
: {}),
|
|
242
|
+
filename,
|
|
243
|
+
bytes: stored.bytes,
|
|
244
|
+
});
|
|
245
|
+
await recordAuditEvent(deps.db, {
|
|
246
|
+
...scope,
|
|
247
|
+
subjectId: grant.subjectId,
|
|
248
|
+
action: "slack.file.upload",
|
|
249
|
+
targetType: "workspace_file",
|
|
250
|
+
targetId: file.id,
|
|
251
|
+
metadata: {
|
|
252
|
+
operationId: input.operationId,
|
|
253
|
+
sessionId: input.sessionId,
|
|
254
|
+
interactionId: target.interaction.id,
|
|
255
|
+
slackFileId: result.slackFileId,
|
|
256
|
+
replayed: result.replayed,
|
|
257
|
+
},
|
|
258
|
+
});
|
|
259
|
+
return {
|
|
260
|
+
status: "uploaded" as const,
|
|
261
|
+
fileId: file.id,
|
|
262
|
+
...result,
|
|
263
|
+
channelId: target.interaction.slackChannelId,
|
|
264
|
+
threadTimestamp: target.interaction.slackThreadTs,
|
|
265
|
+
receipt: {
|
|
266
|
+
credentialRole: OPENGENI_SLACK_BOT_CREDENTIAL_ROLE,
|
|
267
|
+
credentialLabel: OPENGENI_SLACK_BOT_CREDENTIAL_LABEL,
|
|
268
|
+
connectionId: target.interaction.connectionId,
|
|
269
|
+
slackTeamId: target.interaction.slackTeamId,
|
|
270
|
+
operation: "file.upload",
|
|
271
|
+
operationId: input.operationId,
|
|
272
|
+
},
|
|
273
|
+
};
|
|
274
|
+
} finally {
|
|
275
|
+
if (claim.status === "claimed") await releaseSlackFileUploadClaim(deps.db, claimScope);
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export function slackTaskRequesterCanReceiveFile(permissions: AccessGrant["permissions"]): boolean {
|
|
281
|
+
return hasPermission(permissions, "files:read") && hasPermission(permissions, "sessions:read");
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/** Private shortcuts bind a runnable session before its bot-DM route commits. */
|
|
285
|
+
export function assertSlackTaskUploadTarget(
|
|
286
|
+
interaction: Pick<
|
|
287
|
+
SlackInteraction,
|
|
288
|
+
"visibility" | "slackChannelId" | "slackThreadTs" | "routeKey" | "ackSlackMessageTs"
|
|
289
|
+
>,
|
|
290
|
+
): void {
|
|
291
|
+
if (
|
|
292
|
+
interaction.visibility === "private" &&
|
|
293
|
+
(!interaction.slackChannelId.startsWith("D") ||
|
|
294
|
+
interaction.routeKey !== `${interaction.slackChannelId}:${interaction.slackThreadTs}` ||
|
|
295
|
+
(interaction.ackSlackMessageTs !== null &&
|
|
296
|
+
interaction.ackSlackMessageTs !== interaction.slackThreadTs))
|
|
297
|
+
) {
|
|
298
|
+
throw new HTTPException(409, {
|
|
299
|
+
message: "Private Slack file delivery must wait for this task's committed bot-DM thread",
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export function assertSlackTaskUploadFile(
|
|
305
|
+
file: { status: string; scope?: string | undefined; sizeBytes: number; sha256: string | null },
|
|
306
|
+
visibility: string,
|
|
307
|
+
): void {
|
|
308
|
+
if (file.status !== "ready" || !file.sha256 || !/^[a-f0-9]{64}$/.test(file.sha256)) {
|
|
309
|
+
throw new HTTPException(422, {
|
|
310
|
+
message: "Publish a ready, immutable file before uploading it to Slack",
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
if (file.scope === "personal" && visibility !== "private") {
|
|
314
|
+
throw new HTTPException(403, {
|
|
315
|
+
message: "Personal files cannot be uploaded to a shared Slack task thread",
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
if (file.sizeBytes < 1 || file.sizeBytes > SLACK_TASK_FILE_UPLOAD_MAX_BYTES) {
|
|
319
|
+
throw new HTTPException(413, {
|
|
320
|
+
message: "Slack task file delivery supports nonempty files up to 25 MiB",
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
}
|