@app-vitals/agent-admin 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Dockerfile +43 -0
- package/README.md +72 -2
- package/e2e/agents-page.e2e.ts +338 -0
- package/e2e/chat-degraded.e2e.ts +153 -0
- package/e2e/chat-page.e2e.ts +169 -0
- package/e2e/chat-progress.e2e.ts +206 -0
- package/e2e/chat-thread-page.e2e.ts +444 -0
- package/e2e/login-page.e2e.ts +148 -0
- package/e2e/test-server.ts +427 -0
- package/openapi.json +6098 -0
- package/openapi.platform.json +5553 -0
- package/package.json +62 -4
- package/playwright.config.ts +25 -0
- package/prisma/client/browser.ts +134 -0
- package/prisma/client/client.ts +158 -0
- package/prisma/client/commonInputTypes.ts +578 -0
- package/prisma/client/enums.ts +15 -0
- package/prisma/client/internal/class.ts +424 -0
- package/prisma/client/internal/prismaNamespace.ts +2898 -0
- package/prisma/client/internal/prismaNamespaceBrowser.ts +476 -0
- package/prisma/client/models/Account.ts +1774 -0
- package/prisma/client/models/AccountInvite.ts +1386 -0
- package/prisma/client/models/AccountMember.ts +1351 -0
- package/prisma/client/models/Agent.ts +4554 -0
- package/prisma/client/models/AgentAsk.ts +1373 -0
- package/prisma/client/models/AgentChatTokenUsageDailyByModel.ts +1638 -0
- package/prisma/client/models/AgentCronJob.ts +2416 -0
- package/prisma/client/models/AgentCronRun.ts +2858 -0
- package/prisma/client/models/AgentCronRunModelBreakdown.ts +1532 -0
- package/prisma/client/models/AgentCronRunSkillUsage.ts +1642 -0
- package/prisma/client/models/AgentEnv.ts +1421 -0
- package/prisma/client/models/AgentGitHubInstallationsSnapshot.ts +1289 -0
- package/prisma/client/models/AgentMember.ts +1316 -0
- package/prisma/client/models/AgentPhaseMethodology.ts +1355 -0
- package/prisma/client/models/AgentPlugin.ts +1421 -0
- package/prisma/client/models/AgentToken.ts +1380 -0
- package/prisma/client/models/AgentTool.ts +1351 -0
- package/prisma/client/models/AgentWorkQueueSnapshot.ts +1297 -0
- package/prisma/client/models/ChatThreadWatch.ts +1183 -0
- package/prisma/client/models/PushSubscription.ts +1233 -0
- package/prisma/client/models/SessionAlertState.ts +1187 -0
- package/prisma/client/models/SessionFollow.ts +1187 -0
- package/prisma/client/models/UserNotificationPrefs.ts +1221 -0
- package/prisma/client/models.ts +34 -0
- package/prisma/migrations/20260608000000_init/migration.sql +112 -0
- package/prisma/migrations/20260616000000_agent_members/migration.sql +18 -0
- package/prisma/migrations/20260624000000_add_agent_self_hosted/migration.sql +2 -0
- package/prisma/migrations/20260624120000_add_agent_repos/migration.sql +3 -0
- package/prisma/migrations/20260625000000_add_agent_cron_run/migration.sql +26 -0
- package/prisma/migrations/20260626000000_add_cron_run_token_fields/migration.sql +10 -0
- package/prisma/migrations/20260627000000_add_agent_chat_token_usage_daily/migration.sql +18 -0
- package/prisma/migrations/20260627010000_add_agent_chat_token_usage_fk/migration.sql +2 -0
- package/prisma/migrations/20260701000000_add_chat_threads_messages/migration.sql +37 -0
- package/prisma/migrations/20260701010000_drop_chat_threads_messages/migration.sql +17 -0
- package/prisma/migrations/20260701020000_add_cron_run_model_breakdown/migration.sql +23 -0
- package/prisma/migrations/20260701020000_chat_token_by_model/migration.sql +25 -0
- package/prisma/migrations/20260701030000_add_secret_flag_to_agent_env/migration.sql +2 -0
- package/prisma/migrations/20260701030000_drop_cron_run_legacy_columns/migration.sql +5 -0
- package/prisma/migrations/20260706000000_consolidate_cron_run_token_accounting/migration.sql +54 -0
- package/prisma/migrations/20260709000000_add_phase_to_agent_cron_run/migration.sql +8 -0
- package/prisma/migrations/20260717000000_add_item_tracking_to_agent_cron_run/migration.sql +10 -0
- package/prisma/migrations/20260717010000_add_agent_work_queue_snapshot/migration.sql +20 -0
- package/prisma/migrations/20260718000000_add_cron_phase_columns/migration.sql +25 -0
- package/prisma/migrations/20260718100000_drop_agent_cron_run_phase_column/migration.sql +8 -0
- package/prisma/migrations/20260724000000_add_agent_type_name/migration.sql +3 -0
- package/prisma/migrations/20260725232433_add_agent_author_allowlist/migration.sql +2 -0
- package/prisma/migrations/20260727120000_add_not_null_to_agent_author_allowlist/migration.sql +3 -0
- package/prisma/migrations/20260731000000_add_session_id_to_agent_cron_run/migration.sql +2 -0
- package/prisma/migrations/20260821000000_add_agent_restrict_slack_to_members/migration.sql +3 -0
- package/prisma/migrations/20260901000000_add_push_subscription_and_chat_thread_watch/migration.sql +40 -0
- package/prisma/migrations/20260903000000_add_agent_patch_author_allowlist/migration.sql +2 -0
- package/prisma/migrations/20260903120000_add_agent_review_author_allowlist/migration.sql +18 -0
- package/prisma/migrations/20260903150000_drop_agent_author_allowlist/migration.sql +14 -0
- package/prisma/migrations/20260905000000_add_started_at_index_to_agent_cron_run/migration.sql +11 -0
- package/prisma/migrations/20260906000000_add_session_follow_notification_prefs_alert_state/migration.sql +47 -0
- package/prisma/migrations/20260911000000_backfill_auto_follow_since/migration.sql +16 -0
- package/prisma/migrations/20260923115814_add_last_heartbeat_at_to_agent_cron_run/migration.sql +2 -0
- package/prisma/migrations/20260924120000_add_trial_expiry_fields_to_agent/migration.sql +6 -0
- package/prisma/migrations/20260929000000_add_agent_phase_methodology/migration.sql +21 -0
- package/prisma/migrations/20260929120000_add_agent_policy_fields/migration.sql +15 -0
- package/prisma/migrations/20261005000000_add_agent_github_installations_snapshot/migration.sql +21 -0
- package/prisma/migrations/20261008000000_add_cron_run_telemetry/migration.sql +50 -0
- package/prisma/migrations/20261008010000_add_account_models/migration.sql +48 -0
- package/prisma/migrations/20261008020000_add_cron_lockdown_disabled_at/migration.sql +3 -0
- package/prisma/migrations/20261009000000_chat_token_usage_bigint/migration.sql +7 -0
- package/prisma/migrations/20261010000000_add_cron_run_command_variant/migration.sql +2 -0
- package/prisma/migrations/20261019000000_add_agent_anthropic_identity/migration.sql +9 -0
- package/prisma/migrations/20261025000000_add_agent_ask/migration.sql +23 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema/coding.prisma +85 -0
- package/prisma/schema/schema.prisma +506 -0
- package/prisma.config.ts +34 -0
- package/pwa-assets/icons/apple-touch-icon.png +0 -0
- package/pwa-assets/icons/favicon-32.png +0 -0
- package/pwa-assets/icons/icon-192.png +0 -0
- package/pwa-assets/icons/icon-512.png +0 -0
- package/pwa-assets/icons/icon-maskable-192.png +0 -0
- package/pwa-assets/icons/icon-maskable-512.png +0 -0
- package/src/account-agent-create.ts +42 -0
- package/src/account-agent-create.unit.test.ts +28 -0
- package/src/account-created-notifier.ts +71 -0
- package/src/account-created-notifier.unit.test.ts +70 -0
- package/src/account-email.ts +9 -0
- package/src/account-email.unit.test.ts +21 -0
- package/src/account-invites.ts +70 -0
- package/src/account-lifecycle.integration.test.ts +329 -0
- package/src/account-lifecycle.ts +206 -0
- package/src/account-lifecycle.unit.test.ts +108 -0
- package/src/account-members.ts +128 -0
- package/src/account-onboarding.integration.test.ts +101 -0
- package/src/account-onboarding.ts +95 -0
- package/src/account-trial-expiry-sweeper.ts +85 -0
- package/src/account-trial-warning-sweeper.integration.test.ts +228 -0
- package/src/account-trial-warning-sweeper.ts +216 -0
- package/src/accounts-api.integration.test.ts +196 -0
- package/src/accounts-api.smoke.test.ts +292 -0
- package/src/accounts-api.ts +289 -0
- package/src/accounts-api.unit.test.ts +93 -0
- package/src/accounts.integration.test.ts +178 -0
- package/src/accounts.ts +208 -0
- package/src/admin-boot-config.ts +574 -0
- package/src/admin-boot-config.unit.test.ts +28 -0
- package/src/admin-boot.boot-order.unit.test.ts +91 -0
- package/src/admin-boot.ts +690 -0
- package/src/admin-dev-login.smoke.test.ts +317 -0
- package/src/admin-extension.ts +432 -0
- package/src/admin-extension.unit.test.ts +532 -0
- package/src/admin-local-agent.smoke.test.ts +1643 -0
- package/src/admin-spec.smoke.test.ts +96 -0
- package/src/admin-spec.ts +186 -0
- package/src/admin-ui-account-nav.ts +20 -0
- package/src/admin-ui-account-pages.ts +131 -0
- package/src/admin-ui-account-pages.unit.test.ts +112 -0
- package/src/admin-ui-account.smoke.test.ts +286 -0
- package/src/admin-ui-account.ts +185 -0
- package/src/admin-ui-accounts-pages.ts +202 -0
- package/src/admin-ui-accounts-pages.unit.test.ts +138 -0
- package/src/admin-ui-accounts.smoke.test.ts +434 -0
- package/src/admin-ui-accounts.ts +266 -0
- package/src/admin-ui-layout.ts +60 -0
- package/src/admin-ui-layout.unit.test.ts +140 -0
- package/src/admin-ui-pages.ts +3489 -0
- package/src/admin-ui-pages.unit.test.ts +4355 -0
- package/src/admin-ui-push-test.smoke.test.ts +125 -0
- package/src/admin-ui-push-test.ts +102 -0
- package/src/admin-ui-styles.ts +656 -0
- package/src/admin-ui-styles.unit.test.ts +103 -0
- package/src/admin-ui.fixtures.ts +457 -0
- package/src/admin-ui.smoke.test.ts +7227 -0
- package/src/admin-ui.ts +3824 -0
- package/src/agent-account-assignment.ts +49 -0
- package/src/agent-account-assignment.unit.test.ts +99 -0
- package/src/agent-anthropic-identity.integration.test.ts +99 -0
- package/src/agent-anthropic-identity.ts +78 -0
- package/src/agent-anthropic-identity.unit.test.ts +77 -0
- package/src/agent-ask-config.ts +59 -0
- package/src/agent-ask-config.unit.test.ts +70 -0
- package/src/agent-ask.integration.test.ts +175 -0
- package/src/agent-ask.smoke.test.ts +457 -0
- package/src/agent-ask.ts +378 -0
- package/src/agent-chat-tokens.integration.test.ts +420 -0
- package/src/agent-chat-tokens.smoke.test.ts +551 -0
- package/src/agent-chat-tokens.ts +259 -0
- package/src/agent-cron-jobs.integration.test.ts +582 -0
- package/src/agent-cron-jobs.ts +615 -0
- package/src/agent-cron-jobs.unit.test.ts +420 -0
- package/src/agent-cron-run-stats.integration.test.ts +1551 -0
- package/src/agent-cron-run-stats.smoke.test.ts +953 -0
- package/src/agent-cron-run-stats.ts +752 -0
- package/src/agent-cron-runs.integration.test.ts +1491 -0
- package/src/agent-cron-runs.ts +489 -0
- package/src/agent-deletion-checklist.ts +128 -0
- package/src/agent-deletion-checklist.unit.test.ts +201 -0
- package/src/agent-deletion-per-agent.integration.test.ts +323 -0
- package/src/agent-deletion.integration.test.ts +323 -0
- package/src/agent-deletion.ts +329 -0
- package/src/agent-deletion.unit.test.ts +412 -0
- package/src/agent-envs.integration.test.ts +323 -0
- package/src/agent-envs.ts +244 -0
- package/src/agent-github-installations.integration.test.ts +115 -0
- package/src/agent-github-installations.ts +52 -0
- package/src/agent-manifest.ts +685 -0
- package/src/agent-manifest.unit.test.ts +870 -0
- package/src/agent-members.ts +69 -0
- package/src/agent-members.unit.test.ts +250 -0
- package/src/agent-patch-author-allowlist.integration.test.ts +218 -0
- package/src/agent-plugins.integration.test.ts +157 -0
- package/src/agent-plugins.ts +95 -0
- package/src/agent-plugins.unit.test.ts +43 -0
- package/src/agent-provisioner-per-agent.integration.test.ts +949 -0
- package/src/agent-provisioner.integration.test.ts +1245 -0
- package/src/agent-provisioner.ts +1650 -0
- package/src/agent-provisioner.unit.test.ts +1852 -0
- package/src/agent-tokens.integration.test.ts +150 -0
- package/src/agent-tokens.ts +112 -0
- package/src/agent-tools.integration.test.ts +129 -0
- package/src/agent-tools.ts +111 -0
- package/src/agents-api.integration.test.ts +705 -0
- package/src/agents-api.sentry.smoke.test.ts +478 -0
- package/src/agents-api.smoke.test.ts +5986 -0
- package/src/agents-api.ts +2826 -0
- package/src/agents.integration.test.ts +512 -0
- package/src/agents.ts +1097 -0
- package/src/agents.unit.test.ts +1736 -0
- package/src/anthropic-credential-guard.ts +44 -0
- package/src/anthropic-credential-guard.unit.test.ts +88 -0
- package/src/anthropic-federated-token-provider.integration.test.ts +248 -0
- package/src/anthropic-federated-token-provider.ts +249 -0
- package/src/anthropic-identity-client.fixtures.ts +237 -0
- package/src/anthropic-identity-client.integration.test.ts +342 -0
- package/src/anthropic-identity-client.ts +473 -0
- package/src/anthropic-identity-fixture-spec.integration.test.ts +169 -0
- package/src/anthropic-identity-name.integration.test.ts +83 -0
- package/src/anthropic-identity-name.ts +163 -0
- package/src/anthropic-identity-name.unit.test.ts +345 -0
- package/src/anthropic-openapi-contract.fixtures.ts +192 -0
- package/src/api-auth.ts +383 -0
- package/src/api-auth.unit.test.ts +837 -0
- package/src/api.smoke.test.ts +1225 -0
- package/src/api.ts +376 -0
- package/src/attachment-validation.ts +124 -0
- package/src/attachment-validation.unit.test.ts +218 -0
- package/src/caller-scope.ts +131 -0
- package/src/caller-scope.unit.test.ts +116 -0
- package/src/chat-credential-config.ts +22 -0
- package/src/chat-credentials-logging.unit.test.ts +92 -0
- package/src/chat-markers.ts +114 -0
- package/src/chat-markers.unit.test.ts +116 -0
- package/src/chat-service-provisioning-client.integration.test.ts +412 -0
- package/src/chat-service-provisioning-client.ts +221 -0
- package/src/chat.smoke.test.ts +1875 -0
- package/src/clock.ts +47 -0
- package/src/coding-task-store-provisioning-client.integration.test.ts +372 -0
- package/src/coding-task-store-provisioning-client.ts +156 -0
- package/src/crypto.ts +83 -0
- package/src/dev-auth-guard.ts +29 -0
- package/src/dev-auth-guard.unit.test.ts +51 -0
- package/src/errors.ts +77 -0
- package/src/errors.unit.test.ts +52 -0
- package/src/fixtures/anthropic-identity-recorded-fixtures.json +850 -0
- package/src/fixtures/anthropic-openapi/SDK_VERSION +1 -0
- package/src/fixtures/anthropic-openapi/mock-spec.json.gz +0 -0
- package/src/fixtures/chat-service-provisioning-cassette.json +156 -0
- package/src/fixtures/github-app-manifest-cassette.json +44 -0
- package/src/fixtures/google-auth-cassette.json +36 -0
- package/src/fixtures/http-chat-client-cassette.json +226 -0
- package/src/fixtures/k8s-cassette.json +470 -0
- package/src/fixtures/okta-auth-cassette.json +36 -0
- package/src/fixtures/slack-provision-cassette.json +12 -0
- package/src/fixtures/slack-provisioning-http-cassette.json +98 -0
- package/src/fixtures/task-store-provisioning-cassette.json +85 -0
- package/src/github-app-provisioning-client.integration.test.ts +149 -0
- package/src/github-app-provisioning-client.ts +161 -0
- package/src/github-app-provisioning-client.unit.test.ts +114 -0
- package/src/github-provisioning-service.ts +569 -0
- package/src/github-provisioning-service.unit.test.ts +123 -0
- package/src/google-auth-client.integration.test.ts +195 -0
- package/src/google-auth-client.ts +121 -0
- package/src/http-chat-client.integration.test.ts +554 -0
- package/src/http-chat-client.ts +533 -0
- package/src/http-chat-client.unit.test.ts +119 -0
- package/src/import-boundary.unit.test.ts +240 -0
- package/src/kubernetes-client.integration.test.ts +697 -0
- package/src/kubernetes-client.ts +1027 -0
- package/src/kubernetes-client.unit.test.ts +726 -0
- package/src/main.smoke.test.ts +91 -0
- package/src/main.ts +21 -0
- package/src/main.unit.test.ts +801 -0
- package/src/okta-auth-client.integration.test.ts +263 -0
- package/src/okta-auth-client.ts +152 -0
- package/src/openapi-schemas.ts +1310 -0
- package/src/openapi-schemas.unit.test.ts +348 -0
- package/src/per-agent-identity.fixtures.ts +305 -0
- package/src/platform-doc-route.ts +40 -0
- package/src/prisma-client.integration.test.ts +57 -0
- package/src/prisma-client.ts +187 -0
- package/src/prisma-client.unit.test.ts +178 -0
- package/src/prisma-tx.ts +23 -0
- package/src/push-content.ts +177 -0
- package/src/push-content.unit.test.ts +213 -0
- package/src/push-crypto.ts +135 -0
- package/src/push-crypto.unit.test.ts +66 -0
- package/src/push-sender.ts +169 -0
- package/src/push-sender.unit.test.ts +107 -0
- package/src/push-service.ts +332 -0
- package/src/push-service.unit.test.ts +679 -0
- package/src/push-toggle.ts +135 -0
- package/src/push-toggle.unit.test.ts +78 -0
- package/src/pwa-https-gating.smoke.test.ts +229 -0
- package/src/pwa-icons.unit.test.ts +41 -0
- package/src/pwa-routes.smoke.test.ts +276 -0
- package/src/pwa.ts +380 -0
- package/src/pwa.unit.test.ts +321 -0
- package/src/schema.unit.test.ts +122 -0
- package/src/self-serve-config.ts +52 -0
- package/src/self-serve-config.unit.test.ts +79 -0
- package/src/server.smoke.test.ts +273 -0
- package/src/server.ts +50 -0
- package/src/service-integration.ts +67 -0
- package/src/service-integration.unit.test.ts +129 -0
- package/src/slack-provisioning-client.integration.test.ts +308 -0
- package/src/slack-provisioning-client.ts +411 -0
- package/src/slack-provisioning-client.unit.test.ts +80 -0
- package/src/slack-provisioning-service.integration.test.ts +637 -0
- package/src/slack-provisioning-service.ts +451 -0
- package/src/slack-provisioning.integration.test.ts +395 -0
- package/src/specialty-manifest-loader.fixture.integration.test.ts +63 -0
- package/src/specialty-manifest-loader.ts +255 -0
- package/src/specialty-manifest-loader.unit.test.ts +301 -0
- package/src/specialty-registry.schema.unit.test.ts +35 -0
- package/src/specialty-registry.ts +236 -0
- package/src/specialty-registry.unit.test.ts +272 -0
- package/src/startup-policy-reconcile.ts +77 -0
- package/src/startup-policy-reconcile.unit.test.ts +251 -0
- package/src/theme-tokens.json +12 -0
- package/src/token-crypto.ts +60 -0
- package/src/token-crypto.unit.test.ts +182 -0
- package/src/trial-expiry-sweeper.integration.test.ts +318 -0
- package/src/trial-expiry-sweeper.ts +132 -0
- package/src/trial-expiry-sweeper.unit.test.ts +391 -0
- package/src/trial-expiry-warning-sweeper.integration.test.ts +468 -0
- package/src/trial-expiry-warning-sweeper.ts +447 -0
- package/src/trial-expiry-warning-sweeper.unit.test.ts +148 -0
- package/src/voice-contract.unit.test.ts +102 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* admin/src/slack-provisioning-service.ts
|
|
3
|
+
* SlackProvisioningService — Slack app-manifest-creation/OAuth orchestration
|
|
4
|
+
* for connecting an already-existing agent to Slack.
|
|
5
|
+
*
|
|
6
|
+
* Extracted out of admin-ui.ts's inline /admin/provision/* handlers (UAP-1.1)
|
|
7
|
+
* so the same logic can be reused by both the legacy provisioning wizard
|
|
8
|
+
* (POST /admin/provision/start Slack branch, GET /admin/provision/complete,
|
|
9
|
+
* POST /admin/provision/xapp-token) and the new per-agent connect-slack
|
|
10
|
+
* routes (POST /admin/agents/:id/connect-slack, GET .../callback, POST
|
|
11
|
+
* .../app-token). Pure extraction — no behavior change.
|
|
12
|
+
*
|
|
13
|
+
* HTTP-specific mechanics (reading/writing the PROVISION_STATE_COOKIE,
|
|
14
|
+
* issuing redirects, rendering HTML) stay in the route handlers; this service
|
|
15
|
+
* owns the Slack API calls, the signed provision-state token payload, and the
|
|
16
|
+
* env/cron side effects.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { sign, verify } from "hono/jwt";
|
|
20
|
+
import type { AdminUISlackClient } from "./admin-ui.ts";
|
|
21
|
+
import type { AgentCronJobService } from "./agent-cron-jobs.ts";
|
|
22
|
+
import type { AgentEnvService } from "./agent-envs.ts";
|
|
23
|
+
import type { AgentService } from "./agents.ts";
|
|
24
|
+
import { buildAgentManifest } from "./slack-provisioning-client.ts";
|
|
25
|
+
|
|
26
|
+
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
/** Shared with admin-ui.ts — the signed cookie carrying provision state across the Slack OAuth redirect. */
|
|
29
|
+
export const PROVISION_STATE_COOKIE = "slack_provision_state";
|
|
30
|
+
export const PROVISION_STATE_TTL_SECONDS = 300; // 5 min
|
|
31
|
+
|
|
32
|
+
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
interface ProvisionStatePayload {
|
|
35
|
+
agentId: string;
|
|
36
|
+
clientId: string;
|
|
37
|
+
clientSecret: string;
|
|
38
|
+
signingSecret: string;
|
|
39
|
+
appId: string;
|
|
40
|
+
/**
|
|
41
|
+
* The OAuth `redirect_uri` registered in the app manifest for this
|
|
42
|
+
* provisioning attempt. Persisted so completeConnect() replays the exact
|
|
43
|
+
* same value to `oauth.v2.access` (Slack requires the exchange's
|
|
44
|
+
* `redirect_uri` to match a registered one). Optional for backwards
|
|
45
|
+
* compatibility with any cookie signed before this field existed, in which
|
|
46
|
+
* case the caller-supplied fallback is used.
|
|
47
|
+
*/
|
|
48
|
+
redirectUri?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
type StartConnectResult =
|
|
52
|
+
| {
|
|
53
|
+
ok: true;
|
|
54
|
+
/** Signed JWT to store in the PROVISION_STATE_COOKIE. */
|
|
55
|
+
provisionStateToken: string;
|
|
56
|
+
/** Slack OAuth v2 authorize URL to redirect the operator to. */
|
|
57
|
+
oauthRedirectUrl: string;
|
|
58
|
+
}
|
|
59
|
+
| { ok: false; error: string };
|
|
60
|
+
|
|
61
|
+
export type CompleteConnectResult =
|
|
62
|
+
| {
|
|
63
|
+
/** Provision-state cookie missing or invalid/expired — restart the flow. */
|
|
64
|
+
outcome: "invalid_state";
|
|
65
|
+
error: string;
|
|
66
|
+
}
|
|
67
|
+
| {
|
|
68
|
+
/** Valid state but no OAuth code param (e.g. user denied consent). Cookie must NOT be cleared by the caller in this case. */
|
|
69
|
+
outcome: "missing_code";
|
|
70
|
+
error: string;
|
|
71
|
+
}
|
|
72
|
+
| {
|
|
73
|
+
/** OAuth code exchange with Slack failed. */
|
|
74
|
+
outcome: "exchange_failed";
|
|
75
|
+
error: string;
|
|
76
|
+
}
|
|
77
|
+
| {
|
|
78
|
+
/** Success, and this is a reinstall (SLACK_APP_TOKEN was already set) — redirect straight to the agent page. */
|
|
79
|
+
outcome: "reinstalled";
|
|
80
|
+
agentId: string;
|
|
81
|
+
/**
|
|
82
|
+
* Set only for the combined create+connect flow (UAP-2.1): a non-fatal
|
|
83
|
+
* GitHub PAT storage failure that happened before this Slack flow began.
|
|
84
|
+
* The caller surfaces it as a warning on the landing page so the operator
|
|
85
|
+
* isn't misled into believing GH_TOKEN was stored.
|
|
86
|
+
*/
|
|
87
|
+
ghConnectError?: string;
|
|
88
|
+
}
|
|
89
|
+
| {
|
|
90
|
+
/** Success, fresh provisioning — show the xapp-token page next. */
|
|
91
|
+
outcome: "needs_app_token";
|
|
92
|
+
agentId: string;
|
|
93
|
+
/** See `reinstalled.ghConnectError` — same non-fatal combined-flow warning. */
|
|
94
|
+
ghConnectError?: string;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
type SaveAppTokenResult =
|
|
98
|
+
| { ok: true; agentId: string }
|
|
99
|
+
| { ok: false; agentId: string; error: string };
|
|
100
|
+
|
|
101
|
+
interface SlackAppManifestResult {
|
|
102
|
+
appId: string;
|
|
103
|
+
oauthRedirectUrl: string;
|
|
104
|
+
clientId: string;
|
|
105
|
+
clientSecret: string;
|
|
106
|
+
signingSecret: string;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
interface SlackProvisioningServiceDeps {
|
|
110
|
+
slackClient: AdminUISlackClient;
|
|
111
|
+
agentService: Pick<AgentService, "getDetail" | "updateFields">;
|
|
112
|
+
agentEnvService: Pick<AgentEnvService, "patch" | "getConfigBundle">;
|
|
113
|
+
agentCronJobService: Pick<AgentCronJobService, "reconcileSystemCrons">;
|
|
114
|
+
sessionSecret: string;
|
|
115
|
+
appBaseUrl: string;
|
|
116
|
+
/** Env var keys that must be stored as secrets — mirrors SECRET_ENV_VARS. */
|
|
117
|
+
secretEnvVars: Set<string>;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ─── Service ──────────────────────────────────────────────────────────────────
|
|
121
|
+
|
|
122
|
+
export class SlackProvisioningService {
|
|
123
|
+
constructor(private readonly deps: SlackProvisioningServiceDeps) {}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Calls apps.manifest.create for `agentName` and returns the raw Slack
|
|
127
|
+
* result (app id, OAuth authorize URL, and OAuth credentials). Low-level
|
|
128
|
+
* building block shared by startConnect() below and by the legacy POST
|
|
129
|
+
* /admin/provision/start wizard's Slack branch — the wizard needs the raw
|
|
130
|
+
* credentials to sign its own provision-state cookie (which additionally
|
|
131
|
+
* carries `githubOrg` for the GitHub-App-auto-provision sub-flow), so it
|
|
132
|
+
* calls this instead of startConnect().
|
|
133
|
+
*
|
|
134
|
+
* `redirectUri` is supplied by the caller and becomes the manifest's OAuth
|
|
135
|
+
* `redirect_urls` entry, so both the legacy wizard (passing
|
|
136
|
+
* `/admin/provision/complete`) and the new per-agent connect-slack route
|
|
137
|
+
* (passing `/admin/agents/:id/connect-slack/callback`) can reuse this same
|
|
138
|
+
* manifest-building logic while registering different OAuth callback URLs
|
|
139
|
+
* with Slack. Slack requires the `redirect_uri` sent to `oauth.v2.access`
|
|
140
|
+
* to match one of the app's registered `redirect_urls`, so the same value
|
|
141
|
+
* must be threaded through to completeConnect()'s exchange call below.
|
|
142
|
+
*/
|
|
143
|
+
async createAppManifest(
|
|
144
|
+
agentName: string,
|
|
145
|
+
xoxpToken: string,
|
|
146
|
+
redirectUri: string,
|
|
147
|
+
): Promise<SlackAppManifestResult> {
|
|
148
|
+
const manifest = buildAgentManifest(agentName, redirectUri);
|
|
149
|
+
return this.deps.slackClient.createAppManifest(xoxpToken, manifest);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Signs a provision-state token (for the PROVISION_STATE_COOKIE) carrying
|
|
154
|
+
* the given Slack OAuth credentials for `agentId`. Exposed so the legacy
|
|
155
|
+
* wizard can fold its own extra `githubOrg` field into the same signed
|
|
156
|
+
* payload shape that completeConnect() below verifies.
|
|
157
|
+
*/
|
|
158
|
+
async signProvisionState(
|
|
159
|
+
agentId: string,
|
|
160
|
+
creds: {
|
|
161
|
+
clientId: string;
|
|
162
|
+
clientSecret: string;
|
|
163
|
+
signingSecret: string;
|
|
164
|
+
appId: string;
|
|
165
|
+
/**
|
|
166
|
+
* The manifest's registered OAuth `redirect_uri` for this attempt,
|
|
167
|
+
* replayed verbatim to `oauth.v2.access` by completeConnect().
|
|
168
|
+
*/
|
|
169
|
+
redirectUri: string;
|
|
170
|
+
},
|
|
171
|
+
extra?: Record<string, unknown>,
|
|
172
|
+
): Promise<string> {
|
|
173
|
+
const now = Math.floor(Date.now() / 1000);
|
|
174
|
+
return sign(
|
|
175
|
+
{
|
|
176
|
+
agentId,
|
|
177
|
+
...creds,
|
|
178
|
+
iat: now,
|
|
179
|
+
exp: now + PROVISION_STATE_TTL_SECONDS,
|
|
180
|
+
...extra,
|
|
181
|
+
},
|
|
182
|
+
this.deps.sessionSecret,
|
|
183
|
+
"HS256",
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Creates the Slack app manifest for `agentId` via apps.manifest.create and
|
|
189
|
+
* returns a signed provision-state token (for the PROVISION_STATE_COOKIE)
|
|
190
|
+
* plus the Slack OAuth authorize URL to redirect to.
|
|
191
|
+
*
|
|
192
|
+
* Mirrors the Slack branch of the legacy POST /admin/provision/start
|
|
193
|
+
* handler (validation + apps.manifest.create + cookie payload), but for an
|
|
194
|
+
* agent that already exists — no agent-creation/rollback orchestration.
|
|
195
|
+
*
|
|
196
|
+
* `redirectUri` is threaded through to createAppManifest() (registered as
|
|
197
|
+
* the app's OAuth `redirect_urls`) and folded into the signed
|
|
198
|
+
* provision-state cookie so completeConnect() can replay the identical URL
|
|
199
|
+
* to `oauth.v2.access` — Slack rejects an exchange whose `redirect_uri`
|
|
200
|
+
* doesn't match a registered one. The per-agent connect-slack route passes
|
|
201
|
+
* its own `/admin/agents/:id/connect-slack/callback` here.
|
|
202
|
+
*
|
|
203
|
+
* `ghConnectError` (UAP-2.1) is an optional non-fatal GitHub PAT storage
|
|
204
|
+
* failure from the combined create+connect flow. When present it's folded
|
|
205
|
+
* into the signed provision-state cookie so it survives Slack's OAuth round
|
|
206
|
+
* trip and completeConnect() can hand it back to the caller to surface on the
|
|
207
|
+
* post-callback landing page — otherwise the failure would be silently
|
|
208
|
+
* swallowed and the operator misled into believing GH_TOKEN was stored.
|
|
209
|
+
*/
|
|
210
|
+
async startConnect(
|
|
211
|
+
agentId: string,
|
|
212
|
+
xoxpToken: string | undefined,
|
|
213
|
+
redirectUri: string,
|
|
214
|
+
ghConnectError?: string,
|
|
215
|
+
): Promise<StartConnectResult> {
|
|
216
|
+
if (!xoxpToken?.startsWith("xoxe.xoxp-")) {
|
|
217
|
+
return {
|
|
218
|
+
ok: false,
|
|
219
|
+
error: "Slack app configuration token must start with xoxe.xoxp-",
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const agent = await this.deps.agentService.getDetail(agentId);
|
|
224
|
+
if (!agent) {
|
|
225
|
+
return { ok: false, error: "Agent not found." };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
let slackResult: SlackAppManifestResult;
|
|
229
|
+
try {
|
|
230
|
+
slackResult = await this.createAppManifest(
|
|
231
|
+
agent.name,
|
|
232
|
+
xoxpToken,
|
|
233
|
+
redirectUri,
|
|
234
|
+
);
|
|
235
|
+
} catch (err) {
|
|
236
|
+
const msg =
|
|
237
|
+
err instanceof Error
|
|
238
|
+
? err.message
|
|
239
|
+
: "Unknown error creating Slack app.";
|
|
240
|
+
return { ok: false, error: msg };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const { appId, oauthRedirectUrl, clientId, clientSecret, signingSecret } =
|
|
244
|
+
slackResult;
|
|
245
|
+
|
|
246
|
+
const provisionStateToken = await this.signProvisionState(
|
|
247
|
+
agentId,
|
|
248
|
+
{
|
|
249
|
+
clientId,
|
|
250
|
+
clientSecret,
|
|
251
|
+
signingSecret,
|
|
252
|
+
appId,
|
|
253
|
+
redirectUri,
|
|
254
|
+
},
|
|
255
|
+
ghConnectError ? { ghConnectError } : undefined,
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
return { ok: true, provisionStateToken, oauthRedirectUrl };
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Verifies the provision-state token, exchanges the OAuth `code` for a bot
|
|
263
|
+
* token, and stores SLACK_BOT_TOKEN. Mirrors the legacy
|
|
264
|
+
* GET /admin/provision/complete handler exactly.
|
|
265
|
+
*
|
|
266
|
+
* The caller (route handler) is responsible for reading the raw cookie
|
|
267
|
+
* value and for clearing it — this method never has cookie access; it only
|
|
268
|
+
* decides whether the "invalid state" outcome should tell the caller to
|
|
269
|
+
* clear the cookie (yes, for invalid_state) or not (no, for missing_code,
|
|
270
|
+
* where the cookie must survive a retry).
|
|
271
|
+
*
|
|
272
|
+
* `fallbackRedirectUri` is used for the OAuth exchange only when the signed
|
|
273
|
+
* state cookie predates the `redirectUri` field (cookies signed before this
|
|
274
|
+
* fix). New cookies carry their own per-flow redirect URI, so callers pass
|
|
275
|
+
* their own flow's URL as the fallback to preserve behavior for in-flight
|
|
276
|
+
* legacy sessions.
|
|
277
|
+
*/
|
|
278
|
+
async completeConnect(
|
|
279
|
+
rawStateCookie: string | undefined,
|
|
280
|
+
code: string | undefined,
|
|
281
|
+
fallbackRedirectUri: string,
|
|
282
|
+
): Promise<CompleteConnectResult> {
|
|
283
|
+
if (!rawStateCookie) {
|
|
284
|
+
return {
|
|
285
|
+
outcome: "invalid_state",
|
|
286
|
+
error:
|
|
287
|
+
"Provision session expired or missing. Please start the provisioning flow again.",
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
let provisionState: ProvisionStatePayload;
|
|
292
|
+
// Non-fatal combined-flow warning (UAP-2.1), threaded through the signed
|
|
293
|
+
// cookie so it survives Slack's OAuth round trip. Declared out here so it
|
|
294
|
+
// escapes the verify() try block; kept separate from the
|
|
295
|
+
// ProvisionStatePayload shape since it's flow-specific, not Slack state.
|
|
296
|
+
let ghConnectError: string | undefined;
|
|
297
|
+
try {
|
|
298
|
+
const payload = (await verify(
|
|
299
|
+
rawStateCookie,
|
|
300
|
+
this.deps.sessionSecret,
|
|
301
|
+
"HS256",
|
|
302
|
+
)) as Record<string, unknown>;
|
|
303
|
+
if (
|
|
304
|
+
typeof payload.agentId !== "string" ||
|
|
305
|
+
typeof payload.clientId !== "string" ||
|
|
306
|
+
typeof payload.clientSecret !== "string" ||
|
|
307
|
+
typeof payload.signingSecret !== "string" ||
|
|
308
|
+
typeof payload.appId !== "string"
|
|
309
|
+
) {
|
|
310
|
+
throw new Error("invalid payload shape");
|
|
311
|
+
}
|
|
312
|
+
provisionState = {
|
|
313
|
+
agentId: payload.agentId,
|
|
314
|
+
clientId: payload.clientId,
|
|
315
|
+
clientSecret: payload.clientSecret,
|
|
316
|
+
signingSecret: payload.signingSecret,
|
|
317
|
+
appId: payload.appId,
|
|
318
|
+
redirectUri:
|
|
319
|
+
typeof payload.redirectUri === "string"
|
|
320
|
+
? payload.redirectUri
|
|
321
|
+
: undefined,
|
|
322
|
+
};
|
|
323
|
+
ghConnectError =
|
|
324
|
+
typeof payload.ghConnectError === "string"
|
|
325
|
+
? payload.ghConnectError
|
|
326
|
+
: undefined;
|
|
327
|
+
} catch {
|
|
328
|
+
return {
|
|
329
|
+
outcome: "invalid_state",
|
|
330
|
+
error:
|
|
331
|
+
"Provision session expired or invalid. Please start the provisioning flow again.",
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (!code) {
|
|
336
|
+
return {
|
|
337
|
+
outcome: "missing_code",
|
|
338
|
+
error:
|
|
339
|
+
"Authorization was not completed (no OAuth code received). Please restart the provisioning flow from the beginning.",
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
let botToken: string;
|
|
344
|
+
let slackUserId: string;
|
|
345
|
+
try {
|
|
346
|
+
const result = await this.deps.slackClient.exchangeOAuthCode(
|
|
347
|
+
code,
|
|
348
|
+
provisionState.clientId,
|
|
349
|
+
provisionState.clientSecret,
|
|
350
|
+
provisionState.redirectUri ?? fallbackRedirectUri,
|
|
351
|
+
);
|
|
352
|
+
botToken = result.botToken;
|
|
353
|
+
|
|
354
|
+
// Resolve the bot's own Slack user id so it can be persisted to
|
|
355
|
+
// agent.slackId (UAP-1.3) — required for the Sync Manifest button to
|
|
356
|
+
// appear. Folded into the same try/catch as the exchange: an auth.test
|
|
357
|
+
// failure right after a successful exchange is treated the same as an
|
|
358
|
+
// exchange failure so the flow doesn't half-complete with a bot token
|
|
359
|
+
// stored but no resolved slackId.
|
|
360
|
+
const authTestResult = await this.deps.slackClient.authTest(botToken);
|
|
361
|
+
slackUserId = authTestResult.userId;
|
|
362
|
+
} catch (err) {
|
|
363
|
+
const msg =
|
|
364
|
+
err instanceof Error
|
|
365
|
+
? err.message
|
|
366
|
+
: "Unknown error exchanging OAuth code.";
|
|
367
|
+
return {
|
|
368
|
+
outcome: "exchange_failed",
|
|
369
|
+
error: `OAuth exchange failed: ${msg}`,
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const existingBundle = await this.deps.agentEnvService.getConfigBundle(
|
|
374
|
+
provisionState.agentId,
|
|
375
|
+
);
|
|
376
|
+
await this.deps.agentEnvService.patch(
|
|
377
|
+
provisionState.agentId,
|
|
378
|
+
{ SLACK_BOT_TOKEN: botToken },
|
|
379
|
+
this.deps.secretEnvVars,
|
|
380
|
+
);
|
|
381
|
+
|
|
382
|
+
// Persist unconditionally on every successful completion (both
|
|
383
|
+
// "reinstalled" and "needs_app_token" outcomes) — a bot reinstall can
|
|
384
|
+
// also resolve a fresh/different user id.
|
|
385
|
+
await this.deps.agentService.updateFields(provisionState.agentId, {
|
|
386
|
+
slackId: slackUserId,
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
if (existingBundle?.env.SLACK_APP_TOKEN) {
|
|
390
|
+
return {
|
|
391
|
+
outcome: "reinstalled",
|
|
392
|
+
agentId: provisionState.agentId,
|
|
393
|
+
ghConnectError,
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return {
|
|
398
|
+
outcome: "needs_app_token",
|
|
399
|
+
agentId: provisionState.agentId,
|
|
400
|
+
ghConnectError,
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Stores SLACK_APP_TOKEN and reconciles system crons. Mirrors the legacy
|
|
406
|
+
* POST /admin/provision/xapp-token handler exactly.
|
|
407
|
+
*/
|
|
408
|
+
async saveAppToken(
|
|
409
|
+
agentId: string | undefined,
|
|
410
|
+
xappToken: string | undefined,
|
|
411
|
+
): Promise<SaveAppTokenResult> {
|
|
412
|
+
if (!agentId) {
|
|
413
|
+
return { ok: false, agentId: "", error: "Agent ID is required." };
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (!xappToken?.startsWith("xapp-")) {
|
|
417
|
+
return {
|
|
418
|
+
ok: false,
|
|
419
|
+
agentId,
|
|
420
|
+
error: "App-Level Token must start with xapp-",
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
try {
|
|
425
|
+
// SHIPWRIGHT_AGENT_API_KEY and SHIPWRIGHT_TASK_STORE_TOKEN are NOT minted
|
|
426
|
+
// here. K8s-managed agents already have both minted straight into the K8s
|
|
427
|
+
// Secret by provisioner.provision() (agent-provisioner.ts) — that Secret is
|
|
428
|
+
// the sole source of truth (via secretKeyRef in the Deployment manifest).
|
|
429
|
+
// Self-hosted agents never run the containerized entrypoint that reads
|
|
430
|
+
// SHIPWRIGHT_AGENT_API_KEY (agent/src/runtime/entrypoint.ts), and use the
|
|
431
|
+
// GitHub-backed task-store CLI config instead of a bearer token, so they
|
|
432
|
+
// don't need either key in AgentEnv. Minting them here previously orphaned
|
|
433
|
+
// a second, different, unused live credential per key on every provision.
|
|
434
|
+
await this.deps.agentEnvService.patch(
|
|
435
|
+
agentId,
|
|
436
|
+
{ SLACK_APP_TOKEN: xappToken },
|
|
437
|
+
this.deps.secretEnvVars,
|
|
438
|
+
);
|
|
439
|
+
|
|
440
|
+
await this.deps.agentCronJobService.reconcileSystemCrons(agentId);
|
|
441
|
+
|
|
442
|
+
return { ok: true, agentId };
|
|
443
|
+
} catch (err) {
|
|
444
|
+
const msg =
|
|
445
|
+
err instanceof Error
|
|
446
|
+
? err.message
|
|
447
|
+
: "Unknown error completing provisioning.";
|
|
448
|
+
return { ok: false, agentId, error: msg };
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|