@app-vitals/agent-admin 0.0.1 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* admin/e2e/chat-page.e2e.ts
|
|
3
|
+
* Admin UI — Chat List Page E2E Tests (CFB-3.1)
|
|
4
|
+
*
|
|
5
|
+
* Covers the mobile responsive defects on GET /admin/chat (the top-level
|
|
6
|
+
* agent selector + thread list page, distinct from the per-thread page
|
|
7
|
+
* covered by chat-thread-page.e2e.ts):
|
|
8
|
+
* - Every visible input/select computes font-size >= 16px at 375px (iOS
|
|
9
|
+
* Safari zoom-on-focus guard).
|
|
10
|
+
* - No horizontal overflow at 375px and 768px.
|
|
11
|
+
*
|
|
12
|
+
* Architecture:
|
|
13
|
+
* - Spawns admin/e2e/test-server.ts via Bun as a child process.
|
|
14
|
+
* - Uses hono/jwt sign() to mint valid session cookies directly.
|
|
15
|
+
* - No real DB or OAuth flow is initiated.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { type ChildProcess, spawn } from "node:child_process";
|
|
19
|
+
import { dirname, resolve } from "node:path";
|
|
20
|
+
import { fileURLToPath } from "node:url";
|
|
21
|
+
import { type BrowserContext, type Page, expect, test } from "@playwright/test";
|
|
22
|
+
import { sign } from "hono/jwt";
|
|
23
|
+
|
|
24
|
+
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
const PORT = 3493;
|
|
27
|
+
const BASE_URL = `http://localhost:${PORT}`;
|
|
28
|
+
const SESSION_SECRET = "e2e-admin-test-secret-32chars!!!";
|
|
29
|
+
const SESSION_COOKIE = "admin_session";
|
|
30
|
+
const AGENT_ID = "agent-e2e-1";
|
|
31
|
+
|
|
32
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
33
|
+
|
|
34
|
+
// ─── Server lifecycle ─────────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
let serverProcess: ChildProcess | null = null;
|
|
37
|
+
|
|
38
|
+
async function waitForServer(url: string, maxWaitMs: number): Promise<void> {
|
|
39
|
+
const start = Date.now();
|
|
40
|
+
while (Date.now() - start < maxWaitMs) {
|
|
41
|
+
try {
|
|
42
|
+
const res = await fetch(url);
|
|
43
|
+
if (res.status < 500) return;
|
|
44
|
+
} catch {
|
|
45
|
+
// Not ready yet
|
|
46
|
+
}
|
|
47
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
48
|
+
}
|
|
49
|
+
throw new Error(`Server at ${url} did not start within ${maxWaitMs}ms`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
test.beforeAll(async () => {
|
|
53
|
+
const serverScript = resolve(__dirname, "test-server.ts");
|
|
54
|
+
|
|
55
|
+
serverProcess = spawn("bun", ["run", serverScript], {
|
|
56
|
+
env: {
|
|
57
|
+
...process.env,
|
|
58
|
+
ADMIN_E2E_PORT: String(PORT),
|
|
59
|
+
ADMIN_E2E_SESSION_SECRET: SESSION_SECRET,
|
|
60
|
+
},
|
|
61
|
+
stdio: "pipe",
|
|
62
|
+
cwd: resolve(__dirname, "../.."),
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
serverProcess.stderr?.on("data", (data: Buffer) => {
|
|
66
|
+
const msg = data.toString();
|
|
67
|
+
if (msg.trim()) console.error("[admin-e2e-chat-page-server]", msg.trim());
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
serverProcess.stdout?.on("data", (data: Buffer) => {
|
|
71
|
+
const msg = data.toString();
|
|
72
|
+
if (msg.trim()) console.log("[admin-e2e-chat-page-server]", msg.trim());
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
await waitForServer(`${BASE_URL}/health`, 15_000);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test.afterAll(() => {
|
|
79
|
+
if (serverProcess) {
|
|
80
|
+
serverProcess.kill("SIGTERM");
|
|
81
|
+
serverProcess = null;
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// ─── Session cookie helper ────────────────────────────────────────────────────
|
|
86
|
+
|
|
87
|
+
async function mintSession(
|
|
88
|
+
userId = "google-sub-e2e",
|
|
89
|
+
email = "admin@example.com",
|
|
90
|
+
): Promise<string> {
|
|
91
|
+
const nowSec = Math.floor(Date.now() / 1000);
|
|
92
|
+
return sign(
|
|
93
|
+
{ userId, email, isAdmin: true, iat: nowSec, exp: nowSec + 3600 },
|
|
94
|
+
SESSION_SECRET,
|
|
95
|
+
"HS256",
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ─── Helper: load chat list page with auth ────────────────────────────────────
|
|
100
|
+
|
|
101
|
+
async function loadChatPage(
|
|
102
|
+
page: Page,
|
|
103
|
+
context: BrowserContext,
|
|
104
|
+
): Promise<void> {
|
|
105
|
+
const token = await mintSession();
|
|
106
|
+
await context.addCookies([
|
|
107
|
+
{
|
|
108
|
+
name: SESSION_COOKIE,
|
|
109
|
+
value: token,
|
|
110
|
+
domain: "localhost",
|
|
111
|
+
path: "/",
|
|
112
|
+
httpOnly: true,
|
|
113
|
+
sameSite: "Lax",
|
|
114
|
+
},
|
|
115
|
+
]);
|
|
116
|
+
await page.goto(`${BASE_URL}/admin/chat?agentId=${AGENT_ID}`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ─── Tests ────────────────────────────────────────────────────────────────────
|
|
120
|
+
|
|
121
|
+
test.describe("GET /admin/chat — CFB-3.1 mobile responsive", () => {
|
|
122
|
+
test("at 375px, every visible input/select computes font-size >= 16px", async ({
|
|
123
|
+
page,
|
|
124
|
+
context,
|
|
125
|
+
}) => {
|
|
126
|
+
await page.setViewportSize({ width: 375, height: 812 });
|
|
127
|
+
await loadChatPage(page, context);
|
|
128
|
+
|
|
129
|
+
const fontSizes = await page.evaluate(() => {
|
|
130
|
+
const els = Array.from(
|
|
131
|
+
document.querySelectorAll("input, textarea, select"),
|
|
132
|
+
) as HTMLElement[];
|
|
133
|
+
return els
|
|
134
|
+
.filter((el) => {
|
|
135
|
+
const style = getComputedStyle(el);
|
|
136
|
+
return (
|
|
137
|
+
style.display !== "none" &&
|
|
138
|
+
style.visibility !== "hidden" &&
|
|
139
|
+
(el as HTMLInputElement).type !== "hidden"
|
|
140
|
+
);
|
|
141
|
+
})
|
|
142
|
+
.map((el) => ({
|
|
143
|
+
id: el.id || el.tagName,
|
|
144
|
+
fontSize: Number.parseFloat(getComputedStyle(el).fontSize),
|
|
145
|
+
}));
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
expect(fontSizes.length).toBeGreaterThan(0);
|
|
149
|
+
for (const { id, fontSize } of fontSizes) {
|
|
150
|
+
expect(fontSize, `${id} font-size`).toBeGreaterThanOrEqual(16);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("at 375px and 768px, no horizontal overflow", async ({
|
|
155
|
+
page,
|
|
156
|
+
context,
|
|
157
|
+
}) => {
|
|
158
|
+
for (const width of [375, 768]) {
|
|
159
|
+
await page.setViewportSize({ width, height: 812 });
|
|
160
|
+
await loadChatPage(page, context);
|
|
161
|
+
|
|
162
|
+
const { scrollWidth, clientWidth } = await page.evaluate(() => ({
|
|
163
|
+
scrollWidth: document.documentElement.scrollWidth,
|
|
164
|
+
clientWidth: document.documentElement.clientWidth,
|
|
165
|
+
}));
|
|
166
|
+
expect(scrollWidth, `width=${width}`).toBeLessThanOrEqual(clientWidth);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
});
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* admin/e2e/chat-progress.e2e.ts
|
|
3
|
+
* Admin UI — Chat Live Progress E2E Tests (CFB-2.3)
|
|
4
|
+
*
|
|
5
|
+
* Proves the two live-progress guarantees the owner asked for, in a real
|
|
6
|
+
* browser against the mock chat server (ADMIN_E2E_CHAT_PENDING=1 makes the
|
|
7
|
+
* thread's last message an UNREPLIED user message whose progressSeq is FROZEN):
|
|
8
|
+
*
|
|
9
|
+
* 1. Layer-1 elapsed ticker: the "working… (Ns)" value visibly increments
|
|
10
|
+
* across two DOM reads ~2s apart. The ticker computes now - createdAt on a
|
|
11
|
+
* 1s client-side interval with ZERO network dependency — so this is a real
|
|
12
|
+
* wall-clock assertion, not a fake-clock unit test.
|
|
13
|
+
*
|
|
14
|
+
* 2. Stall state: because progressSeq never advances, the live status bubble
|
|
15
|
+
* gains .chat-stall-indicator once STALL_WARN_AFTER_MS elapses. A real
|
|
16
|
+
* 120s wait would be a bad test, so the route accepts a
|
|
17
|
+
* ?stallWarnAfterMs=<small> override (production defaults to 120000).
|
|
18
|
+
*
|
|
19
|
+
* Architecture mirrors chat-thread-page.e2e.ts: spawns admin/e2e/test-server.ts
|
|
20
|
+
* via Bun as a child process, mints session cookies via hono/jwt.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { type ChildProcess, spawn } from "node:child_process";
|
|
24
|
+
import { dirname, resolve } from "node:path";
|
|
25
|
+
import { fileURLToPath } from "node:url";
|
|
26
|
+
import { type BrowserContext, type Page, expect, test } from "@playwright/test";
|
|
27
|
+
import { sign } from "hono/jwt";
|
|
28
|
+
|
|
29
|
+
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
const PORT = 3494;
|
|
32
|
+
const BASE_URL = `http://localhost:${PORT}`;
|
|
33
|
+
const SESSION_SECRET = "e2e-admin-test-secret-32chars!!!";
|
|
34
|
+
const SESSION_COOKIE = "admin_session";
|
|
35
|
+
const AGENT_ID = "agent-e2e-1";
|
|
36
|
+
// Matches MOCK_CHAT_THREAD.id in test-server.ts.
|
|
37
|
+
const THREAD_ID = "thread-e2e-1";
|
|
38
|
+
|
|
39
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
40
|
+
|
|
41
|
+
// ─── Server lifecycle ─────────────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
let serverProcess: ChildProcess | null = null;
|
|
44
|
+
|
|
45
|
+
async function waitForServer(url: string, maxWaitMs: number): Promise<void> {
|
|
46
|
+
const start = Date.now();
|
|
47
|
+
while (Date.now() - start < maxWaitMs) {
|
|
48
|
+
try {
|
|
49
|
+
const res = await fetch(url);
|
|
50
|
+
if (res.status < 500) return;
|
|
51
|
+
} catch {
|
|
52
|
+
// Not ready yet
|
|
53
|
+
}
|
|
54
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
55
|
+
}
|
|
56
|
+
throw new Error(`Server at ${url} did not start within ${maxWaitMs}ms`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
test.beforeAll(async () => {
|
|
60
|
+
const serverScript = resolve(__dirname, "test-server.ts");
|
|
61
|
+
|
|
62
|
+
serverProcess = spawn("bun", ["run", serverScript], {
|
|
63
|
+
env: {
|
|
64
|
+
...process.env,
|
|
65
|
+
ADMIN_E2E_PORT: String(PORT),
|
|
66
|
+
ADMIN_E2E_SESSION_SECRET: SESSION_SECRET,
|
|
67
|
+
// Make the thread's last message a frozen-progress pending user message.
|
|
68
|
+
ADMIN_E2E_CHAT_PENDING: "1",
|
|
69
|
+
},
|
|
70
|
+
stdio: "pipe",
|
|
71
|
+
cwd: resolve(__dirname, "../.."),
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
serverProcess.stderr?.on("data", (data: Buffer) => {
|
|
75
|
+
const msg = data.toString();
|
|
76
|
+
if (msg.trim())
|
|
77
|
+
console.error("[admin-e2e-chat-progress-server]", msg.trim());
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
serverProcess.stdout?.on("data", (data: Buffer) => {
|
|
81
|
+
const msg = data.toString();
|
|
82
|
+
if (msg.trim()) console.log("[admin-e2e-chat-progress-server]", msg.trim());
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
await waitForServer(`${BASE_URL}/health`, 15_000);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test.afterAll(() => {
|
|
89
|
+
if (serverProcess) {
|
|
90
|
+
serverProcess.kill("SIGTERM");
|
|
91
|
+
serverProcess = null;
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// ─── Session cookie helper ────────────────────────────────────────────────────
|
|
96
|
+
|
|
97
|
+
async function mintSession(
|
|
98
|
+
userId = "google-sub-e2e",
|
|
99
|
+
email = "admin@example.com",
|
|
100
|
+
): Promise<string> {
|
|
101
|
+
const nowSec = Math.floor(Date.now() / 1000);
|
|
102
|
+
return sign(
|
|
103
|
+
{ userId, email, isAdmin: true, iat: nowSec, exp: nowSec + 3600 },
|
|
104
|
+
SESSION_SECRET,
|
|
105
|
+
"HS256",
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function loadThread(
|
|
110
|
+
page: Page,
|
|
111
|
+
context: BrowserContext,
|
|
112
|
+
query = "",
|
|
113
|
+
): Promise<void> {
|
|
114
|
+
const token = await mintSession();
|
|
115
|
+
await context.addCookies([
|
|
116
|
+
{
|
|
117
|
+
name: SESSION_COOKIE,
|
|
118
|
+
value: token,
|
|
119
|
+
domain: "localhost",
|
|
120
|
+
path: "/",
|
|
121
|
+
httpOnly: true,
|
|
122
|
+
sameSite: "Lax",
|
|
123
|
+
},
|
|
124
|
+
]);
|
|
125
|
+
await page.goto(
|
|
126
|
+
`${BASE_URL}/admin/chat/${AGENT_ID}/threads/${THREAD_ID}${query}`,
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Parse the integer seconds out of "working… (12s)". */
|
|
131
|
+
function parseElapsedSeconds(text: string | null): number {
|
|
132
|
+
const m = (text ?? "").match(/\((\d+)s\)/);
|
|
133
|
+
return m ? Number.parseInt(m[1], 10) : Number.NaN;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ─── Tests ────────────────────────────────────────────────────────────────────
|
|
137
|
+
|
|
138
|
+
test.describe("GET /admin/chat/:agentId/threads/:threadId — CFB-2.3 live progress", () => {
|
|
139
|
+
test("the server renders the live status bubble for a pending message", async ({
|
|
140
|
+
page,
|
|
141
|
+
context,
|
|
142
|
+
}) => {
|
|
143
|
+
await loadThread(page, context);
|
|
144
|
+
await expect(page.locator("#live-status-bubble")).toBeVisible();
|
|
145
|
+
await expect(page.locator("#live-status-elapsed")).toBeVisible();
|
|
146
|
+
// progressPhase "reading" → "Reading files…" milestone.
|
|
147
|
+
await expect(page.locator("#live-status-milestone")).toHaveText(
|
|
148
|
+
"Reading files…",
|
|
149
|
+
);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("elapsed ticker visibly increments across two DOM reads ~2s apart", async ({
|
|
153
|
+
page,
|
|
154
|
+
context,
|
|
155
|
+
}) => {
|
|
156
|
+
await loadThread(page, context);
|
|
157
|
+
|
|
158
|
+
const elapsed = page.locator("#live-status-elapsed");
|
|
159
|
+
await expect(elapsed).toBeVisible();
|
|
160
|
+
|
|
161
|
+
const first = parseElapsedSeconds(await elapsed.textContent());
|
|
162
|
+
expect(Number.isNaN(first)).toBe(false);
|
|
163
|
+
|
|
164
|
+
// Real-time wait — the 1s ticker must advance the value with no network.
|
|
165
|
+
await page.waitForTimeout(2500);
|
|
166
|
+
|
|
167
|
+
const second = parseElapsedSeconds(await elapsed.textContent());
|
|
168
|
+
expect(Number.isNaN(second)).toBe(false);
|
|
169
|
+
expect(second).toBeGreaterThan(first);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test("elapsed keeps ticking even with the poll endpoint failing (zero network dependency)", async ({
|
|
173
|
+
page,
|
|
174
|
+
context,
|
|
175
|
+
}) => {
|
|
176
|
+
// Block the messages.json poll entirely — Layer 1 must be immune.
|
|
177
|
+
await context.route("**/messages.json*", (route) => route.abort());
|
|
178
|
+
await loadThread(page, context);
|
|
179
|
+
|
|
180
|
+
const elapsed = page.locator("#live-status-elapsed");
|
|
181
|
+
await expect(elapsed).toBeVisible();
|
|
182
|
+
const first = parseElapsedSeconds(await elapsed.textContent());
|
|
183
|
+
await page.waitForTimeout(2500);
|
|
184
|
+
const second = parseElapsedSeconds(await elapsed.textContent());
|
|
185
|
+
expect(second).toBeGreaterThan(first);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("stall state appears once progressSeq stays frozen past the threshold", async ({
|
|
189
|
+
page,
|
|
190
|
+
context,
|
|
191
|
+
}) => {
|
|
192
|
+
// Shrink the stall threshold so we don't wait a real 120s. progressSeq is
|
|
193
|
+
// frozen in the fixture, so the stall class should appear shortly after.
|
|
194
|
+
await loadThread(page, context, "?stallWarnAfterMs=1000");
|
|
195
|
+
|
|
196
|
+
const live = page.locator("#live-status-bubble");
|
|
197
|
+
await expect(live).toBeVisible();
|
|
198
|
+
|
|
199
|
+
// Not stalled immediately on load.
|
|
200
|
+
await expect(live).not.toHaveClass(/chat-stall-indicator/);
|
|
201
|
+
|
|
202
|
+
// After the (shortened) threshold elapses with progressSeq frozen, the
|
|
203
|
+
// stall indicator class is applied by the 1s ticker.
|
|
204
|
+
await expect(live).toHaveClass(/chat-stall-indicator/, { timeout: 5000 });
|
|
205
|
+
});
|
|
206
|
+
});
|