@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,489 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* admin/src/agent-cron-runs.ts
|
|
3
|
+
* AgentCronRunService — create and list AgentCronRun records.
|
|
4
|
+
*
|
|
5
|
+
* Each run records one cron invocation: whether it was skipped (pre-check
|
|
6
|
+
* returned false), the outcome, and any error.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type {
|
|
10
|
+
AgentCronRun,
|
|
11
|
+
Prisma,
|
|
12
|
+
PrismaClient,
|
|
13
|
+
} from "../prisma/client/client.ts";
|
|
14
|
+
import { NotFoundError } from "./errors.ts";
|
|
15
|
+
|
|
16
|
+
export type { AgentCronRun };
|
|
17
|
+
|
|
18
|
+
/** An AgentCronRun with its per-model token/cost breakdown rows attached. */
|
|
19
|
+
type AgentCronRunWithModelBreakdown = Prisma.AgentCronRunGetPayload<{
|
|
20
|
+
include: { modelBreakdown: true };
|
|
21
|
+
}>;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* An AgentCronRun with its per-model token/cost breakdown rows and its
|
|
25
|
+
* owning cron's id/name/schedule attached — used by cross-cron listings
|
|
26
|
+
* (e.g. listForAgent) so callers don't need N+1 cron lookups.
|
|
27
|
+
*/
|
|
28
|
+
type AgentCronRunWithCron = Prisma.AgentCronRunGetPayload<{
|
|
29
|
+
include: {
|
|
30
|
+
modelBreakdown: true;
|
|
31
|
+
cron: { select: { id: true; name: true; schedule: true } };
|
|
32
|
+
phaseCron: { select: { id: true; name: true } };
|
|
33
|
+
};
|
|
34
|
+
}>;
|
|
35
|
+
|
|
36
|
+
interface CreateAgentCronRunInput {
|
|
37
|
+
startedAt: Date;
|
|
38
|
+
completedAt?: Date | null;
|
|
39
|
+
skipped?: boolean;
|
|
40
|
+
skipReason?: string | null;
|
|
41
|
+
outcome?: string | null;
|
|
42
|
+
error?: string | null;
|
|
43
|
+
/** Child AgentCronJob id (FK) of the pipeline phase this run served (dev-task/review/patch/deploy). Null for legacy five-job crons or runs with no phase attribution. */
|
|
44
|
+
phaseId?: string | null;
|
|
45
|
+
/** Work item type this run was dispatched against ("task" | "pr"). Null when the tick had no dispatch. */
|
|
46
|
+
itemType?: string | null;
|
|
47
|
+
/** Work item id this run was dispatched against (e.g. "WLS-2.2" or "acme/x#123"). Null when the tick had no dispatch. */
|
|
48
|
+
itemId?: string | null;
|
|
49
|
+
/** Slash-command variant this dispatch ran ("default" | "alternate"). Null when the run had no variant attribution. */
|
|
50
|
+
commandVariant?: string | null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface ModelBreakdownEntry {
|
|
54
|
+
model: string;
|
|
55
|
+
inputTokens: number;
|
|
56
|
+
outputTokens: number;
|
|
57
|
+
cacheReadTokens: number;
|
|
58
|
+
cacheCreationTokens: number;
|
|
59
|
+
costUsd: number;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* First-turn context baseline reported by the agent: the usage of the run's
|
|
64
|
+
* first assistant message, whose input + cacheCreation + cacheRead is the
|
|
65
|
+
* full always-loaded context independent of cache warmth
|
|
66
|
+
* (computed agent-side from the Claude CLI stream).
|
|
67
|
+
*/
|
|
68
|
+
interface ContextBaselineEntry {
|
|
69
|
+
model: string;
|
|
70
|
+
contextTokens: number;
|
|
71
|
+
inputTokens: number;
|
|
72
|
+
cacheCreationTokens: number;
|
|
73
|
+
cacheReadTokens: number;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Per-skill / per-subagent token attribution row reported by the agent. */
|
|
77
|
+
interface SkillUsageEntry {
|
|
78
|
+
kind: string;
|
|
79
|
+
name: string;
|
|
80
|
+
invocations: number;
|
|
81
|
+
turns: number;
|
|
82
|
+
inputTokens: number;
|
|
83
|
+
outputTokens: number;
|
|
84
|
+
cacheReadTokens: number;
|
|
85
|
+
cacheCreationTokens: number;
|
|
86
|
+
invokeContextDelta?: number | null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface PatchAgentCronRunInput {
|
|
90
|
+
completedAt?: Date | null;
|
|
91
|
+
outcome?: string | null;
|
|
92
|
+
error?: string | null;
|
|
93
|
+
skipped?: boolean;
|
|
94
|
+
skipReason?: string | null;
|
|
95
|
+
inputTokens?: number | null;
|
|
96
|
+
outputTokens?: number | null;
|
|
97
|
+
cacheReadTokens?: number | null;
|
|
98
|
+
cacheCreationTokens?: number | null;
|
|
99
|
+
/** Agent session id this run was executed under. Null clears it. */
|
|
100
|
+
sessionId?: string | null;
|
|
101
|
+
/**
|
|
102
|
+
* Most recent debounced progress-push (recordProgress()) timestamp,
|
|
103
|
+
* sourced from the agent's injected Clock. Null clears it. Overwritten on
|
|
104
|
+
* every progress push — always the latest, not a history.
|
|
105
|
+
*/
|
|
106
|
+
lastHeartbeatAt?: Date | null;
|
|
107
|
+
modelBreakdown?: ModelBreakdownEntry[];
|
|
108
|
+
/** Spread onto the baseline* columns. */
|
|
109
|
+
contextBaseline?: ContextBaselineEntry;
|
|
110
|
+
turns?: number | null;
|
|
111
|
+
toolCalls?: number | null;
|
|
112
|
+
contextFingerprint?: string | null;
|
|
113
|
+
pluginVersion?: string | null;
|
|
114
|
+
claudeCodeVersion?: string | null;
|
|
115
|
+
/** Upserted per [cronRunId, kind, name], like modelBreakdown per model. */
|
|
116
|
+
skillUsage?: SkillUsageEntry[];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
interface ListAgentCronRunsOptions {
|
|
120
|
+
limit?: number;
|
|
121
|
+
offset?: number;
|
|
122
|
+
/** Narrow to runs dispatched against this work item (e.g. "WLS-2.2" or "acme/x#123"). */
|
|
123
|
+
itemId?: string;
|
|
124
|
+
/** Narrow to runs dispatched by this phase cron (a child AgentCronJob id). */
|
|
125
|
+
phaseId?: string;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
interface AgentCronRunList {
|
|
129
|
+
items: AgentCronRunWithModelBreakdown[];
|
|
130
|
+
total: number;
|
|
131
|
+
limit: number;
|
|
132
|
+
offset: number;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
interface ListForAgentOptions {
|
|
136
|
+
/** Narrow to a single cron. Still scoped to agentId — a cronId belonging
|
|
137
|
+
* to a different agent yields an empty result, not an error. */
|
|
138
|
+
cronId?: string;
|
|
139
|
+
/**
|
|
140
|
+
* "skipped" filters WHERE skipped = true regardless of the outcome column
|
|
141
|
+
* (mirrors the cronRunOutcomeLabel display convention: skipped is a
|
|
142
|
+
* boolean flag, not an outcome column value). Any other value filters
|
|
143
|
+
* WHERE skipped = false AND outcome = value. Omitted means no filter.
|
|
144
|
+
*/
|
|
145
|
+
outcome?: string;
|
|
146
|
+
limit?: number;
|
|
147
|
+
offset?: number;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
interface AgentCronRunListForAgent {
|
|
151
|
+
items: AgentCronRunWithCron[];
|
|
152
|
+
total: number;
|
|
153
|
+
limit: number;
|
|
154
|
+
offset: number;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Filter options for `listAcrossAgents()`. Mirrors `ListForAgentOptions` —
|
|
159
|
+
* the agent scoping itself comes from the `agentIds` array argument, not
|
|
160
|
+
* from an option here.
|
|
161
|
+
*/
|
|
162
|
+
interface ListAcrossAgentsOptions {
|
|
163
|
+
/** Narrow to a single cron, still scoped to the given agentIds. */
|
|
164
|
+
cronId?: string;
|
|
165
|
+
/**
|
|
166
|
+
* "skipped" filters WHERE skipped = true regardless of the outcome column
|
|
167
|
+
* (same semantics as ListForAgentOptions.outcome). Any other value filters
|
|
168
|
+
* WHERE skipped = false AND outcome = value. Omitted means no filter.
|
|
169
|
+
*/
|
|
170
|
+
outcome?: string;
|
|
171
|
+
limit?: number;
|
|
172
|
+
offset?: number;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export class AgentCronRunService {
|
|
176
|
+
constructor(private prisma: PrismaClient) {}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Create a new run record for a cron job.
|
|
180
|
+
* Validates that the cronId exists and belongs to agentId.
|
|
181
|
+
* Throws NotFoundError if the cronId doesn't exist or is not owned by agentId.
|
|
182
|
+
*/
|
|
183
|
+
async create(
|
|
184
|
+
cronId: string,
|
|
185
|
+
agentId: string,
|
|
186
|
+
input: CreateAgentCronRunInput,
|
|
187
|
+
): Promise<AgentCronRun> {
|
|
188
|
+
// Validate ownership
|
|
189
|
+
const job = await this.prisma.agentCronJob.findUnique({
|
|
190
|
+
where: { id: cronId },
|
|
191
|
+
});
|
|
192
|
+
if (!job || job.agentId !== agentId) {
|
|
193
|
+
throw new NotFoundError(`cron job ${cronId} not found`);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return this.prisma.agentCronRun.create({
|
|
197
|
+
data: {
|
|
198
|
+
cronId,
|
|
199
|
+
agentId,
|
|
200
|
+
startedAt: input.startedAt,
|
|
201
|
+
completedAt: input.completedAt ?? null,
|
|
202
|
+
skipped: input.skipped ?? false,
|
|
203
|
+
skipReason: input.skipReason ?? null,
|
|
204
|
+
outcome: input.outcome ?? null,
|
|
205
|
+
error: input.error ?? null,
|
|
206
|
+
phaseId: input.phaseId ?? null,
|
|
207
|
+
itemType: input.itemType ?? null,
|
|
208
|
+
itemId: input.itemId ?? null,
|
|
209
|
+
commandVariant: input.commandVariant ?? null,
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Update an existing run record with completion data and/or token fields.
|
|
216
|
+
* Validates that the runId exists, belongs to agentId, and belongs to cronId.
|
|
217
|
+
* Throws NotFoundError if the run doesn't exist, is not owned by agentId, or
|
|
218
|
+
* does not belong to the given cronId (prevents cross-cron access).
|
|
219
|
+
*/
|
|
220
|
+
async patch(
|
|
221
|
+
runId: string,
|
|
222
|
+
agentId: string,
|
|
223
|
+
cronId: string,
|
|
224
|
+
input: PatchAgentCronRunInput,
|
|
225
|
+
): Promise<AgentCronRunWithModelBreakdown> {
|
|
226
|
+
const run = await this.prisma.agentCronRun.findUnique({
|
|
227
|
+
where: { id: runId },
|
|
228
|
+
});
|
|
229
|
+
if (!run || run.agentId !== agentId) {
|
|
230
|
+
throw new NotFoundError(`cron run ${runId} not found`);
|
|
231
|
+
}
|
|
232
|
+
if (run.cronId !== cronId) {
|
|
233
|
+
throw new NotFoundError(`cron run ${runId} not found`);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// The top-level token fields (inputTokens/outputTokens/…) are still accepted
|
|
237
|
+
// on the input for backward compatibility with older agent builds, but they
|
|
238
|
+
// are no longer persisted: those columns were dropped and all token
|
|
239
|
+
// accounting now flows through AgentCronRunModelBreakdown (see modelBreakdown
|
|
240
|
+
// below). We simply ignore them here.
|
|
241
|
+
// TODO(CTT-cleanup): remove top-level token field acceptance once all agent
|
|
242
|
+
// builds send modelBreakdown only (see agent/src/runtime/cron-handler.ts's
|
|
243
|
+
// buildTokenPayload()).
|
|
244
|
+
const runData = {
|
|
245
|
+
...(input.completedAt !== undefined && {
|
|
246
|
+
completedAt: input.completedAt,
|
|
247
|
+
}),
|
|
248
|
+
...(input.outcome !== undefined && { outcome: input.outcome }),
|
|
249
|
+
...(input.error !== undefined && { error: input.error }),
|
|
250
|
+
...(input.skipped !== undefined && { skipped: input.skipped }),
|
|
251
|
+
...(input.skipReason !== undefined && { skipReason: input.skipReason }),
|
|
252
|
+
...(input.sessionId !== undefined && { sessionId: input.sessionId }),
|
|
253
|
+
...(input.lastHeartbeatAt !== undefined && {
|
|
254
|
+
lastHeartbeatAt: input.lastHeartbeatAt,
|
|
255
|
+
}),
|
|
256
|
+
...(input.contextBaseline !== undefined && {
|
|
257
|
+
baselineModel: input.contextBaseline.model,
|
|
258
|
+
baselineContextTokens: input.contextBaseline.contextTokens,
|
|
259
|
+
baselineInputTokens: input.contextBaseline.inputTokens,
|
|
260
|
+
baselineCacheCreationTokens: input.contextBaseline.cacheCreationTokens,
|
|
261
|
+
baselineCacheReadTokens: input.contextBaseline.cacheReadTokens,
|
|
262
|
+
}),
|
|
263
|
+
...(input.turns !== undefined && { turns: input.turns }),
|
|
264
|
+
...(input.toolCalls !== undefined && { toolCalls: input.toolCalls }),
|
|
265
|
+
...(input.contextFingerprint !== undefined && {
|
|
266
|
+
contextFingerprint: input.contextFingerprint,
|
|
267
|
+
}),
|
|
268
|
+
...(input.pluginVersion !== undefined && {
|
|
269
|
+
pluginVersion: input.pluginVersion,
|
|
270
|
+
}),
|
|
271
|
+
...(input.claudeCodeVersion !== undefined && {
|
|
272
|
+
claudeCodeVersion: input.claudeCodeVersion,
|
|
273
|
+
}),
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
const breakdownUpserts = (input.modelBreakdown ?? []).map((entry) =>
|
|
277
|
+
this.prisma.agentCronRunModelBreakdown.upsert({
|
|
278
|
+
where: {
|
|
279
|
+
cronRunId_model: { cronRunId: runId, model: entry.model },
|
|
280
|
+
},
|
|
281
|
+
create: {
|
|
282
|
+
cronRunId: runId,
|
|
283
|
+
model: entry.model,
|
|
284
|
+
inputTokens: entry.inputTokens,
|
|
285
|
+
outputTokens: entry.outputTokens,
|
|
286
|
+
cacheReadTokens: entry.cacheReadTokens,
|
|
287
|
+
cacheCreationTokens: entry.cacheCreationTokens,
|
|
288
|
+
costUsd: entry.costUsd,
|
|
289
|
+
},
|
|
290
|
+
update: {
|
|
291
|
+
inputTokens: entry.inputTokens,
|
|
292
|
+
outputTokens: entry.outputTokens,
|
|
293
|
+
cacheReadTokens: entry.cacheReadTokens,
|
|
294
|
+
cacheCreationTokens: entry.cacheCreationTokens,
|
|
295
|
+
costUsd: entry.costUsd,
|
|
296
|
+
},
|
|
297
|
+
}),
|
|
298
|
+
);
|
|
299
|
+
// Per-skill attribution rows follow the same upsert-per-key pattern as
|
|
300
|
+
// the model breakdown (unique on [cronRunId, kind, name]).
|
|
301
|
+
const skillUsageUpserts = (input.skillUsage ?? []).map((entry) => {
|
|
302
|
+
const fields = {
|
|
303
|
+
invocations: entry.invocations,
|
|
304
|
+
turns: entry.turns,
|
|
305
|
+
inputTokens: entry.inputTokens,
|
|
306
|
+
outputTokens: entry.outputTokens,
|
|
307
|
+
cacheReadTokens: entry.cacheReadTokens,
|
|
308
|
+
cacheCreationTokens: entry.cacheCreationTokens,
|
|
309
|
+
invokeContextDelta: entry.invokeContextDelta ?? null,
|
|
310
|
+
};
|
|
311
|
+
return this.prisma.agentCronRunSkillUsage.upsert({
|
|
312
|
+
where: {
|
|
313
|
+
cronRunId_kind_name: {
|
|
314
|
+
cronRunId: runId,
|
|
315
|
+
kind: entry.kind,
|
|
316
|
+
name: entry.name,
|
|
317
|
+
},
|
|
318
|
+
},
|
|
319
|
+
create: {
|
|
320
|
+
cronRunId: runId,
|
|
321
|
+
kind: entry.kind,
|
|
322
|
+
name: entry.name,
|
|
323
|
+
...fields,
|
|
324
|
+
},
|
|
325
|
+
update: fields,
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
if (breakdownUpserts.length > 0 || skillUsageUpserts.length > 0) {
|
|
330
|
+
// Wrap the run update and all child upserts in a single transaction
|
|
331
|
+
// so the writes are atomic — partial breakdown/attribution rows are
|
|
332
|
+
// never visible. The run update (with its modelBreakdown include) must
|
|
333
|
+
// be the LAST statement in the array: Prisma's array-form $transaction
|
|
334
|
+
// issues statements in order, so an earlier include would return
|
|
335
|
+
// breakdown rows as they stood before this call's upserts ran.
|
|
336
|
+
const results = await this.prisma.$transaction([
|
|
337
|
+
...breakdownUpserts,
|
|
338
|
+
...skillUsageUpserts,
|
|
339
|
+
this.prisma.agentCronRun.update({
|
|
340
|
+
where: { id: runId },
|
|
341
|
+
data: runData,
|
|
342
|
+
include: { modelBreakdown: true },
|
|
343
|
+
}),
|
|
344
|
+
]);
|
|
345
|
+
const updatedRun = results[
|
|
346
|
+
results.length - 1
|
|
347
|
+
] as AgentCronRunWithModelBreakdown;
|
|
348
|
+
return updatedRun;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return this.prisma.agentCronRun.update({
|
|
352
|
+
where: { id: runId },
|
|
353
|
+
data: runData,
|
|
354
|
+
include: { modelBreakdown: true },
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* List all runs for a cron job, sorted descending by startedAt.
|
|
360
|
+
* Validates that the cronId exists and belongs to agentId.
|
|
361
|
+
* Throws NotFoundError if the cronId doesn't exist or is not owned by agentId.
|
|
362
|
+
* Defaults to limit=20, offset=0.
|
|
363
|
+
*/
|
|
364
|
+
async list(
|
|
365
|
+
cronId: string,
|
|
366
|
+
agentId: string,
|
|
367
|
+
opts?: ListAgentCronRunsOptions,
|
|
368
|
+
): Promise<AgentCronRunList> {
|
|
369
|
+
const limit = opts?.limit ?? 20;
|
|
370
|
+
const offset = opts?.offset ?? 0;
|
|
371
|
+
|
|
372
|
+
// Validate ownership
|
|
373
|
+
const job = await this.prisma.agentCronJob.findUnique({
|
|
374
|
+
where: { id: cronId },
|
|
375
|
+
});
|
|
376
|
+
if (!job || job.agentId !== agentId) {
|
|
377
|
+
throw new NotFoundError(`cron job ${cronId} not found`);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const where: Prisma.AgentCronRunWhereInput = { cronId, agentId };
|
|
381
|
+
if (opts?.itemId) {
|
|
382
|
+
where.itemId = opts.itemId;
|
|
383
|
+
}
|
|
384
|
+
if (opts?.phaseId) {
|
|
385
|
+
where.phaseId = opts.phaseId;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const [items, total] = await this.prisma.$transaction([
|
|
389
|
+
this.prisma.agentCronRun.findMany({
|
|
390
|
+
where,
|
|
391
|
+
orderBy: { startedAt: "desc" },
|
|
392
|
+
take: limit,
|
|
393
|
+
skip: offset,
|
|
394
|
+
include: { modelBreakdown: true },
|
|
395
|
+
}),
|
|
396
|
+
this.prisma.agentCronRun.count({ where }),
|
|
397
|
+
]);
|
|
398
|
+
|
|
399
|
+
return { items, total, limit, offset };
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* List all runs for an agent across every cron it owns, sorted descending
|
|
404
|
+
* by startedAt. Unlike `.list()`, this method is agent-scoped by
|
|
405
|
+
* construction and does not validate a specific cronId's ownership — if
|
|
406
|
+
* opts.cronId is given but belongs to a different agent, the WHERE clause
|
|
407
|
+
* (agentId AND cronId) simply matches no rows.
|
|
408
|
+
* Defaults to limit=20, offset=0.
|
|
409
|
+
*/
|
|
410
|
+
async listForAgent(
|
|
411
|
+
agentId: string,
|
|
412
|
+
opts?: ListForAgentOptions,
|
|
413
|
+
): Promise<AgentCronRunListForAgent> {
|
|
414
|
+
const limit = opts?.limit ?? 20;
|
|
415
|
+
const offset = opts?.offset ?? 0;
|
|
416
|
+
|
|
417
|
+
const where: Prisma.AgentCronRunWhereInput = { agentId };
|
|
418
|
+
if (opts?.cronId) {
|
|
419
|
+
where.cronId = opts.cronId;
|
|
420
|
+
}
|
|
421
|
+
if (opts?.outcome === "skipped") {
|
|
422
|
+
where.skipped = true;
|
|
423
|
+
} else if (opts?.outcome !== undefined) {
|
|
424
|
+
where.skipped = false;
|
|
425
|
+
where.outcome = opts.outcome;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const [items, total] = await this.prisma.$transaction([
|
|
429
|
+
this.prisma.agentCronRun.findMany({
|
|
430
|
+
where,
|
|
431
|
+
orderBy: { startedAt: "desc" },
|
|
432
|
+
take: limit,
|
|
433
|
+
skip: offset,
|
|
434
|
+
include: {
|
|
435
|
+
modelBreakdown: true,
|
|
436
|
+
cron: { select: { id: true, name: true, schedule: true } },
|
|
437
|
+
phaseCron: { select: { id: true, name: true } },
|
|
438
|
+
},
|
|
439
|
+
}),
|
|
440
|
+
this.prisma.agentCronRun.count({ where }),
|
|
441
|
+
]);
|
|
442
|
+
|
|
443
|
+
return { items, total, limit, offset };
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* List runs across a set of agents, sorted descending by startedAt.
|
|
448
|
+
* Unlike `.listForAgent()`, this is scoped to a list of agent ids rather
|
|
449
|
+
* than a single one — used by fleet-wide views (e.g. an org dashboard)
|
|
450
|
+
* that need a globally-ordered feed across many agents' cron history.
|
|
451
|
+
* An empty agentIds array yields an empty result, not an error.
|
|
452
|
+
* Defaults to limit=20, offset=0.
|
|
453
|
+
*/
|
|
454
|
+
async listAcrossAgents(
|
|
455
|
+
agentIds: string[],
|
|
456
|
+
opts?: ListAcrossAgentsOptions,
|
|
457
|
+
): Promise<AgentCronRunListForAgent> {
|
|
458
|
+
const limit = opts?.limit ?? 20;
|
|
459
|
+
const offset = opts?.offset ?? 0;
|
|
460
|
+
|
|
461
|
+
const where: Prisma.AgentCronRunWhereInput = { agentId: { in: agentIds } };
|
|
462
|
+
if (opts?.cronId) {
|
|
463
|
+
where.cronId = opts.cronId;
|
|
464
|
+
}
|
|
465
|
+
if (opts?.outcome === "skipped") {
|
|
466
|
+
where.skipped = true;
|
|
467
|
+
} else if (opts?.outcome !== undefined) {
|
|
468
|
+
where.skipped = false;
|
|
469
|
+
where.outcome = opts.outcome;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const [items, total] = await this.prisma.$transaction([
|
|
473
|
+
this.prisma.agentCronRun.findMany({
|
|
474
|
+
where,
|
|
475
|
+
orderBy: { startedAt: "desc" },
|
|
476
|
+
take: limit,
|
|
477
|
+
skip: offset,
|
|
478
|
+
include: {
|
|
479
|
+
modelBreakdown: true,
|
|
480
|
+
cron: { select: { id: true, name: true, schedule: true } },
|
|
481
|
+
phaseCron: { select: { id: true, name: true } },
|
|
482
|
+
},
|
|
483
|
+
}),
|
|
484
|
+
this.prisma.agentCronRun.count({ where }),
|
|
485
|
+
]);
|
|
486
|
+
|
|
487
|
+
return { items, total, limit, offset };
|
|
488
|
+
}
|
|
489
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* admin/src/agent-deletion-checklist.ts
|
|
3
|
+
*
|
|
4
|
+
* Pure builder for the operator-facing "manual steps" checklist surfaced when
|
|
5
|
+
* an agent is deleted. Several agent secrets (GH_TOKEN, LINEAR_API_KEY,
|
|
6
|
+
* STRIPE_API_KEY, CLAUDE_CODE_OAUTH_TOKEN, ANTHROPIC_API_KEY, ...) are
|
|
7
|
+
* hand-pasted into AgentEnv with no stored ID/owner metadata, so no automated
|
|
8
|
+
* revocation is possible on agent delete. Rather than silently dropping them,
|
|
9
|
+
* this module turns the agent's `AgentEnv` rows into a reminder checklist for
|
|
10
|
+
* the operator to work through by hand.
|
|
11
|
+
*
|
|
12
|
+
* Uses the existing `AgentEnv.secret` boolean (checked by default in the "add
|
|
13
|
+
* env var" admin UI form) — no schema changes needed.
|
|
14
|
+
*
|
|
15
|
+
* This function is PURE — no fs, no network, no Prisma, no clock. It only
|
|
16
|
+
* shapes plain objects from plain objects, so it's unit-tested in isolation
|
|
17
|
+
* without a DB. See ./agent-manifest.ts for the same "pure builder" pattern.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/** A single operator-facing reminder produced for one AgentEnv row. */
|
|
21
|
+
export interface ManualStep {
|
|
22
|
+
/** The AgentEnv key this reminder is about (e.g. "GH_TOKEN"). */
|
|
23
|
+
key: string;
|
|
24
|
+
/** Human-readable instructions for the operator. */
|
|
25
|
+
message: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Per-agent Anthropic identity pieces still present after a failed removal.
|
|
30
|
+
* A null/absent piece is already gone and gets no entry.
|
|
31
|
+
*/
|
|
32
|
+
export interface LeftoverIdentity {
|
|
33
|
+
anthropicFederationRuleId?: string | null;
|
|
34
|
+
anthropicServiceAccountId?: string | null;
|
|
35
|
+
k8sServiceAccountName?: string | null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Keys handled by the Slack app deletion step (ADC-2.1 / ADC-3.1) — excluded
|
|
40
|
+
* entirely from this checklist so operators aren't told to do the same
|
|
41
|
+
* cleanup twice.
|
|
42
|
+
*/
|
|
43
|
+
const SLACK_APP_MANAGED_KEYS = new Set([
|
|
44
|
+
"SLACK_APP_ID",
|
|
45
|
+
"SLACK_SIGNING_SECRET",
|
|
46
|
+
"SLACK_BOT_TOKEN",
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Named/standard keys with specific revocation instructions. Exact-match on
|
|
51
|
+
* key name — a differently-cased key (e.g. "gh_token") falls through to the
|
|
52
|
+
* generic custom-secret message rather than being matched here, since
|
|
53
|
+
* AgentEnv keys are conventionally upper-snake-case and we'd rather surface
|
|
54
|
+
* an unexpected casing than silently apply the wrong instructions.
|
|
55
|
+
*/
|
|
56
|
+
const NAMED_KEY_INSTRUCTIONS: Record<string, string> = {
|
|
57
|
+
GH_TOKEN:
|
|
58
|
+
"Revoke this GitHub personal access token at https://github.com/settings/tokens (or the fine-grained PAT settings page).",
|
|
59
|
+
ANTHROPIC_API_KEY: "Revoke/rotate this key in the Anthropic console.",
|
|
60
|
+
CLAUDE_CODE_OAUTH_TOKEN:
|
|
61
|
+
"Revoke this token via `claude auth logout` or the Anthropic console.",
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Build the manual-steps checklist for the given `AgentEnv` rows.
|
|
66
|
+
*
|
|
67
|
+
* Bucketing:
|
|
68
|
+
* - Named/standard keys (GH_TOKEN, ANTHROPIC_API_KEY, CLAUDE_CODE_OAUTH_TOKEN)
|
|
69
|
+
* get a specific instruction.
|
|
70
|
+
* - Slack-app-managed keys (SLACK_APP_ID, SLACK_SIGNING_SECRET,
|
|
71
|
+
* SLACK_BOT_TOKEN) are excluded — handled by the Slack app deletion step.
|
|
72
|
+
* - Any other row with `secret: true` gets a generic "verify manually"
|
|
73
|
+
* reminder, naming the key.
|
|
74
|
+
* - Rows with `secret: false` never produce an entry, regardless of key name.
|
|
75
|
+
*
|
|
76
|
+
* Order-preserving and non-deduplicating: one `ManualStep` per matching input
|
|
77
|
+
* row, in input order (duplicate keys produce duplicate entries).
|
|
78
|
+
*
|
|
79
|
+
* `leftoverIdentity` (optional) appends one entry per per-agent Anthropic
|
|
80
|
+
* identity piece still present after a failed removal, in removal order:
|
|
81
|
+
* federation rule, Anthropic service account, Kubernetes ServiceAccount.
|
|
82
|
+
*/
|
|
83
|
+
export function buildManualStepsChecklist(
|
|
84
|
+
envRows: { key: string; secret: boolean }[],
|
|
85
|
+
leftoverIdentity?: LeftoverIdentity,
|
|
86
|
+
): ManualStep[] {
|
|
87
|
+
const steps: ManualStep[] = [];
|
|
88
|
+
|
|
89
|
+
for (const row of envRows) {
|
|
90
|
+
if (!row.secret) continue;
|
|
91
|
+
if (SLACK_APP_MANAGED_KEYS.has(row.key)) continue;
|
|
92
|
+
|
|
93
|
+
const namedInstruction = NAMED_KEY_INSTRUCTIONS[row.key];
|
|
94
|
+
if (namedInstruction !== undefined) {
|
|
95
|
+
steps.push({ key: row.key, message: namedInstruction });
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
steps.push({
|
|
100
|
+
key: row.key,
|
|
101
|
+
message: `Custom secret '${row.key}' was added manually and has no automated revocation — verify whether it needs to be revoked at the source.`,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Identity pieces the automated removal left behind, in removal order. The
|
|
106
|
+
// rule goes first: Anthropic refuses to archive a service account while a
|
|
107
|
+
// live rule references it.
|
|
108
|
+
if (leftoverIdentity?.anthropicFederationRuleId) {
|
|
109
|
+
steps.push({
|
|
110
|
+
key: "anthropicFederationRuleId",
|
|
111
|
+
message: `Anthropic federation rule ${leftoverIdentity.anthropicFederationRuleId} was not archived automatically — archive it in the Anthropic console first (a service account cannot be archived while a live rule references it).`,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
if (leftoverIdentity?.anthropicServiceAccountId) {
|
|
115
|
+
steps.push({
|
|
116
|
+
key: "anthropicServiceAccountId",
|
|
117
|
+
message: `Anthropic service account ${leftoverIdentity.anthropicServiceAccountId} was not archived automatically — archive it in the Anthropic console once its federation rule is archived.`,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
if (leftoverIdentity?.k8sServiceAccountName) {
|
|
121
|
+
steps.push({
|
|
122
|
+
key: "k8sServiceAccountName",
|
|
123
|
+
message: `Kubernetes ServiceAccount ${leftoverIdentity.k8sServiceAccountName} was not deleted automatically — delete it from the agent namespace with kubectl.`,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return steps;
|
|
128
|
+
}
|