@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.
Files changed (325) hide show
  1. package/Dockerfile +43 -0
  2. package/README.md +72 -2
  3. package/e2e/agents-page.e2e.ts +338 -0
  4. package/e2e/chat-degraded.e2e.ts +153 -0
  5. package/e2e/chat-page.e2e.ts +169 -0
  6. package/e2e/chat-progress.e2e.ts +206 -0
  7. package/e2e/chat-thread-page.e2e.ts +444 -0
  8. package/e2e/login-page.e2e.ts +148 -0
  9. package/e2e/test-server.ts +427 -0
  10. package/openapi.json +6098 -0
  11. package/openapi.platform.json +5553 -0
  12. package/package.json +62 -4
  13. package/playwright.config.ts +25 -0
  14. package/prisma/client/browser.ts +134 -0
  15. package/prisma/client/client.ts +158 -0
  16. package/prisma/client/commonInputTypes.ts +578 -0
  17. package/prisma/client/enums.ts +15 -0
  18. package/prisma/client/internal/class.ts +424 -0
  19. package/prisma/client/internal/prismaNamespace.ts +2898 -0
  20. package/prisma/client/internal/prismaNamespaceBrowser.ts +476 -0
  21. package/prisma/client/models/Account.ts +1774 -0
  22. package/prisma/client/models/AccountInvite.ts +1386 -0
  23. package/prisma/client/models/AccountMember.ts +1351 -0
  24. package/prisma/client/models/Agent.ts +4554 -0
  25. package/prisma/client/models/AgentAsk.ts +1373 -0
  26. package/prisma/client/models/AgentChatTokenUsageDailyByModel.ts +1638 -0
  27. package/prisma/client/models/AgentCronJob.ts +2416 -0
  28. package/prisma/client/models/AgentCronRun.ts +2858 -0
  29. package/prisma/client/models/AgentCronRunModelBreakdown.ts +1532 -0
  30. package/prisma/client/models/AgentCronRunSkillUsage.ts +1642 -0
  31. package/prisma/client/models/AgentEnv.ts +1421 -0
  32. package/prisma/client/models/AgentGitHubInstallationsSnapshot.ts +1289 -0
  33. package/prisma/client/models/AgentMember.ts +1316 -0
  34. package/prisma/client/models/AgentPhaseMethodology.ts +1355 -0
  35. package/prisma/client/models/AgentPlugin.ts +1421 -0
  36. package/prisma/client/models/AgentToken.ts +1380 -0
  37. package/prisma/client/models/AgentTool.ts +1351 -0
  38. package/prisma/client/models/AgentWorkQueueSnapshot.ts +1297 -0
  39. package/prisma/client/models/ChatThreadWatch.ts +1183 -0
  40. package/prisma/client/models/PushSubscription.ts +1233 -0
  41. package/prisma/client/models/SessionAlertState.ts +1187 -0
  42. package/prisma/client/models/SessionFollow.ts +1187 -0
  43. package/prisma/client/models/UserNotificationPrefs.ts +1221 -0
  44. package/prisma/client/models.ts +34 -0
  45. package/prisma/migrations/20260608000000_init/migration.sql +112 -0
  46. package/prisma/migrations/20260616000000_agent_members/migration.sql +18 -0
  47. package/prisma/migrations/20260624000000_add_agent_self_hosted/migration.sql +2 -0
  48. package/prisma/migrations/20260624120000_add_agent_repos/migration.sql +3 -0
  49. package/prisma/migrations/20260625000000_add_agent_cron_run/migration.sql +26 -0
  50. package/prisma/migrations/20260626000000_add_cron_run_token_fields/migration.sql +10 -0
  51. package/prisma/migrations/20260627000000_add_agent_chat_token_usage_daily/migration.sql +18 -0
  52. package/prisma/migrations/20260627010000_add_agent_chat_token_usage_fk/migration.sql +2 -0
  53. package/prisma/migrations/20260701000000_add_chat_threads_messages/migration.sql +37 -0
  54. package/prisma/migrations/20260701010000_drop_chat_threads_messages/migration.sql +17 -0
  55. package/prisma/migrations/20260701020000_add_cron_run_model_breakdown/migration.sql +23 -0
  56. package/prisma/migrations/20260701020000_chat_token_by_model/migration.sql +25 -0
  57. package/prisma/migrations/20260701030000_add_secret_flag_to_agent_env/migration.sql +2 -0
  58. package/prisma/migrations/20260701030000_drop_cron_run_legacy_columns/migration.sql +5 -0
  59. package/prisma/migrations/20260706000000_consolidate_cron_run_token_accounting/migration.sql +54 -0
  60. package/prisma/migrations/20260709000000_add_phase_to_agent_cron_run/migration.sql +8 -0
  61. package/prisma/migrations/20260717000000_add_item_tracking_to_agent_cron_run/migration.sql +10 -0
  62. package/prisma/migrations/20260717010000_add_agent_work_queue_snapshot/migration.sql +20 -0
  63. package/prisma/migrations/20260718000000_add_cron_phase_columns/migration.sql +25 -0
  64. package/prisma/migrations/20260718100000_drop_agent_cron_run_phase_column/migration.sql +8 -0
  65. package/prisma/migrations/20260724000000_add_agent_type_name/migration.sql +3 -0
  66. package/prisma/migrations/20260725232433_add_agent_author_allowlist/migration.sql +2 -0
  67. package/prisma/migrations/20260727120000_add_not_null_to_agent_author_allowlist/migration.sql +3 -0
  68. package/prisma/migrations/20260731000000_add_session_id_to_agent_cron_run/migration.sql +2 -0
  69. package/prisma/migrations/20260821000000_add_agent_restrict_slack_to_members/migration.sql +3 -0
  70. package/prisma/migrations/20260901000000_add_push_subscription_and_chat_thread_watch/migration.sql +40 -0
  71. package/prisma/migrations/20260903000000_add_agent_patch_author_allowlist/migration.sql +2 -0
  72. package/prisma/migrations/20260903120000_add_agent_review_author_allowlist/migration.sql +18 -0
  73. package/prisma/migrations/20260903150000_drop_agent_author_allowlist/migration.sql +14 -0
  74. package/prisma/migrations/20260905000000_add_started_at_index_to_agent_cron_run/migration.sql +11 -0
  75. package/prisma/migrations/20260906000000_add_session_follow_notification_prefs_alert_state/migration.sql +47 -0
  76. package/prisma/migrations/20260911000000_backfill_auto_follow_since/migration.sql +16 -0
  77. package/prisma/migrations/20260923115814_add_last_heartbeat_at_to_agent_cron_run/migration.sql +2 -0
  78. package/prisma/migrations/20260924120000_add_trial_expiry_fields_to_agent/migration.sql +6 -0
  79. package/prisma/migrations/20260929000000_add_agent_phase_methodology/migration.sql +21 -0
  80. package/prisma/migrations/20260929120000_add_agent_policy_fields/migration.sql +15 -0
  81. package/prisma/migrations/20261005000000_add_agent_github_installations_snapshot/migration.sql +21 -0
  82. package/prisma/migrations/20261008000000_add_cron_run_telemetry/migration.sql +50 -0
  83. package/prisma/migrations/20261008010000_add_account_models/migration.sql +48 -0
  84. package/prisma/migrations/20261008020000_add_cron_lockdown_disabled_at/migration.sql +3 -0
  85. package/prisma/migrations/20261009000000_chat_token_usage_bigint/migration.sql +7 -0
  86. package/prisma/migrations/20261010000000_add_cron_run_command_variant/migration.sql +2 -0
  87. package/prisma/migrations/20261019000000_add_agent_anthropic_identity/migration.sql +9 -0
  88. package/prisma/migrations/20261025000000_add_agent_ask/migration.sql +23 -0
  89. package/prisma/migrations/migration_lock.toml +3 -0
  90. package/prisma/schema/coding.prisma +85 -0
  91. package/prisma/schema/schema.prisma +506 -0
  92. package/prisma.config.ts +34 -0
  93. package/pwa-assets/icons/apple-touch-icon.png +0 -0
  94. package/pwa-assets/icons/favicon-32.png +0 -0
  95. package/pwa-assets/icons/icon-192.png +0 -0
  96. package/pwa-assets/icons/icon-512.png +0 -0
  97. package/pwa-assets/icons/icon-maskable-192.png +0 -0
  98. package/pwa-assets/icons/icon-maskable-512.png +0 -0
  99. package/src/account-agent-create.ts +42 -0
  100. package/src/account-agent-create.unit.test.ts +28 -0
  101. package/src/account-created-notifier.ts +71 -0
  102. package/src/account-created-notifier.unit.test.ts +70 -0
  103. package/src/account-email.ts +9 -0
  104. package/src/account-email.unit.test.ts +21 -0
  105. package/src/account-invites.ts +70 -0
  106. package/src/account-lifecycle.integration.test.ts +329 -0
  107. package/src/account-lifecycle.ts +206 -0
  108. package/src/account-lifecycle.unit.test.ts +108 -0
  109. package/src/account-members.ts +128 -0
  110. package/src/account-onboarding.integration.test.ts +101 -0
  111. package/src/account-onboarding.ts +95 -0
  112. package/src/account-trial-expiry-sweeper.ts +85 -0
  113. package/src/account-trial-warning-sweeper.integration.test.ts +228 -0
  114. package/src/account-trial-warning-sweeper.ts +216 -0
  115. package/src/accounts-api.integration.test.ts +196 -0
  116. package/src/accounts-api.smoke.test.ts +292 -0
  117. package/src/accounts-api.ts +289 -0
  118. package/src/accounts-api.unit.test.ts +93 -0
  119. package/src/accounts.integration.test.ts +178 -0
  120. package/src/accounts.ts +208 -0
  121. package/src/admin-boot-config.ts +574 -0
  122. package/src/admin-boot-config.unit.test.ts +28 -0
  123. package/src/admin-boot.boot-order.unit.test.ts +91 -0
  124. package/src/admin-boot.ts +690 -0
  125. package/src/admin-dev-login.smoke.test.ts +317 -0
  126. package/src/admin-extension.ts +432 -0
  127. package/src/admin-extension.unit.test.ts +532 -0
  128. package/src/admin-local-agent.smoke.test.ts +1643 -0
  129. package/src/admin-spec.smoke.test.ts +96 -0
  130. package/src/admin-spec.ts +186 -0
  131. package/src/admin-ui-account-nav.ts +20 -0
  132. package/src/admin-ui-account-pages.ts +131 -0
  133. package/src/admin-ui-account-pages.unit.test.ts +112 -0
  134. package/src/admin-ui-account.smoke.test.ts +286 -0
  135. package/src/admin-ui-account.ts +185 -0
  136. package/src/admin-ui-accounts-pages.ts +202 -0
  137. package/src/admin-ui-accounts-pages.unit.test.ts +138 -0
  138. package/src/admin-ui-accounts.smoke.test.ts +434 -0
  139. package/src/admin-ui-accounts.ts +266 -0
  140. package/src/admin-ui-layout.ts +60 -0
  141. package/src/admin-ui-layout.unit.test.ts +140 -0
  142. package/src/admin-ui-pages.ts +3489 -0
  143. package/src/admin-ui-pages.unit.test.ts +4355 -0
  144. package/src/admin-ui-push-test.smoke.test.ts +125 -0
  145. package/src/admin-ui-push-test.ts +102 -0
  146. package/src/admin-ui-styles.ts +656 -0
  147. package/src/admin-ui-styles.unit.test.ts +103 -0
  148. package/src/admin-ui.fixtures.ts +457 -0
  149. package/src/admin-ui.smoke.test.ts +7227 -0
  150. package/src/admin-ui.ts +3824 -0
  151. package/src/agent-account-assignment.ts +49 -0
  152. package/src/agent-account-assignment.unit.test.ts +99 -0
  153. package/src/agent-anthropic-identity.integration.test.ts +99 -0
  154. package/src/agent-anthropic-identity.ts +78 -0
  155. package/src/agent-anthropic-identity.unit.test.ts +77 -0
  156. package/src/agent-ask-config.ts +59 -0
  157. package/src/agent-ask-config.unit.test.ts +70 -0
  158. package/src/agent-ask.integration.test.ts +175 -0
  159. package/src/agent-ask.smoke.test.ts +457 -0
  160. package/src/agent-ask.ts +378 -0
  161. package/src/agent-chat-tokens.integration.test.ts +420 -0
  162. package/src/agent-chat-tokens.smoke.test.ts +551 -0
  163. package/src/agent-chat-tokens.ts +259 -0
  164. package/src/agent-cron-jobs.integration.test.ts +582 -0
  165. package/src/agent-cron-jobs.ts +615 -0
  166. package/src/agent-cron-jobs.unit.test.ts +420 -0
  167. package/src/agent-cron-run-stats.integration.test.ts +1551 -0
  168. package/src/agent-cron-run-stats.smoke.test.ts +953 -0
  169. package/src/agent-cron-run-stats.ts +752 -0
  170. package/src/agent-cron-runs.integration.test.ts +1491 -0
  171. package/src/agent-cron-runs.ts +489 -0
  172. package/src/agent-deletion-checklist.ts +128 -0
  173. package/src/agent-deletion-checklist.unit.test.ts +201 -0
  174. package/src/agent-deletion-per-agent.integration.test.ts +323 -0
  175. package/src/agent-deletion.integration.test.ts +323 -0
  176. package/src/agent-deletion.ts +329 -0
  177. package/src/agent-deletion.unit.test.ts +412 -0
  178. package/src/agent-envs.integration.test.ts +323 -0
  179. package/src/agent-envs.ts +244 -0
  180. package/src/agent-github-installations.integration.test.ts +115 -0
  181. package/src/agent-github-installations.ts +52 -0
  182. package/src/agent-manifest.ts +685 -0
  183. package/src/agent-manifest.unit.test.ts +870 -0
  184. package/src/agent-members.ts +69 -0
  185. package/src/agent-members.unit.test.ts +250 -0
  186. package/src/agent-patch-author-allowlist.integration.test.ts +218 -0
  187. package/src/agent-plugins.integration.test.ts +157 -0
  188. package/src/agent-plugins.ts +95 -0
  189. package/src/agent-plugins.unit.test.ts +43 -0
  190. package/src/agent-provisioner-per-agent.integration.test.ts +949 -0
  191. package/src/agent-provisioner.integration.test.ts +1245 -0
  192. package/src/agent-provisioner.ts +1650 -0
  193. package/src/agent-provisioner.unit.test.ts +1852 -0
  194. package/src/agent-tokens.integration.test.ts +150 -0
  195. package/src/agent-tokens.ts +112 -0
  196. package/src/agent-tools.integration.test.ts +129 -0
  197. package/src/agent-tools.ts +111 -0
  198. package/src/agents-api.integration.test.ts +705 -0
  199. package/src/agents-api.sentry.smoke.test.ts +478 -0
  200. package/src/agents-api.smoke.test.ts +5986 -0
  201. package/src/agents-api.ts +2826 -0
  202. package/src/agents.integration.test.ts +512 -0
  203. package/src/agents.ts +1097 -0
  204. package/src/agents.unit.test.ts +1736 -0
  205. package/src/anthropic-credential-guard.ts +44 -0
  206. package/src/anthropic-credential-guard.unit.test.ts +88 -0
  207. package/src/anthropic-federated-token-provider.integration.test.ts +248 -0
  208. package/src/anthropic-federated-token-provider.ts +249 -0
  209. package/src/anthropic-identity-client.fixtures.ts +237 -0
  210. package/src/anthropic-identity-client.integration.test.ts +342 -0
  211. package/src/anthropic-identity-client.ts +473 -0
  212. package/src/anthropic-identity-fixture-spec.integration.test.ts +169 -0
  213. package/src/anthropic-identity-name.integration.test.ts +83 -0
  214. package/src/anthropic-identity-name.ts +163 -0
  215. package/src/anthropic-identity-name.unit.test.ts +345 -0
  216. package/src/anthropic-openapi-contract.fixtures.ts +192 -0
  217. package/src/api-auth.ts +383 -0
  218. package/src/api-auth.unit.test.ts +837 -0
  219. package/src/api.smoke.test.ts +1225 -0
  220. package/src/api.ts +376 -0
  221. package/src/attachment-validation.ts +124 -0
  222. package/src/attachment-validation.unit.test.ts +218 -0
  223. package/src/caller-scope.ts +131 -0
  224. package/src/caller-scope.unit.test.ts +116 -0
  225. package/src/chat-credential-config.ts +22 -0
  226. package/src/chat-credentials-logging.unit.test.ts +92 -0
  227. package/src/chat-markers.ts +114 -0
  228. package/src/chat-markers.unit.test.ts +116 -0
  229. package/src/chat-service-provisioning-client.integration.test.ts +412 -0
  230. package/src/chat-service-provisioning-client.ts +221 -0
  231. package/src/chat.smoke.test.ts +1875 -0
  232. package/src/clock.ts +47 -0
  233. package/src/coding-task-store-provisioning-client.integration.test.ts +372 -0
  234. package/src/coding-task-store-provisioning-client.ts +156 -0
  235. package/src/crypto.ts +83 -0
  236. package/src/dev-auth-guard.ts +29 -0
  237. package/src/dev-auth-guard.unit.test.ts +51 -0
  238. package/src/errors.ts +77 -0
  239. package/src/errors.unit.test.ts +52 -0
  240. package/src/fixtures/anthropic-identity-recorded-fixtures.json +850 -0
  241. package/src/fixtures/anthropic-openapi/SDK_VERSION +1 -0
  242. package/src/fixtures/anthropic-openapi/mock-spec.json.gz +0 -0
  243. package/src/fixtures/chat-service-provisioning-cassette.json +156 -0
  244. package/src/fixtures/github-app-manifest-cassette.json +44 -0
  245. package/src/fixtures/google-auth-cassette.json +36 -0
  246. package/src/fixtures/http-chat-client-cassette.json +226 -0
  247. package/src/fixtures/k8s-cassette.json +470 -0
  248. package/src/fixtures/okta-auth-cassette.json +36 -0
  249. package/src/fixtures/slack-provision-cassette.json +12 -0
  250. package/src/fixtures/slack-provisioning-http-cassette.json +98 -0
  251. package/src/fixtures/task-store-provisioning-cassette.json +85 -0
  252. package/src/github-app-provisioning-client.integration.test.ts +149 -0
  253. package/src/github-app-provisioning-client.ts +161 -0
  254. package/src/github-app-provisioning-client.unit.test.ts +114 -0
  255. package/src/github-provisioning-service.ts +569 -0
  256. package/src/github-provisioning-service.unit.test.ts +123 -0
  257. package/src/google-auth-client.integration.test.ts +195 -0
  258. package/src/google-auth-client.ts +121 -0
  259. package/src/http-chat-client.integration.test.ts +554 -0
  260. package/src/http-chat-client.ts +533 -0
  261. package/src/http-chat-client.unit.test.ts +119 -0
  262. package/src/import-boundary.unit.test.ts +240 -0
  263. package/src/kubernetes-client.integration.test.ts +697 -0
  264. package/src/kubernetes-client.ts +1027 -0
  265. package/src/kubernetes-client.unit.test.ts +726 -0
  266. package/src/main.smoke.test.ts +91 -0
  267. package/src/main.ts +21 -0
  268. package/src/main.unit.test.ts +801 -0
  269. package/src/okta-auth-client.integration.test.ts +263 -0
  270. package/src/okta-auth-client.ts +152 -0
  271. package/src/openapi-schemas.ts +1310 -0
  272. package/src/openapi-schemas.unit.test.ts +348 -0
  273. package/src/per-agent-identity.fixtures.ts +305 -0
  274. package/src/platform-doc-route.ts +40 -0
  275. package/src/prisma-client.integration.test.ts +57 -0
  276. package/src/prisma-client.ts +187 -0
  277. package/src/prisma-client.unit.test.ts +178 -0
  278. package/src/prisma-tx.ts +23 -0
  279. package/src/push-content.ts +177 -0
  280. package/src/push-content.unit.test.ts +213 -0
  281. package/src/push-crypto.ts +135 -0
  282. package/src/push-crypto.unit.test.ts +66 -0
  283. package/src/push-sender.ts +169 -0
  284. package/src/push-sender.unit.test.ts +107 -0
  285. package/src/push-service.ts +332 -0
  286. package/src/push-service.unit.test.ts +679 -0
  287. package/src/push-toggle.ts +135 -0
  288. package/src/push-toggle.unit.test.ts +78 -0
  289. package/src/pwa-https-gating.smoke.test.ts +229 -0
  290. package/src/pwa-icons.unit.test.ts +41 -0
  291. package/src/pwa-routes.smoke.test.ts +276 -0
  292. package/src/pwa.ts +380 -0
  293. package/src/pwa.unit.test.ts +321 -0
  294. package/src/schema.unit.test.ts +122 -0
  295. package/src/self-serve-config.ts +52 -0
  296. package/src/self-serve-config.unit.test.ts +79 -0
  297. package/src/server.smoke.test.ts +273 -0
  298. package/src/server.ts +50 -0
  299. package/src/service-integration.ts +67 -0
  300. package/src/service-integration.unit.test.ts +129 -0
  301. package/src/slack-provisioning-client.integration.test.ts +308 -0
  302. package/src/slack-provisioning-client.ts +411 -0
  303. package/src/slack-provisioning-client.unit.test.ts +80 -0
  304. package/src/slack-provisioning-service.integration.test.ts +637 -0
  305. package/src/slack-provisioning-service.ts +451 -0
  306. package/src/slack-provisioning.integration.test.ts +395 -0
  307. package/src/specialty-manifest-loader.fixture.integration.test.ts +63 -0
  308. package/src/specialty-manifest-loader.ts +255 -0
  309. package/src/specialty-manifest-loader.unit.test.ts +301 -0
  310. package/src/specialty-registry.schema.unit.test.ts +35 -0
  311. package/src/specialty-registry.ts +236 -0
  312. package/src/specialty-registry.unit.test.ts +272 -0
  313. package/src/startup-policy-reconcile.ts +77 -0
  314. package/src/startup-policy-reconcile.unit.test.ts +251 -0
  315. package/src/theme-tokens.json +12 -0
  316. package/src/token-crypto.ts +60 -0
  317. package/src/token-crypto.unit.test.ts +182 -0
  318. package/src/trial-expiry-sweeper.integration.test.ts +318 -0
  319. package/src/trial-expiry-sweeper.ts +132 -0
  320. package/src/trial-expiry-sweeper.unit.test.ts +391 -0
  321. package/src/trial-expiry-warning-sweeper.integration.test.ts +468 -0
  322. package/src/trial-expiry-warning-sweeper.ts +447 -0
  323. package/src/trial-expiry-warning-sweeper.unit.test.ts +148 -0
  324. package/src/voice-contract.unit.test.ts +102 -0
  325. package/tsconfig.json +9 -0
@@ -0,0 +1,411 @@
1
+ /**
2
+ * agent/src/slack-provisioning-client.ts
3
+ * SlackProvisioningClient interface and HttpSlackProvisioningClient implementation.
4
+ *
5
+ * Used during the one-time agent provisioning flow:
6
+ * 1. apps.manifest.create — creates the Slack app from a manifest
7
+ *
8
+ * apps.manifest.create returns credentials (client_id, client_secret, signing_secret)
9
+ * directly in the response — no separate oauth.v2.access exchange needed.
10
+ */
11
+
12
+ // ─── Types ────────────────────────────────────────────────────────────────────
13
+
14
+ interface AppManifestDisplayInfo {
15
+ name: string;
16
+ description?: string;
17
+ long_description?: string;
18
+ background_color?: string;
19
+ }
20
+
21
+ interface AppManifestBotUser {
22
+ display_name: string;
23
+ always_online?: boolean;
24
+ }
25
+
26
+ interface AppManifestOAuthConfig {
27
+ scopes: {
28
+ bot?: string[];
29
+ user?: string[];
30
+ };
31
+ redirect_urls?: string[];
32
+ }
33
+
34
+ interface AppManifestSettings {
35
+ event_subscriptions?: {
36
+ bot_events?: string[];
37
+ };
38
+ interactivity?: {
39
+ is_enabled: boolean;
40
+ request_url?: string;
41
+ };
42
+ org_deploy_enabled?: boolean;
43
+ socket_mode_enabled?: boolean;
44
+ }
45
+
46
+ export interface AppManifest {
47
+ display_information: AppManifestDisplayInfo;
48
+ features?: {
49
+ app_home?: {
50
+ home_tab_enabled?: boolean;
51
+ messages_tab_enabled?: boolean;
52
+ messages_tab_read_only_enabled?: boolean;
53
+ };
54
+ bot_user?: AppManifestBotUser;
55
+ agent_view?: {
56
+ agent_description?: string;
57
+ suggested_prompts?: { title: string; message: string }[];
58
+ };
59
+ };
60
+ oauth_config?: AppManifestOAuthConfig;
61
+ settings?: AppManifestSettings;
62
+ }
63
+
64
+ // ─── Interface ────────────────────────────────────────────────────────────────
65
+
66
+ export interface SlackProvisioningClient {
67
+ /**
68
+ * Call apps.manifest.create with the given xoxp- user token and manifest.
69
+ * Returns the new app's ID, OAuth redirect URL, and OAuth credentials.
70
+ */
71
+ createAppManifest(
72
+ xoxpToken: string,
73
+ manifest: AppManifest,
74
+ ): Promise<{
75
+ appId: string;
76
+ oauthRedirectUrl: string;
77
+ clientId: string;
78
+ clientSecret: string;
79
+ signingSecret: string;
80
+ }>;
81
+
82
+ /**
83
+ * Call apps.manifest.update with the given xoxp- user token, app ID, and manifest.
84
+ * Updates the manifest of an already-provisioned Slack app in-place.
85
+ */
86
+ updateAppManifest(
87
+ xoxpToken: string,
88
+ appId: string,
89
+ manifest: AppManifest,
90
+ ): Promise<void>;
91
+
92
+ /**
93
+ * Call apps.manifest.delete with the given xoxp- user token and app ID.
94
+ * Permanently deletes a provisioned Slack app.
95
+ */
96
+ deleteApp(xoxpToken: string, appId: string): Promise<void>;
97
+
98
+ /**
99
+ * Exchange an OAuth authorization code for a bot token via oauth.v2.access.
100
+ */
101
+ exchangeOAuthCode(
102
+ code: string,
103
+ clientId: string,
104
+ clientSecret: string,
105
+ redirectUri: string,
106
+ ): Promise<{ botToken: string }>;
107
+
108
+ /**
109
+ * Call auth.test with a bot token to resolve the bot's own Slack user id.
110
+ * Used right after exchangeOAuthCode() to persist `agent.slackId`.
111
+ */
112
+ authTest(botToken: string): Promise<{ userId: string }>;
113
+ }
114
+
115
+ // ─── Agent manifest ───────────────────────────────────────────────────────────
116
+
117
+ /**
118
+ * Builds the Slack app manifest for a Shipwright agent.
119
+ *
120
+ * Used for both initial provisioning (pass redirectUri for the OAuth callback)
121
+ * and subsequent manifest syncs (omit redirectUri to keep localhost default).
122
+ * Socket Mode and the full event/agent view config are always applied — there
123
+ * is no separate "provisioning-only" manifest.
124
+ *
125
+ * The canonical bot scope list is exported as `AGENT_BOT_SCOPES` so that other
126
+ * code paths (e.g. the sync-manifest reinstall OAuth URL) can stay in sync
127
+ * without duplicating the array.
128
+ */
129
+ export const AGENT_BOT_SCOPES: string[] = [
130
+ "app_mentions:read",
131
+ "assistant:write",
132
+ "channels:history",
133
+ "channels:read",
134
+ "chat:write",
135
+ "files:read",
136
+ "files:write",
137
+ "groups:history",
138
+ "im:history",
139
+ "im:write",
140
+ "mpim:history",
141
+ "reactions:read",
142
+ "reactions:write",
143
+ "users:read",
144
+ "users:read.email",
145
+ ];
146
+
147
+ export function buildAgentManifest(
148
+ appName: string,
149
+ redirectUri?: string,
150
+ ): AppManifest {
151
+ return {
152
+ display_information: {
153
+ name: appName,
154
+ description: `${appName} — powered by Shipwright`,
155
+ background_color: "#1a1a2e",
156
+ },
157
+ features: {
158
+ app_home: {
159
+ home_tab_enabled: false,
160
+ messages_tab_enabled: true,
161
+ messages_tab_read_only_enabled: false,
162
+ },
163
+ bot_user: {
164
+ display_name: appName,
165
+ always_online: true,
166
+ },
167
+ agent_view: {
168
+ agent_description: `${appName} — powered by Shipwright`,
169
+ suggested_prompts: [],
170
+ },
171
+ },
172
+ oauth_config: {
173
+ scopes: {
174
+ bot: AGENT_BOT_SCOPES,
175
+ },
176
+ ...(redirectUri !== undefined ? { redirect_urls: [redirectUri] } : {}),
177
+ },
178
+ settings: {
179
+ org_deploy_enabled: false,
180
+ socket_mode_enabled: true,
181
+ event_subscriptions: {
182
+ bot_events: [
183
+ "agent_session_stopped",
184
+ "app_mention",
185
+ "message.channels",
186
+ "message.groups",
187
+ "message.im",
188
+ "reaction_added",
189
+ ],
190
+ },
191
+ },
192
+ };
193
+ }
194
+
195
+ // ─── HTTP implementation ──────────────────────────────────────────────────────
196
+
197
+ /**
198
+ * Production implementation — calls real Slack API endpoints.
199
+ *
200
+ * The constructor takes optional Slack API URL overrides for testing/staging.
201
+ */
202
+ export class HttpSlackProvisioningClient implements SlackProvisioningClient {
203
+ private readonly apiBase: string;
204
+ private readonly fetchFn: typeof fetch;
205
+
206
+ constructor(opts?: { apiBase?: string; fetchFn?: typeof fetch }) {
207
+ this.apiBase = opts?.apiBase ?? "https://slack.com/api";
208
+ this.fetchFn = opts?.fetchFn ?? fetch;
209
+ }
210
+
211
+ async exchangeOAuthCode(
212
+ code: string,
213
+ clientId: string,
214
+ clientSecret: string,
215
+ redirectUri: string,
216
+ ): Promise<{ botToken: string }> {
217
+ const url = `${this.apiBase}/oauth.v2.access`;
218
+ const params = new URLSearchParams({
219
+ code,
220
+ redirect_uri: redirectUri,
221
+ });
222
+
223
+ const credentials = Buffer.from(`${clientId}:${clientSecret}`).toString(
224
+ "base64",
225
+ );
226
+ const resp = await this.fetchFn(url, {
227
+ method: "POST",
228
+ headers: {
229
+ Authorization: `Basic ${credentials}`,
230
+ "Content-Type": "application/x-www-form-urlencoded",
231
+ },
232
+ body: params.toString(),
233
+ });
234
+
235
+ if (!resp.ok) {
236
+ throw new Error(
237
+ `Slack oauth.v2.access HTTP error: ${resp.status} ${resp.statusText}`,
238
+ );
239
+ }
240
+
241
+ const data = (await resp.json()) as {
242
+ ok: boolean;
243
+ error?: string;
244
+ access_token?: string;
245
+ };
246
+
247
+ if (!data.ok) {
248
+ throw new Error(`Slack oauth.v2.access failed: ${data.error}`);
249
+ }
250
+
251
+ if (!data.access_token) {
252
+ throw new Error("Slack oauth.v2.access response missing access_token");
253
+ }
254
+
255
+ return { botToken: data.access_token };
256
+ }
257
+
258
+ async authTest(botToken: string): Promise<{ userId: string }> {
259
+ const url = `${this.apiBase}/auth.test`;
260
+ const resp = await this.fetchFn(url, {
261
+ method: "POST",
262
+ headers: {
263
+ Authorization: `Bearer ${botToken}`,
264
+ },
265
+ });
266
+
267
+ if (!resp.ok) {
268
+ throw new Error(
269
+ `Slack auth.test HTTP error: ${resp.status} ${resp.statusText}`,
270
+ );
271
+ }
272
+
273
+ const data = (await resp.json()) as {
274
+ ok: boolean;
275
+ error?: string;
276
+ user_id?: string;
277
+ };
278
+
279
+ if (!data.ok) {
280
+ throw new Error(`Slack auth.test failed: ${data.error}`);
281
+ }
282
+
283
+ if (!data.user_id) {
284
+ throw new Error("Slack auth.test response missing user_id");
285
+ }
286
+
287
+ return { userId: data.user_id };
288
+ }
289
+
290
+ async updateAppManifest(
291
+ xoxpToken: string,
292
+ appId: string,
293
+ manifest: AppManifest,
294
+ ): Promise<void> {
295
+ const url = `${this.apiBase}/apps.manifest.update`;
296
+ const resp = await this.fetchFn(url, {
297
+ method: "POST",
298
+ headers: {
299
+ Authorization: `Bearer ${xoxpToken}`,
300
+ "Content-Type": "application/json; charset=utf-8",
301
+ },
302
+ body: JSON.stringify({
303
+ app_id: appId,
304
+ manifest: JSON.stringify(manifest),
305
+ }),
306
+ });
307
+
308
+ if (!resp.ok) {
309
+ throw new Error(
310
+ `Slack apps.manifest.update HTTP error: ${resp.status} ${resp.statusText}`,
311
+ );
312
+ }
313
+
314
+ const data = (await resp.json()) as { ok: boolean; error?: string };
315
+
316
+ if (!data.ok) {
317
+ throw new Error(`Slack apps.manifest.update failed: ${data.error}`);
318
+ }
319
+ }
320
+
321
+ async deleteApp(xoxpToken: string, appId: string): Promise<void> {
322
+ const url = `${this.apiBase}/apps.manifest.delete`;
323
+ const resp = await this.fetchFn(url, {
324
+ method: "POST",
325
+ headers: {
326
+ Authorization: `Bearer ${xoxpToken}`,
327
+ "Content-Type": "application/json; charset=utf-8",
328
+ },
329
+ body: JSON.stringify({ app_id: appId }),
330
+ });
331
+
332
+ if (!resp.ok) {
333
+ throw new Error(
334
+ `Slack apps.manifest.delete HTTP error: ${resp.status} ${resp.statusText}`,
335
+ );
336
+ }
337
+
338
+ const data = (await resp.json()) as { ok: boolean; error?: string };
339
+
340
+ if (!data.ok) {
341
+ throw new Error(`Slack apps.manifest.delete failed: ${data.error}`);
342
+ }
343
+ }
344
+
345
+ async createAppManifest(
346
+ xoxpToken: string,
347
+ manifest: AppManifest,
348
+ ): Promise<{
349
+ appId: string;
350
+ oauthRedirectUrl: string;
351
+ clientId: string;
352
+ clientSecret: string;
353
+ signingSecret: string;
354
+ }> {
355
+ const url = `${this.apiBase}/apps.manifest.create`;
356
+ const resp = await this.fetchFn(url, {
357
+ method: "POST",
358
+ headers: {
359
+ Authorization: `Bearer ${xoxpToken}`,
360
+ "Content-Type": "application/json; charset=utf-8",
361
+ },
362
+ body: JSON.stringify({ manifest: JSON.stringify(manifest) }),
363
+ });
364
+
365
+ if (!resp.ok) {
366
+ throw new Error(
367
+ `Slack apps.manifest.create HTTP error: ${resp.status} ${resp.statusText}`,
368
+ );
369
+ }
370
+
371
+ const data = (await resp.json()) as {
372
+ ok: boolean;
373
+ error?: string;
374
+ app_id?: string;
375
+ oauth_authorize_url?: string;
376
+ credentials?: {
377
+ client_id?: string;
378
+ client_secret?: string;
379
+ signing_secret?: string;
380
+ };
381
+ };
382
+
383
+ if (!data.ok) {
384
+ throw new Error(`Slack apps.manifest.create failed: ${data.error}`);
385
+ }
386
+
387
+ if (!data.app_id || !data.oauth_authorize_url) {
388
+ throw new Error(
389
+ "Slack apps.manifest.create response missing app_id or oauth_authorize_url",
390
+ );
391
+ }
392
+
393
+ if (
394
+ !data.credentials?.client_id ||
395
+ !data.credentials?.client_secret ||
396
+ !data.credentials?.signing_secret
397
+ ) {
398
+ throw new Error(
399
+ "Slack apps.manifest.create response missing credentials (client_id, client_secret, or signing_secret)",
400
+ );
401
+ }
402
+
403
+ return {
404
+ appId: data.app_id,
405
+ oauthRedirectUrl: data.oauth_authorize_url,
406
+ clientId: data.credentials.client_id,
407
+ clientSecret: data.credentials.client_secret,
408
+ signingSecret: data.credentials.signing_secret,
409
+ };
410
+ }
411
+ }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * admin/src/slack-provisioning-client.unit.test.ts
3
+ * Unit tests for buildAgentManifest. No I/O, no network — shape assertions only.
4
+ */
5
+
6
+ import { describe, expect, it } from "bun:test";
7
+ import { buildAgentManifest } from "./slack-provisioning-client.ts";
8
+
9
+ const NAME = "test-agent";
10
+ const REDIRECT = "https://shipwright.example.com/admin/provision/complete";
11
+
12
+ describe("buildAgentManifest", () => {
13
+ describe("buildAgentManifest without redirectUri (unit)", () => {
14
+ const manifest = buildAgentManifest(NAME);
15
+
16
+ it("sets socket_mode_enabled: true", () => {
17
+ expect(manifest.settings?.socket_mode_enabled).toBe(true);
18
+ });
19
+
20
+ it("includes required bot events", () => {
21
+ const events = manifest.settings?.event_subscriptions?.bot_events ?? [];
22
+ expect(events).toContain("message.im");
23
+ expect(events).toContain("message.channels");
24
+ expect(events).toContain("message.groups");
25
+ expect(events).toContain("app_mention");
26
+ expect(events).toContain("reaction_added");
27
+ // AGS-1.1: Slack's native Stop/session-cancellation event for the
28
+ // Agent Sessions API — without this subscription the agent has no way
29
+ // to know when Slack has externally halted a stream (confirmed via
30
+ // repeated missing_agent_session_stopped_event_subscription log lines).
31
+ expect(events).toContain("agent_session_stopped");
32
+ });
33
+
34
+ it("includes agent_view", () => {
35
+ expect(manifest.features?.agent_view).toBeDefined();
36
+ expect(manifest.features?.agent_view?.agent_description).toContain(NAME);
37
+ });
38
+
39
+ it("sets always_online: true", () => {
40
+ expect(manifest.features?.bot_user?.always_online).toBe(true);
41
+ });
42
+
43
+ it("includes required scopes", () => {
44
+ const scopes = manifest.oauth_config?.scopes.bot ?? [];
45
+ expect(scopes).toContain("assistant:write");
46
+ expect(scopes).toContain("app_mentions:read");
47
+ expect(scopes).toContain("im:history");
48
+ expect(scopes).toContain("mpim:history");
49
+ expect(scopes).toContain("channels:history");
50
+ expect(scopes).toContain("groups:history");
51
+ expect(scopes).toContain("chat:write");
52
+ expect(scopes).toContain("users:read.email");
53
+ });
54
+
55
+ it("omits redirect_urls when no redirectUri provided", () => {
56
+ expect(manifest.oauth_config?.redirect_urls).toBeUndefined();
57
+ });
58
+
59
+ it("uses agent name in display_information", () => {
60
+ expect(manifest.display_information.name).toBe(NAME);
61
+ expect(manifest.display_information.description).toContain(NAME);
62
+ });
63
+ });
64
+
65
+ describe("provisioning (with redirectUri)", () => {
66
+ const manifest = buildAgentManifest(NAME, REDIRECT);
67
+
68
+ it("sets redirect_urls to the provided URI", () => {
69
+ expect(manifest.oauth_config?.redirect_urls).toEqual([REDIRECT]);
70
+ });
71
+
72
+ it("still sets socket_mode_enabled: true", () => {
73
+ expect(manifest.settings?.socket_mode_enabled).toBe(true);
74
+ });
75
+
76
+ it("still includes agent_view", () => {
77
+ expect(manifest.features?.agent_view).toBeDefined();
78
+ });
79
+ });
80
+ });