@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,506 @@
|
|
|
1
|
+
// Shipwright — Admin Service Schema
|
|
2
|
+
// Owns: Agent, AgentEnv, AgentCronJob, AgentTool, AgentToken, AgentPlugin,
|
|
3
|
+
// plus the coding-owned models in coding.prisma (AgentWorkQueueSnapshot,
|
|
4
|
+
// AgentPhaseMethodology, SessionFollow, SessionAlertState).
|
|
5
|
+
// Agent is a first-class model (standalone, not embedded in a User model).
|
|
6
|
+
//
|
|
7
|
+
// WARNING: This schema uses DATABASE_URL_SHIPWRIGHT_ADMIN — a dedicated database
|
|
8
|
+
// for the admin service. Do NOT point this at a shared database.
|
|
9
|
+
|
|
10
|
+
// Prisma 7: the `prisma-client` generator (ESM, driver-adapter based) replaces
|
|
11
|
+
// `prisma-client-js`, and the datasource block no longer accepts `url` — the
|
|
12
|
+
// connection string for CLI/Migrate lives in ../../prisma.config.ts, and the
|
|
13
|
+
// runtime client connects through a PrismaPg driver adapter (see
|
|
14
|
+
// admin/src/prisma-client.ts).
|
|
15
|
+
generator client {
|
|
16
|
+
provider = "prisma-client"
|
|
17
|
+
output = "../client"
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
datasource db {
|
|
21
|
+
provider = "postgresql"
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ─── Agent ────────────────────────────────────────────────────────────────────
|
|
25
|
+
// A Shipwright agent: an autonomous runner that picks tasks, builds, and ships PRs.
|
|
26
|
+
|
|
27
|
+
model Agent {
|
|
28
|
+
id String @id @default(cuid())
|
|
29
|
+
name String
|
|
30
|
+
slackId String? @unique
|
|
31
|
+
slackBotToken String? // AES-256-GCM encrypted at the service layer
|
|
32
|
+
anthropicApiKey String? // AES-256-GCM encrypted at the service layer
|
|
33
|
+
selfHosted Boolean @default(false)
|
|
34
|
+
repos String[] @default([])
|
|
35
|
+
// DBR-2.1→2.4: renamed from authorAllowlist. DBR-2.1 added this column as
|
|
36
|
+
// a dual-written/dual-read twin of the old authorAllowlist column and
|
|
37
|
+
// backfilled it; DBR-2.2/2.3 migrated the admin UI and the agent's
|
|
38
|
+
// runtime read path onto this field exclusively; DBR-2.4 dropped the old
|
|
39
|
+
// authorAllowlist column now that nothing reads or writes it.
|
|
40
|
+
// CODING-OWNED (AEP-3.4): reviewAuthorAllowlist and patchAuthorAllowlist are
|
|
41
|
+
// read by the coding review/patch flows. Kept on Agent (no column move).
|
|
42
|
+
reviewAuthorAllowlist String[] @default([])
|
|
43
|
+
patchAuthorAllowlist String[] @default([])
|
|
44
|
+
restrictSlackToMembers Boolean @default(false)
|
|
45
|
+
typeName String @default("coding")
|
|
46
|
+
// SSP-1.2: owning account. Nullable/additive — existing agents keep NULL.
|
|
47
|
+
accountId String?
|
|
48
|
+
account Account? @relation(fields: [accountId], references: [id], onDelete: Restrict)
|
|
49
|
+
// ATE-1.1: trial expiry tracking. Both nullable/additive — null means "no
|
|
50
|
+
// trial configured" / "no warning sent yet". trialExpiresAt is settable via
|
|
51
|
+
// PATCH /agents/:id like other nullable scalar fields; trialExpiryWarnedAt
|
|
52
|
+
// is written internally by ATE-2.1's warning check (not user-editable via
|
|
53
|
+
// the PATCH route) but is included in GET/PATCH responses.
|
|
54
|
+
trialExpiresAt DateTime?
|
|
55
|
+
trialExpiryWarnedAt DateTime?
|
|
56
|
+
// PAI-1.1: owner and Anthropic identity. All nullable/additive — existing
|
|
57
|
+
// agents keep NULL. anthropicIdentityName is unique across agents.
|
|
58
|
+
ownerEmail String?
|
|
59
|
+
anthropicIdentityName String? @unique
|
|
60
|
+
anthropicServiceAccountId String?
|
|
61
|
+
anthropicFederationRuleId String?
|
|
62
|
+
k8sServiceAccountName String?
|
|
63
|
+
// APM-1.1: the 6 agent policy fields (auto_post_reviews, allow_self_review,
|
|
64
|
+
// min_confidence, max_findings, cleanup_merged_worktrees,
|
|
65
|
+
// cleanup_after_days). Originally mirrored from the legacy
|
|
66
|
+
// state/agent-policy.md; the DB is now the only source and each column
|
|
67
|
+
// carries its documented default.
|
|
68
|
+
// CODING-OWNED (AEP-3.4): autoPostReviews, allowSelfReview, minConfidence,
|
|
69
|
+
// maxFindings configure the coding review flow; cleanupMergedWorktrees and
|
|
70
|
+
// cleanupAfterDays below are runtime-owned.
|
|
71
|
+
autoPostReviews Boolean @default(true)
|
|
72
|
+
allowSelfReview Boolean @default(false)
|
|
73
|
+
minConfidence Int @default(75)
|
|
74
|
+
maxFindings Int @default(5)
|
|
75
|
+
cleanupMergedWorktrees Boolean @default(true)
|
|
76
|
+
cleanupAfterDays Int @default(14)
|
|
77
|
+
createdAt DateTime @default(now())
|
|
78
|
+
updatedAt DateTime @updatedAt
|
|
79
|
+
|
|
80
|
+
envVars AgentEnv[]
|
|
81
|
+
cronJobs AgentCronJob[]
|
|
82
|
+
tools AgentTool[]
|
|
83
|
+
tokens AgentToken[]
|
|
84
|
+
plugins AgentPlugin[]
|
|
85
|
+
members AgentMember[]
|
|
86
|
+
chatTokenUsage AgentChatTokenUsageDailyByModel[]
|
|
87
|
+
workQueueSnapshot AgentWorkQueueSnapshot?
|
|
88
|
+
githubInstallationsSnapshot AgentGitHubInstallationsSnapshot?
|
|
89
|
+
phaseMethodologies AgentPhaseMethodology[]
|
|
90
|
+
|
|
91
|
+
@@index([accountId])
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ─── Agent Env ────────────────────────────────────────────────────────────────
|
|
95
|
+
// Generic key/value env var store for deployed agents.
|
|
96
|
+
// Keys are env var names (e.g. ANTHROPIC_API_KEY). Values are AES-256-GCM encrypted.
|
|
97
|
+
|
|
98
|
+
model AgentEnv {
|
|
99
|
+
id String @id @default(cuid())
|
|
100
|
+
agentId String
|
|
101
|
+
agent Agent @relation(fields: [agentId], references: [id], onDelete: Cascade)
|
|
102
|
+
key String // env var name
|
|
103
|
+
value String // AES-256-GCM encrypted
|
|
104
|
+
secret Boolean @default(false) // when true, GET /agents/:id/envs masks the value as ***
|
|
105
|
+
createdAt DateTime @default(now())
|
|
106
|
+
updatedAt DateTime @updatedAt
|
|
107
|
+
|
|
108
|
+
@@unique([agentId, key])
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ─── Agent Cron Jobs ──────────────────────────────────────────────────────────
|
|
112
|
+
// Scheduled prompts per agent.
|
|
113
|
+
// Delivery target is either a Slack channel ID or a Slack user ID (DM).
|
|
114
|
+
// Validation that exactly one of channel/user is set happens in the service layer.
|
|
115
|
+
|
|
116
|
+
model AgentCronJob {
|
|
117
|
+
id String @id @default(cuid())
|
|
118
|
+
agentId String
|
|
119
|
+
agent Agent @relation(fields: [agentId], references: [id], onDelete: Cascade)
|
|
120
|
+
schedule String // cron expression e.g. "0 9 * * 1-5"
|
|
121
|
+
prompt String
|
|
122
|
+
channel String? // Slack channel ID (mutually exclusive with user)
|
|
123
|
+
user String? // Slack user ID for DMs
|
|
124
|
+
silent Boolean @default(false)
|
|
125
|
+
enabled Boolean @default(true)
|
|
126
|
+
preCheck String? // optional pre-check script e.g. "shipwright:check-docs-freshness.ts"
|
|
127
|
+
name String? // stable key for system crons; null for user-created crons
|
|
128
|
+
system Boolean @default(false)
|
|
129
|
+
// Set when the trial-expiry lockdown (not the user) disabled this cron, so
|
|
130
|
+
// restoreLockdownDisabled() can re-enable exactly those crons. Null for
|
|
131
|
+
// enabled crons and for crons the user disabled manually.
|
|
132
|
+
lockdownDisabledAt DateTime?
|
|
133
|
+
createdAt DateTime @default(now())
|
|
134
|
+
updatedAt DateTime @updatedAt
|
|
135
|
+
|
|
136
|
+
// Self-relation: a parent cron can have child "phase" crons (e.g. dev-task/
|
|
137
|
+
// review/patch/deploy phases of a single logical loop). Null for top-level
|
|
138
|
+
// crons. Deleting a parent cascades to its phase children.
|
|
139
|
+
parentCronId String?
|
|
140
|
+
parentCron AgentCronJob? @relation("CronPhases", fields: [parentCronId], references: [id], onDelete: Cascade)
|
|
141
|
+
phases AgentCronJob[] @relation("CronPhases")
|
|
142
|
+
|
|
143
|
+
runs AgentCronRun[]
|
|
144
|
+
phaseRuns AgentCronRun[] @relation("PhaseRuns")
|
|
145
|
+
|
|
146
|
+
@@index([parentCronId])
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ─── Agent Tools ──────────────────────────────────────────────────────────────
|
|
150
|
+
// Allowed tool patterns per agent. Each row grants an agent permission to use
|
|
151
|
+
// a specific tool pattern (e.g. "Read", "Bash", "Write"). The unique constraint
|
|
152
|
+
// on [agentId, pattern] prevents duplicates.
|
|
153
|
+
|
|
154
|
+
model AgentTool {
|
|
155
|
+
id String @id @default(cuid())
|
|
156
|
+
agentId String
|
|
157
|
+
agent Agent @relation(fields: [agentId], references: [id], onDelete: Cascade)
|
|
158
|
+
pattern String
|
|
159
|
+
enabled Boolean @default(true)
|
|
160
|
+
createdAt DateTime @default(now())
|
|
161
|
+
|
|
162
|
+
@@unique([agentId, pattern])
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ─── Agent Tokens ─────────────────────────────────────────────────────────────
|
|
166
|
+
// Scoped API tokens for agent authentication. The raw token is returned once at
|
|
167
|
+
// creation; only the SHA-256 hash is stored. Token validation is O(1) via the
|
|
168
|
+
// hash index.
|
|
169
|
+
|
|
170
|
+
model AgentToken {
|
|
171
|
+
id String @id @default(cuid())
|
|
172
|
+
agentId String
|
|
173
|
+
agent Agent @relation(fields: [agentId], references: [id], onDelete: Cascade)
|
|
174
|
+
token String @unique // SHA-256 hash of the raw token (hex)
|
|
175
|
+
label String?
|
|
176
|
+
createdAt DateTime @default(now())
|
|
177
|
+
revokedAt DateTime?
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ─── Agent Plugins ────────────────────────────────────────────────────────────
|
|
181
|
+
// Claude Code plugins installed per agent. Each row represents one installed
|
|
182
|
+
// plugin package. The unique constraint on [agentId, name] prevents duplicates.
|
|
183
|
+
|
|
184
|
+
model AgentPlugin {
|
|
185
|
+
id String @id @default(cuid())
|
|
186
|
+
agentId String
|
|
187
|
+
agent Agent @relation(fields: [agentId], references: [id], onDelete: Cascade)
|
|
188
|
+
name String // plugin package name e.g. "@shipwright/plugin"
|
|
189
|
+
version String? // pinned version; null = latest
|
|
190
|
+
enabled Boolean @default(true)
|
|
191
|
+
createdAt DateTime @default(now())
|
|
192
|
+
updatedAt DateTime @updatedAt
|
|
193
|
+
|
|
194
|
+
@@unique([agentId, name])
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ─── Agent Members ────────────────────────────────────────────────────────────
|
|
198
|
+
// Non-admin users granted web UI access to a specific agent.
|
|
199
|
+
// Admins (SHIPWRIGHT_ADMIN_ALLOWED_EMAILS) bypass this table entirely.
|
|
200
|
+
// Members can view and edit only the agents they are listed on.
|
|
201
|
+
|
|
202
|
+
model AgentMember {
|
|
203
|
+
id String @id @default(cuid())
|
|
204
|
+
agentId String
|
|
205
|
+
email String
|
|
206
|
+
createdAt DateTime @default(now())
|
|
207
|
+
agent Agent @relation(fields: [agentId], references: [id], onDelete: Cascade)
|
|
208
|
+
|
|
209
|
+
@@unique([agentId, email])
|
|
210
|
+
@@index([email])
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ─── Accounts ─────────────────────────────────────────────────────────────────
|
|
214
|
+
// SSP-1.2: self-serve agent provisioning. An Account owns agents and has
|
|
215
|
+
// members (email-keyed) and pending invites. Phase 1: one account per email
|
|
216
|
+
// (UNIQUE(email) on AccountMember). Enum-like strings are validated in the
|
|
217
|
+
// service layer, matching the rest of this schema.
|
|
218
|
+
|
|
219
|
+
model Account {
|
|
220
|
+
id String @id @default(cuid())
|
|
221
|
+
name String
|
|
222
|
+
status String @default("active") // 'active' | 'suspended' | 'trial_expired'
|
|
223
|
+
maxAgents Int @default(0)
|
|
224
|
+
plan String?
|
|
225
|
+
trialExpiresAt DateTime?
|
|
226
|
+
trialExpiryWarnedAt DateTime?
|
|
227
|
+
createdAt DateTime @default(now())
|
|
228
|
+
updatedAt DateTime @updatedAt
|
|
229
|
+
|
|
230
|
+
agents Agent[]
|
|
231
|
+
members AccountMember[]
|
|
232
|
+
invites AccountInvite[]
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
model AccountMember {
|
|
236
|
+
id String @id @default(cuid())
|
|
237
|
+
accountId String
|
|
238
|
+
email String @unique // lowercased; one account per email in Phase 1
|
|
239
|
+
role String // 'owner' | 'member'
|
|
240
|
+
createdAt DateTime @default(now())
|
|
241
|
+
account Account @relation(fields: [accountId], references: [id], onDelete: Cascade)
|
|
242
|
+
|
|
243
|
+
@@unique([accountId, email])
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
model AccountInvite {
|
|
247
|
+
id String @id @default(cuid())
|
|
248
|
+
accountId String
|
|
249
|
+
email String // lowercased
|
|
250
|
+
invitedBy String
|
|
251
|
+
createdAt DateTime @default(now())
|
|
252
|
+
acceptedAt DateTime?
|
|
253
|
+
account Account @relation(fields: [accountId], references: [id], onDelete: Cascade)
|
|
254
|
+
|
|
255
|
+
@@unique([accountId, email])
|
|
256
|
+
@@index([email])
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// ─── Agent Cron Runs ──────────────────────────────────────────────────────────
|
|
260
|
+
// Execution history for AgentCronJob firings. Each row records one cron invocation:
|
|
261
|
+
// whether it was skipped (pre-check returned false), the outcome, and any error.
|
|
262
|
+
// agentId is denormalized for efficient agent-level history queries.
|
|
263
|
+
|
|
264
|
+
model AgentCronRun {
|
|
265
|
+
id String @id @default(cuid())
|
|
266
|
+
cronId String
|
|
267
|
+
cron AgentCronJob @relation(fields: [cronId], references: [id], onDelete: Cascade)
|
|
268
|
+
agentId String // denormalized from cron.agentId for efficient queries
|
|
269
|
+
startedAt DateTime
|
|
270
|
+
completedAt DateTime?
|
|
271
|
+
skipped Boolean @default(false)
|
|
272
|
+
skipReason String?
|
|
273
|
+
outcome String?
|
|
274
|
+
error String?
|
|
275
|
+
itemType String? // work item type this run was dispatched against ("task" | "pr");
|
|
276
|
+
// null when the tick had no dispatch (skipped tick, empty queue)
|
|
277
|
+
itemId String? // work item id this run was dispatched against (e.g. "WLS-2.2" or
|
|
278
|
+
// "acme/x#123"); null when the tick had no dispatch
|
|
279
|
+
commandVariant String? // which slash-command variant a loop dispatch ran ("default" | "alternate");
|
|
280
|
+
// null for runs with no variant attribution
|
|
281
|
+
sessionId String? // Claude session id this cron run corresponds to
|
|
282
|
+
lastHeartbeatAt DateTime? // Most recent debounced progress-push (recordProgress())
|
|
283
|
+
// timestamp, sourced from the agent's injected Clock. Null
|
|
284
|
+
// for runs that never reported progress (short/legacy runs).
|
|
285
|
+
// Lets the admin UI show a "last checked in" signal — and a
|
|
286
|
+
// best-effort in-progress duration — for a run that stalls
|
|
287
|
+
// or crashes before reaching completedAt. Additive/nullable;
|
|
288
|
+
// no backfill for existing rows.
|
|
289
|
+
createdAt DateTime @default(now())
|
|
290
|
+
|
|
291
|
+
// Secondary/denormalized pointer to the phase cron (a child AgentCronJob)
|
|
292
|
+
// this run was dispatched by, distinct from cronId which always points at
|
|
293
|
+
// the top-level cron. Null until phase crons are wired up (LPC-1.2+).
|
|
294
|
+
// SetNull (not Cascade) on delete: deleting a phase cron shouldn't delete
|
|
295
|
+
// the parent cron's run history, which cronId's own Cascade already owns.
|
|
296
|
+
phaseId String?
|
|
297
|
+
phaseCron AgentCronJob? @relation("PhaseRuns", fields: [phaseId], references: [id], onDelete: SetNull)
|
|
298
|
+
|
|
299
|
+
// ── Run telemetry (prompt-audit metrics; all additive/nullable) ──────────
|
|
300
|
+
// First-turn context baseline: the usage of the run's first assistant
|
|
301
|
+
// message. baselineContextTokens = input + cacheCreation + cacheRead of
|
|
302
|
+
// that turn — the full always-loaded context (system prompt, tool schemas,
|
|
303
|
+
// CLAUDE.md/rules, skill listing, cron prompt) independent of cache
|
|
304
|
+
// warmth. Null for resumed sessions (their first turn replays history)
|
|
305
|
+
// and for runs reported by older agent builds.
|
|
306
|
+
baselineModel String?
|
|
307
|
+
baselineContextTokens Int?
|
|
308
|
+
baselineInputTokens Int?
|
|
309
|
+
baselineCacheCreationTokens Int?
|
|
310
|
+
baselineCacheReadTokens Int?
|
|
311
|
+
// Distinct usage-bearing assistant turns / distinct tool_use blocks.
|
|
312
|
+
turns Int?
|
|
313
|
+
toolCalls Int?
|
|
314
|
+
// sha256[:12] over the workspace's always-loaded markdown (CLAUDE.md, its
|
|
315
|
+
// @imports, no-`paths` rules) + plugin version — groups runs by what the
|
|
316
|
+
// model was given, so before/after series for a markdown change key on
|
|
317
|
+
// content rather than merge date. Computed agent-side.
|
|
318
|
+
contextFingerprint String?
|
|
319
|
+
pluginVersion String?
|
|
320
|
+
claudeCodeVersion String?
|
|
321
|
+
|
|
322
|
+
modelBreakdown AgentCronRunModelBreakdown[]
|
|
323
|
+
skillUsage AgentCronRunSkillUsage[]
|
|
324
|
+
|
|
325
|
+
@@index([cronId])
|
|
326
|
+
@@index([agentId, startedAt])
|
|
327
|
+
@@index([phaseId])
|
|
328
|
+
@@index([startedAt])
|
|
329
|
+
@@index([contextFingerprint])
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// ─── Agent Cron Run Skill Usage ──────────────────────────────────────────────
|
|
333
|
+
// Per-skill / per-subagent token attribution for a cron run, derived from the
|
|
334
|
+
// Claude CLI stream by the agent. `kind` is "skill" (a plugin
|
|
335
|
+
// skill invoked via the Skill tool), "agent" (a subagent type invoked via the
|
|
336
|
+
// Agent tool), or "root" (turns before any skill was invoked). Attribution is
|
|
337
|
+
// last-wins: every turn after an invoke is charged to the most recently
|
|
338
|
+
// invoked skill. invokeContextDelta is input + cacheCreation of the first
|
|
339
|
+
// turn after the skill's first invoke — the new context admitted when its
|
|
340
|
+
// body loaded. The unique constraint on [cronRunId, kind, name] allows upsert.
|
|
341
|
+
|
|
342
|
+
model AgentCronRunSkillUsage {
|
|
343
|
+
id String @id @default(cuid())
|
|
344
|
+
cronRunId String
|
|
345
|
+
kind String
|
|
346
|
+
name String
|
|
347
|
+
invocations Int @default(0)
|
|
348
|
+
turns Int @default(0)
|
|
349
|
+
inputTokens Int @default(0)
|
|
350
|
+
outputTokens Int @default(0)
|
|
351
|
+
cacheReadTokens Int @default(0)
|
|
352
|
+
cacheCreationTokens Int @default(0)
|
|
353
|
+
invokeContextDelta Int?
|
|
354
|
+
cronRun AgentCronRun @relation(fields: [cronRunId], references: [id], onDelete: Cascade)
|
|
355
|
+
|
|
356
|
+
@@unique([cronRunId, kind, name])
|
|
357
|
+
@@index([kind, name])
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// ─── Agent Cron Run Model Breakdown ──────────────────────────────────────────
|
|
361
|
+
// Per-model token breakdown for a cron run. Populated when sub-agents use
|
|
362
|
+
// different models in a single run. The unique constraint on [cronRunId, model]
|
|
363
|
+
// allows upsert semantics.
|
|
364
|
+
|
|
365
|
+
model AgentCronRunModelBreakdown {
|
|
366
|
+
id String @id @default(cuid())
|
|
367
|
+
cronRunId String
|
|
368
|
+
model String
|
|
369
|
+
inputTokens Int @default(0)
|
|
370
|
+
outputTokens Int @default(0)
|
|
371
|
+
cacheReadTokens Int @default(0)
|
|
372
|
+
cacheCreationTokens Int @default(0)
|
|
373
|
+
costUsd Float @default(0)
|
|
374
|
+
cronRun AgentCronRun @relation(fields: [cronRunId], references: [id], onDelete: Cascade)
|
|
375
|
+
|
|
376
|
+
@@unique([cronRunId, model])
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// ─── Agent Chat Token Usage (Daily, per Model) ───────────────────────────────
|
|
380
|
+
// Daily rollup table for Slack/chat session token usage per agent per model.
|
|
381
|
+
// Accumulated atomically via INSERT ... ON CONFLICT ... DO UPDATE (no read-modify-write).
|
|
382
|
+
// Totals and byAgent aggregations are computed by summing across models.
|
|
383
|
+
|
|
384
|
+
model AgentChatTokenUsageDailyByModel {
|
|
385
|
+
id String @id @default(cuid())
|
|
386
|
+
agentId String
|
|
387
|
+
agent Agent @relation(fields: [agentId], references: [id], onDelete: Cascade)
|
|
388
|
+
date String // YYYY-MM-DD
|
|
389
|
+
model String // e.g. "claude-sonnet-4-5"
|
|
390
|
+
inputTokens BigInt @default(0)
|
|
391
|
+
outputTokens BigInt @default(0)
|
|
392
|
+
cacheReadTokens BigInt @default(0)
|
|
393
|
+
cacheCreationTokens BigInt @default(0)
|
|
394
|
+
costUsd Float @default(0)
|
|
395
|
+
createdAt DateTime @default(now())
|
|
396
|
+
updatedAt DateTime @updatedAt
|
|
397
|
+
|
|
398
|
+
@@unique([agentId, date, model])
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// ─── Agent Ask ──────────────────────────────────────────────────────────────
|
|
402
|
+
// Audit row per agent-to-agent ask (COS-31.1, POST /agents/:id/ask). Written
|
|
403
|
+
// before the chat call and updated after it. The question text is never
|
|
404
|
+
// stored. No relation to Agent on purpose: the audit trail outlives either
|
|
405
|
+
// agent. threadId is null until the chat thread is created (and stays null
|
|
406
|
+
// when that call fails). status: created | pending | replied | failed.
|
|
407
|
+
|
|
408
|
+
model AgentAsk {
|
|
409
|
+
id String @id @default(cuid())
|
|
410
|
+
fromAgentId String
|
|
411
|
+
toAgentId String
|
|
412
|
+
threadId String?
|
|
413
|
+
depth Int
|
|
414
|
+
waitMs Int
|
|
415
|
+
ttlSeconds Int
|
|
416
|
+
status String
|
|
417
|
+
callerLabel String
|
|
418
|
+
createdAt DateTime @default(now())
|
|
419
|
+
repliedAt DateTime?
|
|
420
|
+
|
|
421
|
+
@@index([toAgentId, createdAt])
|
|
422
|
+
@@index([fromAgentId, createdAt])
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// ─── Agent GitHub Installations Snapshot ────────────────────────────────────
|
|
426
|
+
// One row per agent holding the agent's most recently reported GitHub App
|
|
427
|
+
// installations (owner, installation id, state, sanitized lastError). PUT
|
|
428
|
+
// replaces the whole list — a dropped installation disappears. No tokens or
|
|
429
|
+
// other credentials are ever stored here.
|
|
430
|
+
|
|
431
|
+
model AgentGitHubInstallationsSnapshot {
|
|
432
|
+
id String @id @default(cuid())
|
|
433
|
+
agentId String @unique
|
|
434
|
+
agent Agent @relation(fields: [agentId], references: [id], onDelete: Cascade)
|
|
435
|
+
reportedAt DateTime
|
|
436
|
+
installations Json // GitHubInstallation[]
|
|
437
|
+
createdAt DateTime @default(now())
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// ─── Push Subscription ──────────────────────────────────────────────────────
|
|
441
|
+
// A single browser/device Web Push subscription (RFC 8291) belonging to a
|
|
442
|
+
// user. One user may have many rows (multi-device). Keyed by the unique push
|
|
443
|
+
// `endpoint`; `p256dh` and `auth` are the subscription's public key + auth
|
|
444
|
+
// secret used to encrypt the payload. `detailOptIn` is the USER'S opt-in for
|
|
445
|
+
// how much notification detail they want on a locked screen — the effective
|
|
446
|
+
// level is min(operator ceiling SHIPWRIGHT_ADMIN_PUSH_MAX_DETAIL, detailOptIn).
|
|
447
|
+
// Values: "generic" | "title" | "preview". Never carries an agentId — targeting
|
|
448
|
+
// is by userEmail via ChatThreadWatch, so a subscription is agent-agnostic.
|
|
449
|
+
model PushSubscription {
|
|
450
|
+
id String @id @default(cuid())
|
|
451
|
+
userEmail String
|
|
452
|
+
endpoint String @unique
|
|
453
|
+
p256dh String // base64url of the UA public key
|
|
454
|
+
auth String // base64url of the UA auth secret
|
|
455
|
+
detailOptIn String @default("generic") // user opt-in: generic | title | preview
|
|
456
|
+
createdAt DateTime @default(now())
|
|
457
|
+
updatedAt DateTime @updatedAt
|
|
458
|
+
|
|
459
|
+
@@index([userEmail])
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// ─── Chat Thread Watch ──────────────────────────────────────────────────────
|
|
463
|
+
// Records that a user is "watching" a chat thread so the agent's reply can be
|
|
464
|
+
// targeted to exactly the right subscriber(s) without fanning out to every
|
|
465
|
+
// member of the agent. Upserted whenever a user sends a message in a thread.
|
|
466
|
+
// The chat Message model has no author column, and admin is the only web front
|
|
467
|
+
// door that knows both userEmail and threadId at send time — so authorship
|
|
468
|
+
// targeting lives here, not in the chat service.
|
|
469
|
+
model ChatThreadWatch {
|
|
470
|
+
id String @id @default(cuid())
|
|
471
|
+
userEmail String
|
|
472
|
+
threadId String
|
|
473
|
+
agentId String
|
|
474
|
+
createdAt DateTime @default(now())
|
|
475
|
+
updatedAt DateTime @updatedAt
|
|
476
|
+
|
|
477
|
+
@@unique([userEmail, threadId])
|
|
478
|
+
@@index([threadId])
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// ─── User Notification Prefs ─────────────────────────────────────────────────
|
|
482
|
+
// One row per user, holding their notification preferences. `userEmail` is
|
|
483
|
+
// the primary key (one row per user, not per session). `autoFollowSessions`
|
|
484
|
+
// controls whether sessions *visible to* the user (any session in an agent or
|
|
485
|
+
// repo they can see — not only sessions they started) are auto-followed by
|
|
486
|
+
// session-alert-sweeper.ts; `autoFollowSince` records when that behavior was
|
|
487
|
+
// turned on (null if never explicitly set), and the sweeper skips
|
|
488
|
+
// auto-following sessions that were already waiting before that moment. When
|
|
489
|
+
// it is null the sweeper falls back to this row's `createdAt` as the boundary
|
|
490
|
+
// — `autoFollowSessions` defaults to true and a row is created just by loading
|
|
491
|
+
// the settings page, so "null" must not mean "no boundary", or the first sweep
|
|
492
|
+
// would back-follow and immediately push the whole waiting backlog. Rows that
|
|
493
|
+
// predate the sweeper were stamped once by
|
|
494
|
+
// 20260911000000_backfill_auto_follow_since.
|
|
495
|
+
// `reminderHourLocal` is validated to be an integer in
|
|
496
|
+
// [0, 23] at the service layer (session-follow-service.ts), not here — Prisma
|
|
497
|
+
// has no native CHECK constraint support in this schema style.
|
|
498
|
+
model UserNotificationPrefs {
|
|
499
|
+
userEmail String @id
|
|
500
|
+
autoFollowSessions Boolean @default(true)
|
|
501
|
+
reminderHourLocal Int @default(9)
|
|
502
|
+
autoFollowSince DateTime?
|
|
503
|
+
createdAt DateTime @default(now())
|
|
504
|
+
updatedAt DateTime @updatedAt
|
|
505
|
+
}
|
|
506
|
+
|
package/prisma.config.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* admin/prisma.config.ts
|
|
3
|
+
*
|
|
4
|
+
* Prisma 7 CLI configuration for the admin service.
|
|
5
|
+
*
|
|
6
|
+
* Prisma 7 removed `url` from the `datasource` block in the schema (prisma/schema/), so the
|
|
7
|
+
* connection string used by CLI commands (`prisma migrate deploy|dev`,
|
|
8
|
+
* `prisma db ...`) is declared here instead. The *runtime* client does not read
|
|
9
|
+
* this file at all — it connects through a `PrismaPg` driver adapter built in
|
|
10
|
+
* admin/src/prisma-client.ts.
|
|
11
|
+
*
|
|
12
|
+
* `datasource.url` is read straight off `process.env` rather than Prisma's
|
|
13
|
+
* `env()` helper on purpose: `env()` throws when the variable is unset, which
|
|
14
|
+
* would break `prisma generate` (run in `postinstall` and in admin/Dockerfile)
|
|
15
|
+
* in environments that legitimately have no database — generate needs the
|
|
16
|
+
* schema, not a connection. Leaving it `undefined` defers the failure to the
|
|
17
|
+
* commands that actually need a URL, which report it themselves.
|
|
18
|
+
*
|
|
19
|
+
* Paths are relative to this file (admin/), matching the existing
|
|
20
|
+
* `--schema=prisma/schema` invocations in package.json, Taskfile.yml,
|
|
21
|
+
* admin/Dockerfile, scripts/hitl.ts, and scripts/dev-tmux.ts.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { defineConfig } from "prisma/config";
|
|
25
|
+
|
|
26
|
+
export default defineConfig({
|
|
27
|
+
schema: "prisma/schema",
|
|
28
|
+
migrations: {
|
|
29
|
+
path: "prisma/migrations",
|
|
30
|
+
},
|
|
31
|
+
datasource: {
|
|
32
|
+
url: process.env.DATABASE_URL_SHIPWRIGHT_ADMIN,
|
|
33
|
+
},
|
|
34
|
+
});
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* admin/src/account-agent-create.ts
|
|
3
|
+
*
|
|
4
|
+
* Shared rules for self-serve agent creation by account users (SSP-4.2),
|
|
5
|
+
* used by both the admin UI form handler and the JSON API route so the two
|
|
6
|
+
* cannot drift: account users always create "coding" agents stamped with
|
|
7
|
+
* THEIR account (never a client-supplied one), and quota / account-status
|
|
8
|
+
* failures surface as clear messages.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { CallerScope } from "./caller-scope.ts";
|
|
12
|
+
import { resolveDefaultSpecialtyName } from "./specialty-manifest-loader.ts";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The only specialty account users may create: SHIPWRIGHT_SELF_SERVE_AGENT_TYPE
|
|
16
|
+
* when set, else the platform's default specialty (SHIPWRIGHT_DEFAULT_AGENT_TYPE,
|
|
17
|
+
* "coding" when unset).
|
|
18
|
+
*/
|
|
19
|
+
export function getSelfServeSpecialty(
|
|
20
|
+
env: Record<string, string | undefined> = process.env,
|
|
21
|
+
): string {
|
|
22
|
+
return (
|
|
23
|
+
env.SHIPWRIGHT_SELF_SERVE_AGENT_TYPE || resolveDefaultSpecialtyName(env)
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Account the caller may create agents in, or null when they have none. */
|
|
28
|
+
export function accountIdFromScope(scope: CallerScope): string | null {
|
|
29
|
+
return scope.kind === "scoped" ? scope.accountId : null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Clear, user-facing messages for the account-specific createAgent() errors. */
|
|
33
|
+
export const ACCOUNT_CREATE_ERROR_MESSAGES = {
|
|
34
|
+
quota_exceeded: "This account has reached its agent limit.",
|
|
35
|
+
account_inactive: "This account is not active.",
|
|
36
|
+
} as const;
|
|
37
|
+
|
|
38
|
+
export function isAccountCreateErrorCode(
|
|
39
|
+
code: string,
|
|
40
|
+
): code is keyof typeof ACCOUNT_CREATE_ERROR_MESSAGES {
|
|
41
|
+
return code in ACCOUNT_CREATE_ERROR_MESSAGES;
|
|
42
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* admin/src/account-agent-create.unit.test.ts
|
|
3
|
+
* Unit tests for self-serve specialty resolution (AEP-1.2).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, expect, it } from "bun:test";
|
|
7
|
+
import { getSelfServeSpecialty } from "./account-agent-create.ts";
|
|
8
|
+
|
|
9
|
+
describe("getSelfServeSpecialty", () => {
|
|
10
|
+
it("defaults to coding", () => {
|
|
11
|
+
expect(getSelfServeSpecialty({})).toBe("coding");
|
|
12
|
+
expect(
|
|
13
|
+
getSelfServeSpecialty({ SHIPWRIGHT_DEFAULT_AGENT_TYPE: "fixture" }),
|
|
14
|
+
).toBe("fixture");
|
|
15
|
+
expect(
|
|
16
|
+
getSelfServeSpecialty({
|
|
17
|
+
SHIPWRIGHT_DEFAULT_AGENT_TYPE: "fixture",
|
|
18
|
+
SHIPWRIGHT_SELF_SERVE_AGENT_TYPE: "other",
|
|
19
|
+
}),
|
|
20
|
+
).toBe("other");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("honours SHIPWRIGHT_SELF_SERVE_AGENT_TYPE", () => {
|
|
24
|
+
expect(
|
|
25
|
+
getSelfServeSpecialty({ SHIPWRIGHT_SELF_SERVE_AGENT_TYPE: "base" }),
|
|
26
|
+
).toBe("base");
|
|
27
|
+
});
|
|
28
|
+
});
|