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