@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,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"mintToken_success": {
|
|
3
|
+
"status": 201,
|
|
4
|
+
"body": { "id": "tok_abc123", "rawToken": "sts_raw_test_token_value" }
|
|
5
|
+
},
|
|
6
|
+
"mintToken_success_with_agentId": {
|
|
7
|
+
"status": 201,
|
|
8
|
+
"body": { "id": "tok_def456", "rawToken": "sts_raw_test_token_agent_scoped" }
|
|
9
|
+
},
|
|
10
|
+
"mintToken_500": {
|
|
11
|
+
"status": 500,
|
|
12
|
+
"body": { "error": "internal server error" }
|
|
13
|
+
},
|
|
14
|
+
"revokeToken_success": {
|
|
15
|
+
"status": 204,
|
|
16
|
+
"body": {}
|
|
17
|
+
},
|
|
18
|
+
"revokeToken_404": {
|
|
19
|
+
"status": 404,
|
|
20
|
+
"body": { "error": "not found" }
|
|
21
|
+
},
|
|
22
|
+
"revokeToken_500": {
|
|
23
|
+
"status": 500,
|
|
24
|
+
"body": { "error": "internal server error" }
|
|
25
|
+
},
|
|
26
|
+
"listTokens_mixedAgents": {
|
|
27
|
+
"status": 200,
|
|
28
|
+
"body": [
|
|
29
|
+
{
|
|
30
|
+
"id": "tok_agent1_a",
|
|
31
|
+
"label": "agent-1 token a",
|
|
32
|
+
"agentId": "agent-1",
|
|
33
|
+
"createdAt": "2026-01-01T00:00:00.000Z"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"id": "tok_agent2_a",
|
|
37
|
+
"label": "agent-2 token",
|
|
38
|
+
"agentId": "agent-2",
|
|
39
|
+
"createdAt": "2026-01-01T00:00:00.000Z"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"id": "tok_agent1_b",
|
|
43
|
+
"label": "agent-1 token b",
|
|
44
|
+
"agentId": "agent-1",
|
|
45
|
+
"createdAt": "2026-01-02T00:00:00.000Z"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"id": "tok_admin_noagent",
|
|
49
|
+
"label": "admin token",
|
|
50
|
+
"createdAt": "2026-01-01T00:00:00.000Z"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"id": "tok_agent1_revoked",
|
|
54
|
+
"label": "agent-1 already revoked",
|
|
55
|
+
"agentId": "agent-1",
|
|
56
|
+
"createdAt": "2026-01-01T00:00:00.000Z",
|
|
57
|
+
"revokedAt": "2026-01-03T00:00:00.000Z"
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
},
|
|
61
|
+
"listTokens_noMatches": {
|
|
62
|
+
"status": 200,
|
|
63
|
+
"body": [
|
|
64
|
+
{
|
|
65
|
+
"id": "tok_agent2_a",
|
|
66
|
+
"label": "agent-2 token",
|
|
67
|
+
"agentId": "agent-2",
|
|
68
|
+
"createdAt": "2026-01-01T00:00:00.000Z"
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"id": "tok_admin_noagent",
|
|
72
|
+
"label": "admin token",
|
|
73
|
+
"createdAt": "2026-01-01T00:00:00.000Z"
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
},
|
|
77
|
+
"listTokens_empty": {
|
|
78
|
+
"status": 200,
|
|
79
|
+
"body": []
|
|
80
|
+
},
|
|
81
|
+
"listTokens_500": {
|
|
82
|
+
"status": 500,
|
|
83
|
+
"body": { "error": "internal server error" }
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* admin/src/github-app-provisioning-client.integration.test.ts
|
|
3
|
+
* Integration tests for HttpGithubAppProvisioningClient against recorded
|
|
4
|
+
* GitHub API fixtures.
|
|
5
|
+
*
|
|
6
|
+
* Drives the client through an INJECTED fetchFn that replays canned Responses
|
|
7
|
+
* from a cassette keyed by scenario — no live API server, no global.fetch
|
|
8
|
+
* override, no mock.module(). Mirrors the pattern in
|
|
9
|
+
* slack-provisioning-client.integration.test.ts.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { describe, expect, it } from "bun:test";
|
|
13
|
+
import { readFileSync } from "node:fs";
|
|
14
|
+
import { HttpGithubAppProvisioningClient } from "./github-app-provisioning-client.ts";
|
|
15
|
+
|
|
16
|
+
// ─── Cassette ───────────────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
interface CassetteEntry {
|
|
19
|
+
status: number;
|
|
20
|
+
body: unknown;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const CASSETTE_PATH = new URL(
|
|
24
|
+
"./fixtures/github-app-manifest-cassette.json",
|
|
25
|
+
import.meta.url,
|
|
26
|
+
).pathname;
|
|
27
|
+
|
|
28
|
+
const cassette: Record<string, CassetteEntry> = JSON.parse(
|
|
29
|
+
readFileSync(CASSETTE_PATH, "utf-8"),
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
interface RecordedRequest {
|
|
33
|
+
url: string;
|
|
34
|
+
method: string;
|
|
35
|
+
headers: Headers;
|
|
36
|
+
body?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Build an injected fetchFn that returns the cassette entry for `key`.
|
|
41
|
+
* Records the last request so tests can assert URL/method/headers/body.
|
|
42
|
+
*/
|
|
43
|
+
function cassetteFetch(key: string): {
|
|
44
|
+
fetchFn: typeof fetch;
|
|
45
|
+
lastRequest: () => RecordedRequest;
|
|
46
|
+
} {
|
|
47
|
+
let last: RecordedRequest | undefined;
|
|
48
|
+
const entry = cassette[key];
|
|
49
|
+
if (!entry) throw new Error(`cassette key not found: ${key}`);
|
|
50
|
+
|
|
51
|
+
const fetchFn = (async (input: RequestInfo | URL, init?: RequestInit) => {
|
|
52
|
+
const url = typeof input === "string" ? input : input.toString();
|
|
53
|
+
last = {
|
|
54
|
+
url,
|
|
55
|
+
method: init?.method ?? "GET",
|
|
56
|
+
headers: new Headers(init?.headers),
|
|
57
|
+
body: typeof init?.body === "string" ? init.body : undefined,
|
|
58
|
+
};
|
|
59
|
+
return new Response(JSON.stringify(entry.body), {
|
|
60
|
+
status: entry.status,
|
|
61
|
+
statusText: `status-${entry.status}`,
|
|
62
|
+
headers: { "Content-Type": "application/json" },
|
|
63
|
+
});
|
|
64
|
+
}) as typeof fetch;
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
fetchFn,
|
|
68
|
+
lastRequest: () => {
|
|
69
|
+
if (!last) throw new Error("fetchFn was not called");
|
|
70
|
+
return last;
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function makeClient(key: string): {
|
|
76
|
+
client: HttpGithubAppProvisioningClient;
|
|
77
|
+
lastRequest: () => RecordedRequest;
|
|
78
|
+
} {
|
|
79
|
+
const { fetchFn, lastRequest } = cassetteFetch(key);
|
|
80
|
+
const client = new HttpGithubAppProvisioningClient({ fetchFn });
|
|
81
|
+
return { client, lastRequest };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ─── exchangeManifestCode ────────────────────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
describe("HttpGithubAppProvisioningClient — exchangeManifestCode", () => {
|
|
87
|
+
it("POSTs unauthenticated to app-manifests/{code}/conversions and returns narrowed fields", async () => {
|
|
88
|
+
const { client, lastRequest } = makeClient("exchangeManifestCode_success");
|
|
89
|
+
const result = await client.exchangeManifestCode("test-code");
|
|
90
|
+
|
|
91
|
+
const req = lastRequest();
|
|
92
|
+
expect(req.method).toBe("POST");
|
|
93
|
+
expect(req.url).toBe(
|
|
94
|
+
"https://api.github.com/app-manifests/test-code/conversions",
|
|
95
|
+
);
|
|
96
|
+
expect(req.headers.get("authorization")).toBeNull();
|
|
97
|
+
|
|
98
|
+
expect(result).toEqual({
|
|
99
|
+
appId: "123456",
|
|
100
|
+
slug: "test-agent-app",
|
|
101
|
+
pem: "-----BEGIN RSA PRIVATE KEY-----\ntestpemcontents\n-----END RSA PRIVATE KEY-----\n",
|
|
102
|
+
clientId: "Iv1.testclientid1234",
|
|
103
|
+
clientSecret: "test-client-secret-value",
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("never stores or returns webhook_secret", async () => {
|
|
108
|
+
const { client } = makeClient("exchangeManifestCode_success");
|
|
109
|
+
const result = await client.exchangeManifestCode("test-code");
|
|
110
|
+
|
|
111
|
+
expect(result).not.toHaveProperty("webhookSecret");
|
|
112
|
+
expect(result).not.toHaveProperty("webhook_secret");
|
|
113
|
+
expect(Object.keys(result)).not.toContain("webhookSecret");
|
|
114
|
+
expect(JSON.stringify(result)).not.toContain("test-webhook-secret");
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("throws a descriptive error on a 404 (bad/expired code)", async () => {
|
|
118
|
+
const { client } = makeClient("exchangeManifestCode_not_found");
|
|
119
|
+
await expect(client.exchangeManifestCode("bad-code")).rejects.toThrow(
|
|
120
|
+
/GitHub app-manifests conversion HTTP error: 404/,
|
|
121
|
+
);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("throws a descriptive error on a 422 (validation failed)", async () => {
|
|
125
|
+
const { client } = makeClient("exchangeManifestCode_unprocessable");
|
|
126
|
+
await expect(client.exchangeManifestCode("bad-code")).rejects.toThrow(
|
|
127
|
+
/GitHub app-manifests conversion HTTP error: 422/,
|
|
128
|
+
);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("throws a descriptive error when required fields are missing from an ok response", async () => {
|
|
132
|
+
const { client } = makeClient("exchangeManifestCode_missing_fields");
|
|
133
|
+
await expect(client.exchangeManifestCode("test-code")).rejects.toThrow(
|
|
134
|
+
/response missing/,
|
|
135
|
+
);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// ─── Default construction (no opts) ─────────────────────────────────────────
|
|
140
|
+
|
|
141
|
+
describe("HttpGithubAppProvisioningClient — default construction", () => {
|
|
142
|
+
it("can be constructed with no arguments (defaults apiBase and fetchFn)", () => {
|
|
143
|
+
expect(() => new HttpGithubAppProvisioningClient()).not.toThrow();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("can be constructed with empty opts", () => {
|
|
147
|
+
expect(() => new HttpGithubAppProvisioningClient({})).not.toThrow();
|
|
148
|
+
});
|
|
149
|
+
});
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* admin/src/github-app-provisioning-client.ts
|
|
3
|
+
* GithubAppProvisioningClient interface and HttpGithubAppProvisioningClient
|
|
4
|
+
* implementation.
|
|
5
|
+
*
|
|
6
|
+
* Used during the one-time agent provisioning flow:
|
|
7
|
+
* 1. Redirect the user through GitHub's "create a GitHub App from a
|
|
8
|
+
* manifest" flow (https://docs.github.com/apps/creating-github-apps/setting-up-a-github-app/creating-a-github-app-from-a-manifest)
|
|
9
|
+
* 2. GitHub redirects back with a one-time `code`.
|
|
10
|
+
* 3. exchangeManifestCode(code) — POST app-manifests/{code}/conversions
|
|
11
|
+
* (unauthenticated) — returns the new app's credentials directly.
|
|
12
|
+
*
|
|
13
|
+
* The conversion response also includes a `webhook_secret` field. This
|
|
14
|
+
* codebase has no webhook receiver, so it is intentionally discarded — never
|
|
15
|
+
* stored, never returned.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* GitHub's manifest flow requires a top-level `url` (the app's homepage) —
|
|
22
|
+
* every agent/org gets the same value, no per-agent plumbing needed.
|
|
23
|
+
*/
|
|
24
|
+
const AGENT_APP_HOMEPAGE_URL = "https://shipwrightharness.com";
|
|
25
|
+
|
|
26
|
+
interface GithubAppManifest {
|
|
27
|
+
name: string;
|
|
28
|
+
public: boolean;
|
|
29
|
+
default_permissions: {
|
|
30
|
+
contents: "write";
|
|
31
|
+
pull_requests: "write";
|
|
32
|
+
actions: "read";
|
|
33
|
+
workflows: "write";
|
|
34
|
+
};
|
|
35
|
+
url: string;
|
|
36
|
+
redirect_url?: string;
|
|
37
|
+
setup_url?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ─── Interface ────────────────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
export interface GithubAppProvisioningClient {
|
|
43
|
+
/**
|
|
44
|
+
* Call POST app-manifests/{code}/conversions with the one-time code GitHub
|
|
45
|
+
* redirected back with after the manifest-based app creation flow.
|
|
46
|
+
* Unauthenticated per GitHub's docs. Returns the new app's ID, slug, and
|
|
47
|
+
* credentials — `webhook_secret` is discarded, never returned.
|
|
48
|
+
*/
|
|
49
|
+
exchangeManifestCode(code: string): Promise<{
|
|
50
|
+
appId: string;
|
|
51
|
+
slug: string;
|
|
52
|
+
pem: string;
|
|
53
|
+
clientId: string;
|
|
54
|
+
clientSecret: string;
|
|
55
|
+
}>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ─── Agent manifest ───────────────────────────────────────────────────────────
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Builds the GitHub App manifest for a Shipwright agent.
|
|
62
|
+
*
|
|
63
|
+
* Used during the manifest-based app creation flow. `redirectUri` is where
|
|
64
|
+
* GitHub sends the user after app creation (manifest flow's `redirect_url`,
|
|
65
|
+
* distinct from the OAuth `callback_urls`). `setupUrl` is where the user
|
|
66
|
+
* lands after installing the app (`setup_url`). Both are omitted when not
|
|
67
|
+
* provided. `public` (default false) makes the App installable on other
|
|
68
|
+
* orgs besides the one that owns it.
|
|
69
|
+
*
|
|
70
|
+
* No `hook_attributes` is set — no webhook receiver exists in this codebase.
|
|
71
|
+
*/
|
|
72
|
+
export function buildAgentAppManifest(
|
|
73
|
+
appName: string,
|
|
74
|
+
opts?: { redirectUri?: string; setupUrl?: string; public?: boolean },
|
|
75
|
+
): GithubAppManifest {
|
|
76
|
+
return {
|
|
77
|
+
name: appName,
|
|
78
|
+
public: opts?.public === true,
|
|
79
|
+
default_permissions: {
|
|
80
|
+
contents: "write",
|
|
81
|
+
pull_requests: "write",
|
|
82
|
+
actions: "read",
|
|
83
|
+
workflows: "write",
|
|
84
|
+
},
|
|
85
|
+
url: AGENT_APP_HOMEPAGE_URL,
|
|
86
|
+
...(opts?.redirectUri !== undefined
|
|
87
|
+
? { redirect_url: opts.redirectUri }
|
|
88
|
+
: {}),
|
|
89
|
+
...(opts?.setupUrl !== undefined ? { setup_url: opts.setupUrl } : {}),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ─── HTTP implementation ──────────────────────────────────────────────────────
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Production implementation — calls the real GitHub API.
|
|
97
|
+
*
|
|
98
|
+
* The constructor takes optional apiBase/fetchFn overrides for testability.
|
|
99
|
+
*/
|
|
100
|
+
export class HttpGithubAppProvisioningClient
|
|
101
|
+
implements GithubAppProvisioningClient
|
|
102
|
+
{
|
|
103
|
+
private readonly apiBase: string;
|
|
104
|
+
private readonly fetchFn: typeof fetch;
|
|
105
|
+
|
|
106
|
+
constructor(opts?: { apiBase?: string; fetchFn?: typeof fetch }) {
|
|
107
|
+
this.apiBase = opts?.apiBase ?? "https://api.github.com";
|
|
108
|
+
this.fetchFn = opts?.fetchFn ?? fetch;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async exchangeManifestCode(code: string): Promise<{
|
|
112
|
+
appId: string;
|
|
113
|
+
slug: string;
|
|
114
|
+
pem: string;
|
|
115
|
+
clientId: string;
|
|
116
|
+
clientSecret: string;
|
|
117
|
+
}> {
|
|
118
|
+
const url = `${this.apiBase}/app-manifests/${code}/conversions`;
|
|
119
|
+
|
|
120
|
+
// Unauthenticated per GitHub's docs — no Authorization header.
|
|
121
|
+
const resp = await this.fetchFn(url, {
|
|
122
|
+
method: "POST",
|
|
123
|
+
headers: {
|
|
124
|
+
Accept: "application/vnd.github+json",
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
if (!resp.ok) {
|
|
129
|
+
throw new Error(
|
|
130
|
+
`GitHub app-manifests conversion HTTP error: ${resp.status} ${resp.statusText}`,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const data = (await resp.json()) as {
|
|
135
|
+
id?: number;
|
|
136
|
+
slug?: string;
|
|
137
|
+
pem?: string;
|
|
138
|
+
client_id?: string;
|
|
139
|
+
client_secret?: string;
|
|
140
|
+
// Present in GitHub's real response but intentionally never read —
|
|
141
|
+
// no webhook receiver exists in this codebase, so webhook_secret must
|
|
142
|
+
// never be stored or returned.
|
|
143
|
+
webhook_secret?: string;
|
|
144
|
+
};
|
|
145
|
+
const { id, slug, pem, client_id, client_secret } = data;
|
|
146
|
+
|
|
147
|
+
if (id === undefined || !slug || !pem || !client_id || !client_secret) {
|
|
148
|
+
throw new Error(
|
|
149
|
+
"GitHub app-manifests conversion response missing one or more required fields (id, slug, pem, client_id, client_secret)",
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
appId: String(id),
|
|
155
|
+
slug,
|
|
156
|
+
pem,
|
|
157
|
+
clientId: client_id,
|
|
158
|
+
clientSecret: client_secret,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* admin/src/github-app-provisioning-client.unit.test.ts
|
|
3
|
+
* Unit tests for buildAgentAppManifest. No I/O, no network — shape assertions only.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, expect, it } from "bun:test";
|
|
7
|
+
import { buildAgentAppManifest } from "./github-app-provisioning-client.ts";
|
|
8
|
+
|
|
9
|
+
const NAME = "test-agent";
|
|
10
|
+
const REDIRECT = "https://shipwright.example.com/admin/provision/complete";
|
|
11
|
+
const SETUP_URL = "https://shipwright.example.com/admin/provision/setup";
|
|
12
|
+
|
|
13
|
+
describe("buildAgentAppManifest", () => {
|
|
14
|
+
describe("without redirectUri/setupUrl (unit)", () => {
|
|
15
|
+
const manifest = buildAgentAppManifest(NAME);
|
|
16
|
+
|
|
17
|
+
it("locks in exactly the four required permission scopes, no extras", () => {
|
|
18
|
+
expect(manifest.default_permissions).toEqual({
|
|
19
|
+
contents: "write",
|
|
20
|
+
pull_requests: "write",
|
|
21
|
+
actions: "read",
|
|
22
|
+
workflows: "write",
|
|
23
|
+
});
|
|
24
|
+
expect(Object.keys(manifest.default_permissions).sort()).toEqual(
|
|
25
|
+
["actions", "contents", "pull_requests", "workflows"].sort(),
|
|
26
|
+
);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("sets public: false", () => {
|
|
30
|
+
expect(manifest.public).toBe(false);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("uses appName in the manifest name field", () => {
|
|
34
|
+
expect(manifest.name).toBe(NAME);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("omits redirect_url when no redirectUri provided", () => {
|
|
38
|
+
expect(manifest.redirect_url).toBeUndefined();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("omits setup_url when no setupUrl provided", () => {
|
|
42
|
+
expect(manifest.setup_url).toBeUndefined();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("does not add hook_attributes (no webhook receiver exists)", () => {
|
|
46
|
+
expect(manifest).not.toHaveProperty("hook_attributes");
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("sets url to the agent app homepage URL", () => {
|
|
50
|
+
expect(manifest.url).toBe("https://shipwrightharness.com");
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe("public option", () => {
|
|
55
|
+
it("emits public: true only when the option is on", () => {
|
|
56
|
+
expect(buildAgentAppManifest(NAME, { public: true }).public).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("emits public: false when the option is false or omitted", () => {
|
|
60
|
+
expect(buildAgentAppManifest(NAME, { public: false }).public).toBe(false);
|
|
61
|
+
expect(
|
|
62
|
+
buildAgentAppManifest(NAME, { redirectUri: REDIRECT }).public,
|
|
63
|
+
).toBe(false);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe("provisioning (with redirectUri and setupUrl)", () => {
|
|
68
|
+
const manifest = buildAgentAppManifest(NAME, {
|
|
69
|
+
redirectUri: REDIRECT,
|
|
70
|
+
setupUrl: SETUP_URL,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("wires redirectUri into redirect_url", () => {
|
|
74
|
+
expect(manifest.redirect_url).toBe(REDIRECT);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("wires setupUrl into setup_url", () => {
|
|
78
|
+
expect(manifest.setup_url).toBe(SETUP_URL);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("still locks in exactly the four required permission scopes", () => {
|
|
82
|
+
expect(manifest.default_permissions).toEqual({
|
|
83
|
+
contents: "write",
|
|
84
|
+
pull_requests: "write",
|
|
85
|
+
actions: "read",
|
|
86
|
+
workflows: "write",
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("still sets public: false", () => {
|
|
91
|
+
expect(manifest.public).toBe(false);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("sets url to the agent app homepage URL", () => {
|
|
95
|
+
expect(manifest.url).toBe("https://shipwrightharness.com");
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe("provisioning with only redirectUri", () => {
|
|
100
|
+
const manifest = buildAgentAppManifest(NAME, { redirectUri: REDIRECT });
|
|
101
|
+
|
|
102
|
+
it("sets redirect_url", () => {
|
|
103
|
+
expect(manifest.redirect_url).toBe(REDIRECT);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("omits setup_url", () => {
|
|
107
|
+
expect(manifest.setup_url).toBeUndefined();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("sets url to the agent app homepage URL", () => {
|
|
111
|
+
expect(manifest.url).toBe("https://shipwrightharness.com");
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
});
|