@app-vitals/agent-admin 0.0.1 → 0.1.1

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,412 @@
1
+ /**
2
+ * admin/src/agent-deletion.unit.test.ts
3
+ *
4
+ * Unit tests for deleteAgentFully() — the shared agent-deletion orchestration.
5
+ * All dependencies (Prisma, provisioner, task-store, chat-service, Slack) are
6
+ * injected in-memory doubles: no real DB, no network. Per this repo's isolation
7
+ * contract — no mock.module(), no global overrides.
8
+ */
9
+
10
+ import { describe, expect, it } from "bun:test";
11
+ import {
12
+ type DeleteAgentFullyDeps,
13
+ deleteAgentFully,
14
+ } from "./agent-deletion.ts";
15
+ import { NotFoundError } from "./errors.ts";
16
+
17
+ // ─── In-memory doubles ──────────────────────────────────────────────────────
18
+
19
+ interface EnvRow {
20
+ key: string;
21
+ value: string;
22
+ secret: boolean;
23
+ }
24
+
25
+ interface AgentRow {
26
+ id: string;
27
+ name: string;
28
+ anthropicFederationRuleId?: string | null;
29
+ anthropicServiceAccountId?: string | null;
30
+ k8sServiceAccountName?: string | null;
31
+ }
32
+
33
+ /** Minimal in-memory Prisma double tracking whether the Agent row exists. */
34
+ class FakePrisma {
35
+ deleted = false;
36
+ constructor(
37
+ private readonly row: AgentRow | null,
38
+ private readonly envRows: EnvRow[] = [],
39
+ ) {}
40
+
41
+ agent = {
42
+ findUnique: async (_args: {
43
+ where: { id: string };
44
+ select: {
45
+ id: true;
46
+ name: true;
47
+ anthropicFederationRuleId: true;
48
+ anthropicServiceAccountId: true;
49
+ k8sServiceAccountName: true;
50
+ };
51
+ }) => {
52
+ if (this.deleted || !this.row) return null;
53
+ return {
54
+ id: this.row.id,
55
+ name: this.row.name,
56
+ anthropicFederationRuleId: this.row.anthropicFederationRuleId ?? null,
57
+ anthropicServiceAccountId: this.row.anthropicServiceAccountId ?? null,
58
+ k8sServiceAccountName: this.row.k8sServiceAccountName ?? null,
59
+ };
60
+ },
61
+ delete: async (_args: { where: { id: string } }): Promise<unknown> => {
62
+ this.deleted = true;
63
+ return {};
64
+ },
65
+ };
66
+
67
+ agentEnv = {
68
+ findMany: async (_args: {
69
+ where: { agentId: string };
70
+ select: { key: true; value: true; secret: true };
71
+ }): Promise<EnvRow[]> => this.envRows,
72
+ };
73
+ }
74
+
75
+ interface CallLog {
76
+ deprovision: string[];
77
+ tsListed: string[];
78
+ tsRevoked: string[];
79
+ csListed: string[];
80
+ csRevoked: string[];
81
+ threadsDeleted: string[];
82
+ slackDeleted: Array<{ token: string; appId: string }>;
83
+ identityRemoved: string[];
84
+ }
85
+
86
+ function makeDeps(opts: {
87
+ prisma: FakePrisma;
88
+ envRows?: EnvRow[];
89
+ tsTokens?: { id: string }[];
90
+ csTokens?: { id: string }[];
91
+ fail?: Partial<{
92
+ deprovision: Error;
93
+ tsRevoke: Error;
94
+ csRevoke: Error;
95
+ threads: Error;
96
+ slack: Error;
97
+ }>;
98
+ /** Give the provisioner a removeAnthropicIdentity (absent by default). */
99
+ withIdentityRemoval?: boolean;
100
+ }): { deps: DeleteAgentFullyDeps; log: CallLog } {
101
+ const log: CallLog = {
102
+ deprovision: [],
103
+ tsListed: [],
104
+ tsRevoked: [],
105
+ csListed: [],
106
+ csRevoked: [],
107
+ threadsDeleted: [],
108
+ slackDeleted: [],
109
+ identityRemoved: [],
110
+ };
111
+
112
+ const deps: DeleteAgentFullyDeps = {
113
+ prisma: opts.prisma,
114
+ provisioner: {
115
+ deprovision: async (agentId, _o) => {
116
+ log.deprovision.push(agentId);
117
+ if (opts.fail?.deprovision) throw opts.fail.deprovision;
118
+ },
119
+ ...(opts.withIdentityRemoval
120
+ ? {
121
+ removeAnthropicIdentity: async (agentId: string) => {
122
+ log.identityRemoved.push(agentId);
123
+ },
124
+ }
125
+ : {}),
126
+ },
127
+ serviceIntegrations: [
128
+ {
129
+ name: "task-store",
130
+ listTokensForAgent: async (agentId) => {
131
+ log.tsListed.push(agentId);
132
+ return opts.tsTokens ?? [];
133
+ },
134
+ revokeToken: async (id) => {
135
+ log.tsRevoked.push(id);
136
+ if (opts.fail?.tsRevoke) throw opts.fail.tsRevoke;
137
+ },
138
+ },
139
+ ],
140
+ chatService: {
141
+ listTokensForAgent: async (agentId) => {
142
+ log.csListed.push(agentId);
143
+ return opts.csTokens ?? [];
144
+ },
145
+ revokeToken: async (id) => {
146
+ log.csRevoked.push(id);
147
+ if (opts.fail?.csRevoke) throw opts.fail.csRevoke;
148
+ },
149
+ deleteThreadsForAgent: async (agentId) => {
150
+ log.threadsDeleted.push(agentId);
151
+ if (opts.fail?.threads) throw opts.fail.threads;
152
+ return { deleted: 0 };
153
+ },
154
+ },
155
+ slack: {
156
+ deleteApp: async (token, appId) => {
157
+ log.slackDeleted.push({ token, appId });
158
+ if (opts.fail?.slack) throw opts.fail.slack;
159
+ },
160
+ },
161
+ // Identity decrypt: env values in these tests are stored "plaintext".
162
+ decrypt: (value) => value,
163
+ };
164
+
165
+ return { deps, log };
166
+ }
167
+
168
+ const AGENT = { id: "agent-1", name: "dude-bot" };
169
+
170
+ // ─── AC1: Happy path ─────────────────────────────────────────────────────────
171
+
172
+ describe("deleteAgentFully — happy path", () => {
173
+ it("runs every automated step, deletes the Agent row, and returns agentDeleted: true", async () => {
174
+ const envRows: EnvRow[] = [
175
+ { key: "SLACK_APP_ID", value: "A123", secret: true },
176
+ { key: "GH_TOKEN", value: "ghp_x", secret: true },
177
+ { key: "PORT", value: "3000", secret: false },
178
+ ];
179
+ const prisma = new FakePrisma(AGENT, envRows);
180
+ const { deps, log } = makeDeps({
181
+ prisma,
182
+ tsTokens: [{ id: "ts-1" }],
183
+ csTokens: [{ id: "cs-1" }],
184
+ });
185
+
186
+ const result = await deleteAgentFully(AGENT.id, deps, {
187
+ xoxpToken: "xoxp-user",
188
+ });
189
+
190
+ expect(result.agentDeleted).toBe(true);
191
+ expect(result.failed).toEqual([]);
192
+ expect(result.completed).toEqual([
193
+ "k8s",
194
+ "task-store-tokens",
195
+ "chat-service-tokens-and-threads",
196
+ "slack-app",
197
+ ]);
198
+ // Agent row actually deleted.
199
+ expect(prisma.deleted).toBe(true);
200
+ // Each dependency was actually driven.
201
+ expect(log.deprovision).toEqual([AGENT.id]);
202
+ expect(log.tsRevoked).toEqual(["ts-1"]);
203
+ expect(log.csRevoked).toEqual(["cs-1"]);
204
+ expect(log.threadsDeleted).toEqual([AGENT.id]);
205
+ expect(log.slackDeleted).toEqual([{ token: "xoxp-user", appId: "A123" }]);
206
+ // Checklist contains the manual GH_TOKEN reminder; Slack was auto-deleted
207
+ // so it is NOT in the manual list; non-secret PORT is excluded.
208
+ const keys = result.manualStepsRequired.map((s) => s.key);
209
+ expect(keys).toEqual(["GH_TOKEN"]);
210
+ });
211
+
212
+ it("omits the slack-app step entirely when no SLACK_APP_ID is set", async () => {
213
+ const prisma = new FakePrisma(AGENT, [
214
+ { key: "GH_TOKEN", value: "ghp_x", secret: true },
215
+ ]);
216
+ const { deps, log } = makeDeps({ prisma });
217
+
218
+ const result = await deleteAgentFully(AGENT.id, deps, {
219
+ xoxpToken: "xoxp-user",
220
+ });
221
+
222
+ expect(result.agentDeleted).toBe(true);
223
+ expect(result.completed).not.toContain("slack-app");
224
+ expect(log.slackDeleted).toEqual([]);
225
+ expect(result.manualStepsRequired.map((s) => s.key)).toEqual(["GH_TOKEN"]);
226
+ });
227
+ });
228
+
229
+ // ─── AC2: Partial failure ────────────────────────────────────────────────────
230
+
231
+ describe("deleteAgentFully — partial failure", () => {
232
+ it("does not delete the Agent row, records the failed step, and still runs the others", async () => {
233
+ const prisma = new FakePrisma(AGENT, []);
234
+ const { deps, log } = makeDeps({
235
+ prisma,
236
+ tsTokens: [{ id: "ts-1" }],
237
+ fail: { tsRevoke: new Error("task-store down") },
238
+ });
239
+
240
+ const result = await deleteAgentFully(AGENT.id, deps);
241
+
242
+ expect(result.agentDeleted).toBe(false);
243
+ // Agent row still present.
244
+ expect(prisma.deleted).toBe(false);
245
+ // The failing step is captured with its message.
246
+ expect(result.failed).toEqual([
247
+ { step: "task-store-tokens", error: "task-store down" },
248
+ ]);
249
+ // Other steps still ran (not skipped) — K8s + chat-service completed.
250
+ expect(result.completed).toContain("k8s");
251
+ expect(result.completed).toContain("chat-service-tokens-and-threads");
252
+ expect(log.deprovision).toEqual([AGENT.id]);
253
+ expect(log.threadsDeleted).toEqual([AGENT.id]);
254
+ });
255
+
256
+ it("attempts every token in a step even if one revoke throws, then records the step failed", async () => {
257
+ const prisma = new FakePrisma(AGENT, []);
258
+ const { deps, log } = makeDeps({
259
+ prisma,
260
+ tsTokens: [{ id: "ts-1" }, { id: "ts-2" }],
261
+ fail: { tsRevoke: new Error("boom") },
262
+ });
263
+
264
+ const result = await deleteAgentFully(AGENT.id, deps);
265
+
266
+ // Both tokens were attempted despite the first throwing.
267
+ expect(log.tsRevoked).toEqual(["ts-1", "ts-2"]);
268
+ expect(result.failed[0].step).toBe("task-store-tokens");
269
+ expect(result.agentDeleted).toBe(false);
270
+ });
271
+ });
272
+
273
+ // ─── AC3: Retry after partial failure ────────────────────────────────────────
274
+
275
+ describe("deleteAgentFully — retry", () => {
276
+ it("completes remaining steps and deletes the row on a second, healthy call", async () => {
277
+ const prisma = new FakePrisma(AGENT, []);
278
+
279
+ // First call: task-store revoke fails.
280
+ const first = makeDeps({
281
+ prisma,
282
+ tsTokens: [{ id: "ts-1" }],
283
+ fail: { tsRevoke: new Error("task-store down") },
284
+ });
285
+ const r1 = await deleteAgentFully(AGENT.id, first.deps);
286
+ expect(r1.agentDeleted).toBe(false);
287
+ expect(prisma.deleted).toBe(false);
288
+
289
+ // Second call: dependency healthy again; underlying steps are idempotent
290
+ // (K8s already gone, token already effectively revoked) — no errors on
291
+ // re-run, and this time the row is deleted.
292
+ const second = makeDeps({ prisma, tsTokens: [{ id: "ts-1" }] });
293
+ const r2 = await deleteAgentFully(AGENT.id, second.deps);
294
+
295
+ expect(r2.agentDeleted).toBe(true);
296
+ expect(r2.failed).toEqual([]);
297
+ expect(prisma.deleted).toBe(true);
298
+ // Re-running already-completed steps didn't error.
299
+ expect(second.log.deprovision).toEqual([AGENT.id]);
300
+ expect(second.log.tsRevoked).toEqual(["ts-1"]);
301
+ });
302
+ });
303
+
304
+ // ─── AC4: Slack app present but no token supplied ────────────────────────────
305
+
306
+ describe("deleteAgentFully — Slack app without a token", () => {
307
+ it("adds a manual Slack checklist entry and does NOT mark agentDeleted: false", async () => {
308
+ const prisma = new FakePrisma(AGENT, [
309
+ { key: "SLACK_APP_ID", value: "A999", secret: true },
310
+ ]);
311
+ const { deps, log } = makeDeps({ prisma });
312
+
313
+ // No xoxpToken supplied.
314
+ const result = await deleteAgentFully(AGENT.id, deps);
315
+
316
+ expect(result.agentDeleted).toBe(true);
317
+ expect(prisma.deleted).toBe(true);
318
+ expect(log.slackDeleted).toEqual([]);
319
+ expect(result.completed).not.toContain("slack-app");
320
+ expect(result.failed).toEqual([]);
321
+
322
+ const slackStep = result.manualStepsRequired.find(
323
+ (s) => s.key === "SLACK_APP_ID",
324
+ );
325
+ expect(slackStep).toBeDefined();
326
+ expect(slackStep?.message).toContain("A999");
327
+ expect(slackStep?.message).toContain("no Slack user token was supplied");
328
+ });
329
+
330
+ it("records slack-app as failed (row not deleted) when a token IS supplied but the delete throws", async () => {
331
+ const prisma = new FakePrisma(AGENT, [
332
+ { key: "SLACK_APP_ID", value: "A999", secret: true },
333
+ ]);
334
+ const { deps } = makeDeps({
335
+ prisma,
336
+ fail: { slack: new Error("slack rejected") },
337
+ });
338
+
339
+ const result = await deleteAgentFully(AGENT.id, deps, {
340
+ xoxpToken: "xoxp-user",
341
+ });
342
+
343
+ expect(result.agentDeleted).toBe(false);
344
+ expect(prisma.deleted).toBe(false);
345
+ expect(result.failed).toEqual([
346
+ { step: "slack-app", error: "slack rejected" },
347
+ ]);
348
+ });
349
+ });
350
+
351
+ // ─── Edge: unknown agent ─────────────────────────────────────────────────────
352
+
353
+ describe("deleteAgentFully — unknown agent", () => {
354
+ it("throws NotFoundError when the Agent row is absent", async () => {
355
+ const prisma = new FakePrisma(null);
356
+ const { deps } = makeDeps({ prisma });
357
+
358
+ await expect(deleteAgentFully("nope", deps)).rejects.toBeInstanceOf(
359
+ NotFoundError,
360
+ );
361
+ });
362
+ });
363
+
364
+ // ─── Per-agent Anthropic identity ────────────────────────────────────────────
365
+
366
+ describe("deleteAgentFully — anthropic identity step", () => {
367
+ it("runs no anthropic-identity step for an agent without identity IDs", async () => {
368
+ const prisma = new FakePrisma(AGENT);
369
+ const { deps, log } = makeDeps({ prisma, withIdentityRemoval: true });
370
+
371
+ const result = await deleteAgentFully(AGENT.id, deps);
372
+
373
+ expect(result.agentDeleted).toBe(true);
374
+ expect(result.completed).not.toContain("anthropic-identity");
375
+ expect(log.identityRemoved).toEqual([]);
376
+ });
377
+
378
+ it("runs the step right after k8s when the agent has an identity", async () => {
379
+ const prisma = new FakePrisma({
380
+ ...AGENT,
381
+ anthropicServiceAccountId: "svac_1",
382
+ });
383
+ const { deps, log } = makeDeps({ prisma, withIdentityRemoval: true });
384
+
385
+ const result = await deleteAgentFully(AGENT.id, deps);
386
+
387
+ expect(log.identityRemoved).toEqual([AGENT.id]);
388
+ expect(result.completed.slice(0, 2)).toEqual(["k8s", "anthropic-identity"]);
389
+ expect(result.agentDeleted).toBe(true);
390
+ });
391
+
392
+ it("fails the step and lists the identity pieces when the provisioner cannot remove them", async () => {
393
+ const prisma = new FakePrisma({
394
+ ...AGENT,
395
+ anthropicFederationRuleId: "fdrl_1",
396
+ anthropicServiceAccountId: "svac_1",
397
+ k8sServiceAccountName: "dude-bot-sa",
398
+ });
399
+ const { deps } = makeDeps({ prisma });
400
+
401
+ const result = await deleteAgentFully(AGENT.id, deps);
402
+
403
+ expect(result.agentDeleted).toBe(false);
404
+ expect(prisma.deleted).toBe(false);
405
+ expect(result.failed.map((f) => f.step)).toEqual(["anthropic-identity"]);
406
+ expect(result.manualStepsRequired.map((s) => s.key)).toEqual([
407
+ "anthropicFederationRuleId",
408
+ "anthropicServiceAccountId",
409
+ "k8sServiceAccountName",
410
+ ]);
411
+ });
412
+ });