availsync 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/.adal/skills/stripe-best-practices/SKILL.md +42 -0
- package/.adal/skills/stripe-best-practices/references/billing.md +36 -0
- package/.adal/skills/stripe-best-practices/references/connect.md +48 -0
- package/.adal/skills/stripe-best-practices/references/payments.md +79 -0
- package/.adal/skills/stripe-best-practices/references/security.md +109 -0
- package/.adal/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.adal/skills/stripe-projects/SKILL.md +139 -0
- package/.adal/skills/upgrade-stripe/SKILL.md +185 -0
- package/.agents/skills/stripe-best-practices/SKILL.md +42 -0
- package/.agents/skills/stripe-best-practices/references/billing.md +36 -0
- package/.agents/skills/stripe-best-practices/references/connect.md +48 -0
- package/.agents/skills/stripe-best-practices/references/payments.md +79 -0
- package/.agents/skills/stripe-best-practices/references/security.md +109 -0
- package/.agents/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.agents/skills/stripe-projects/SKILL.md +139 -0
- package/.agents/skills/upgrade-stripe/SKILL.md +185 -0
- package/.augment/skills/stripe-best-practices/SKILL.md +42 -0
- package/.augment/skills/stripe-best-practices/references/billing.md +36 -0
- package/.augment/skills/stripe-best-practices/references/connect.md +48 -0
- package/.augment/skills/stripe-best-practices/references/payments.md +79 -0
- package/.augment/skills/stripe-best-practices/references/security.md +109 -0
- package/.augment/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.augment/skills/stripe-projects/SKILL.md +139 -0
- package/.augment/skills/upgrade-stripe/SKILL.md +185 -0
- package/.bob/skills/stripe-best-practices/SKILL.md +42 -0
- package/.bob/skills/stripe-best-practices/references/billing.md +36 -0
- package/.bob/skills/stripe-best-practices/references/connect.md +48 -0
- package/.bob/skills/stripe-best-practices/references/payments.md +79 -0
- package/.bob/skills/stripe-best-practices/references/security.md +109 -0
- package/.bob/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.bob/skills/stripe-projects/SKILL.md +139 -0
- package/.bob/skills/upgrade-stripe/SKILL.md +185 -0
- package/.claude/settings.local.json +7 -0
- package/.claude/skills/stripe-best-practices/SKILL.md +42 -0
- package/.claude/skills/stripe-best-practices/references/billing.md +36 -0
- package/.claude/skills/stripe-best-practices/references/connect.md +48 -0
- package/.claude/skills/stripe-best-practices/references/payments.md +79 -0
- package/.claude/skills/stripe-best-practices/references/security.md +109 -0
- package/.claude/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.claude/skills/stripe-projects/SKILL.md +139 -0
- package/.claude/skills/upgrade-stripe/SKILL.md +185 -0
- package/.codebuddy/skills/stripe-best-practices/SKILL.md +42 -0
- package/.codebuddy/skills/stripe-best-practices/references/billing.md +36 -0
- package/.codebuddy/skills/stripe-best-practices/references/connect.md +48 -0
- package/.codebuddy/skills/stripe-best-practices/references/payments.md +79 -0
- package/.codebuddy/skills/stripe-best-practices/references/security.md +109 -0
- package/.codebuddy/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.codebuddy/skills/stripe-projects/SKILL.md +139 -0
- package/.codebuddy/skills/upgrade-stripe/SKILL.md +185 -0
- package/.commandcode/skills/stripe-best-practices/SKILL.md +42 -0
- package/.commandcode/skills/stripe-best-practices/references/billing.md +36 -0
- package/.commandcode/skills/stripe-best-practices/references/connect.md +48 -0
- package/.commandcode/skills/stripe-best-practices/references/payments.md +79 -0
- package/.commandcode/skills/stripe-best-practices/references/security.md +109 -0
- package/.commandcode/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.commandcode/skills/stripe-projects/SKILL.md +139 -0
- package/.commandcode/skills/upgrade-stripe/SKILL.md +185 -0
- package/.continue/skills/stripe-best-practices/SKILL.md +42 -0
- package/.continue/skills/stripe-best-practices/references/billing.md +36 -0
- package/.continue/skills/stripe-best-practices/references/connect.md +48 -0
- package/.continue/skills/stripe-best-practices/references/payments.md +79 -0
- package/.continue/skills/stripe-best-practices/references/security.md +109 -0
- package/.continue/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.continue/skills/stripe-projects/SKILL.md +139 -0
- package/.continue/skills/upgrade-stripe/SKILL.md +185 -0
- package/.cortex/skills/stripe-best-practices/SKILL.md +42 -0
- package/.cortex/skills/stripe-best-practices/references/billing.md +36 -0
- package/.cortex/skills/stripe-best-practices/references/connect.md +48 -0
- package/.cortex/skills/stripe-best-practices/references/payments.md +79 -0
- package/.cortex/skills/stripe-best-practices/references/security.md +109 -0
- package/.cortex/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.cortex/skills/stripe-projects/SKILL.md +139 -0
- package/.cortex/skills/upgrade-stripe/SKILL.md +185 -0
- package/.crush/skills/stripe-best-practices/SKILL.md +42 -0
- package/.crush/skills/stripe-best-practices/references/billing.md +36 -0
- package/.crush/skills/stripe-best-practices/references/connect.md +48 -0
- package/.crush/skills/stripe-best-practices/references/payments.md +79 -0
- package/.crush/skills/stripe-best-practices/references/security.md +109 -0
- package/.crush/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.crush/skills/stripe-projects/SKILL.md +139 -0
- package/.crush/skills/upgrade-stripe/SKILL.md +185 -0
- package/.env.example +20 -0
- package/.factory/skills/stripe-best-practices/SKILL.md +42 -0
- package/.factory/skills/stripe-best-practices/references/billing.md +36 -0
- package/.factory/skills/stripe-best-practices/references/connect.md +48 -0
- package/.factory/skills/stripe-best-practices/references/payments.md +79 -0
- package/.factory/skills/stripe-best-practices/references/security.md +109 -0
- package/.factory/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.factory/skills/stripe-projects/SKILL.md +139 -0
- package/.factory/skills/upgrade-stripe/SKILL.md +185 -0
- package/.goose/skills/stripe-best-practices/SKILL.md +42 -0
- package/.goose/skills/stripe-best-practices/references/billing.md +36 -0
- package/.goose/skills/stripe-best-practices/references/connect.md +48 -0
- package/.goose/skills/stripe-best-practices/references/payments.md +79 -0
- package/.goose/skills/stripe-best-practices/references/security.md +109 -0
- package/.goose/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.goose/skills/stripe-projects/SKILL.md +139 -0
- package/.goose/skills/upgrade-stripe/SKILL.md +185 -0
- package/.iflow/skills/stripe-best-practices/SKILL.md +42 -0
- package/.iflow/skills/stripe-best-practices/references/billing.md +36 -0
- package/.iflow/skills/stripe-best-practices/references/connect.md +48 -0
- package/.iflow/skills/stripe-best-practices/references/payments.md +79 -0
- package/.iflow/skills/stripe-best-practices/references/security.md +109 -0
- package/.iflow/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.iflow/skills/stripe-projects/SKILL.md +139 -0
- package/.iflow/skills/upgrade-stripe/SKILL.md +185 -0
- package/.junie/skills/stripe-best-practices/SKILL.md +42 -0
- package/.junie/skills/stripe-best-practices/references/billing.md +36 -0
- package/.junie/skills/stripe-best-practices/references/connect.md +48 -0
- package/.junie/skills/stripe-best-practices/references/payments.md +79 -0
- package/.junie/skills/stripe-best-practices/references/security.md +109 -0
- package/.junie/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.junie/skills/stripe-projects/SKILL.md +139 -0
- package/.junie/skills/upgrade-stripe/SKILL.md +185 -0
- package/.kilocode/skills/stripe-best-practices/SKILL.md +42 -0
- package/.kilocode/skills/stripe-best-practices/references/billing.md +36 -0
- package/.kilocode/skills/stripe-best-practices/references/connect.md +48 -0
- package/.kilocode/skills/stripe-best-practices/references/payments.md +79 -0
- package/.kilocode/skills/stripe-best-practices/references/security.md +109 -0
- package/.kilocode/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.kilocode/skills/stripe-projects/SKILL.md +139 -0
- package/.kilocode/skills/upgrade-stripe/SKILL.md +185 -0
- package/.kiro/skills/stripe-best-practices/SKILL.md +42 -0
- package/.kiro/skills/stripe-best-practices/references/billing.md +36 -0
- package/.kiro/skills/stripe-best-practices/references/connect.md +48 -0
- package/.kiro/skills/stripe-best-practices/references/payments.md +79 -0
- package/.kiro/skills/stripe-best-practices/references/security.md +109 -0
- package/.kiro/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.kiro/skills/stripe-projects/SKILL.md +139 -0
- package/.kiro/skills/upgrade-stripe/SKILL.md +185 -0
- package/.kode/skills/stripe-best-practices/SKILL.md +42 -0
- package/.kode/skills/stripe-best-practices/references/billing.md +36 -0
- package/.kode/skills/stripe-best-practices/references/connect.md +48 -0
- package/.kode/skills/stripe-best-practices/references/payments.md +79 -0
- package/.kode/skills/stripe-best-practices/references/security.md +109 -0
- package/.kode/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.kode/skills/stripe-projects/SKILL.md +139 -0
- package/.kode/skills/upgrade-stripe/SKILL.md +185 -0
- package/.mcpjam/skills/stripe-best-practices/SKILL.md +42 -0
- package/.mcpjam/skills/stripe-best-practices/references/billing.md +36 -0
- package/.mcpjam/skills/stripe-best-practices/references/connect.md +48 -0
- package/.mcpjam/skills/stripe-best-practices/references/payments.md +79 -0
- package/.mcpjam/skills/stripe-best-practices/references/security.md +109 -0
- package/.mcpjam/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.mcpjam/skills/stripe-projects/SKILL.md +139 -0
- package/.mcpjam/skills/upgrade-stripe/SKILL.md +185 -0
- package/.mux/skills/stripe-best-practices/SKILL.md +42 -0
- package/.mux/skills/stripe-best-practices/references/billing.md +36 -0
- package/.mux/skills/stripe-best-practices/references/connect.md +48 -0
- package/.mux/skills/stripe-best-practices/references/payments.md +79 -0
- package/.mux/skills/stripe-best-practices/references/security.md +109 -0
- package/.mux/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.mux/skills/stripe-projects/SKILL.md +139 -0
- package/.mux/skills/upgrade-stripe/SKILL.md +185 -0
- package/.neovate/skills/stripe-best-practices/SKILL.md +42 -0
- package/.neovate/skills/stripe-best-practices/references/billing.md +36 -0
- package/.neovate/skills/stripe-best-practices/references/connect.md +48 -0
- package/.neovate/skills/stripe-best-practices/references/payments.md +79 -0
- package/.neovate/skills/stripe-best-practices/references/security.md +109 -0
- package/.neovate/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.neovate/skills/stripe-projects/SKILL.md +139 -0
- package/.neovate/skills/upgrade-stripe/SKILL.md +185 -0
- package/.nixpacksignore +14 -0
- package/.openhands/skills/stripe-best-practices/SKILL.md +42 -0
- package/.openhands/skills/stripe-best-practices/references/billing.md +36 -0
- package/.openhands/skills/stripe-best-practices/references/connect.md +48 -0
- package/.openhands/skills/stripe-best-practices/references/payments.md +79 -0
- package/.openhands/skills/stripe-best-practices/references/security.md +109 -0
- package/.openhands/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.openhands/skills/stripe-projects/SKILL.md +139 -0
- package/.openhands/skills/upgrade-stripe/SKILL.md +185 -0
- package/.pi/skills/stripe-best-practices/SKILL.md +42 -0
- package/.pi/skills/stripe-best-practices/references/billing.md +36 -0
- package/.pi/skills/stripe-best-practices/references/connect.md +48 -0
- package/.pi/skills/stripe-best-practices/references/payments.md +79 -0
- package/.pi/skills/stripe-best-practices/references/security.md +109 -0
- package/.pi/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.pi/skills/stripe-projects/SKILL.md +139 -0
- package/.pi/skills/upgrade-stripe/SKILL.md +185 -0
- package/.pochi/skills/stripe-best-practices/SKILL.md +42 -0
- package/.pochi/skills/stripe-best-practices/references/billing.md +36 -0
- package/.pochi/skills/stripe-best-practices/references/connect.md +48 -0
- package/.pochi/skills/stripe-best-practices/references/payments.md +79 -0
- package/.pochi/skills/stripe-best-practices/references/security.md +109 -0
- package/.pochi/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.pochi/skills/stripe-projects/SKILL.md +139 -0
- package/.pochi/skills/upgrade-stripe/SKILL.md +185 -0
- package/.qoder/skills/stripe-best-practices/SKILL.md +42 -0
- package/.qoder/skills/stripe-best-practices/references/billing.md +36 -0
- package/.qoder/skills/stripe-best-practices/references/connect.md +48 -0
- package/.qoder/skills/stripe-best-practices/references/payments.md +79 -0
- package/.qoder/skills/stripe-best-practices/references/security.md +109 -0
- package/.qoder/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.qoder/skills/stripe-projects/SKILL.md +139 -0
- package/.qoder/skills/upgrade-stripe/SKILL.md +185 -0
- package/.qwen/skills/stripe-best-practices/SKILL.md +42 -0
- package/.qwen/skills/stripe-best-practices/references/billing.md +36 -0
- package/.qwen/skills/stripe-best-practices/references/connect.md +48 -0
- package/.qwen/skills/stripe-best-practices/references/payments.md +79 -0
- package/.qwen/skills/stripe-best-practices/references/security.md +109 -0
- package/.qwen/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.qwen/skills/stripe-projects/SKILL.md +139 -0
- package/.qwen/skills/upgrade-stripe/SKILL.md +185 -0
- package/.roo/skills/stripe-best-practices/SKILL.md +42 -0
- package/.roo/skills/stripe-best-practices/references/billing.md +36 -0
- package/.roo/skills/stripe-best-practices/references/connect.md +48 -0
- package/.roo/skills/stripe-best-practices/references/payments.md +79 -0
- package/.roo/skills/stripe-best-practices/references/security.md +109 -0
- package/.roo/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.roo/skills/stripe-projects/SKILL.md +139 -0
- package/.roo/skills/upgrade-stripe/SKILL.md +185 -0
- package/.trae/skills/stripe-best-practices/SKILL.md +42 -0
- package/.trae/skills/stripe-best-practices/references/billing.md +36 -0
- package/.trae/skills/stripe-best-practices/references/connect.md +48 -0
- package/.trae/skills/stripe-best-practices/references/payments.md +79 -0
- package/.trae/skills/stripe-best-practices/references/security.md +109 -0
- package/.trae/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.trae/skills/stripe-projects/SKILL.md +139 -0
- package/.trae/skills/upgrade-stripe/SKILL.md +185 -0
- package/.vibe/skills/stripe-best-practices/SKILL.md +42 -0
- package/.vibe/skills/stripe-best-practices/references/billing.md +36 -0
- package/.vibe/skills/stripe-best-practices/references/connect.md +48 -0
- package/.vibe/skills/stripe-best-practices/references/payments.md +79 -0
- package/.vibe/skills/stripe-best-practices/references/security.md +109 -0
- package/.vibe/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.vibe/skills/stripe-projects/SKILL.md +139 -0
- package/.vibe/skills/upgrade-stripe/SKILL.md +185 -0
- package/.windsurf/skills/stripe-best-practices/SKILL.md +42 -0
- package/.windsurf/skills/stripe-best-practices/references/billing.md +36 -0
- package/.windsurf/skills/stripe-best-practices/references/connect.md +48 -0
- package/.windsurf/skills/stripe-best-practices/references/payments.md +79 -0
- package/.windsurf/skills/stripe-best-practices/references/security.md +109 -0
- package/.windsurf/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.windsurf/skills/stripe-projects/SKILL.md +139 -0
- package/.windsurf/skills/upgrade-stripe/SKILL.md +185 -0
- package/.zencoder/skills/stripe-best-practices/SKILL.md +42 -0
- package/.zencoder/skills/stripe-best-practices/references/billing.md +36 -0
- package/.zencoder/skills/stripe-best-practices/references/connect.md +48 -0
- package/.zencoder/skills/stripe-best-practices/references/payments.md +79 -0
- package/.zencoder/skills/stripe-best-practices/references/security.md +109 -0
- package/.zencoder/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.zencoder/skills/stripe-projects/SKILL.md +139 -0
- package/.zencoder/skills/upgrade-stripe/SKILL.md +185 -0
- package/AUDIT.md +95 -0
- package/BLOCKERS.md +0 -0
- package/COOLIFY.md +51 -0
- package/MCP_SETUP.md +23 -0
- package/PRODUCTION_CHECKLIST.md +246 -0
- package/README.md +47 -0
- package/ROADMAP.md +91 -0
- package/docs/superpowers/plans/2026-05-11-availsync-frontend-sales-flow.md +2445 -0
- package/frontend/.env.example +2 -0
- package/frontend/app/admin/layout.tsx +13 -0
- package/frontend/app/admin/page.tsx +747 -0
- package/frontend/app/app/activity/page.tsx +257 -0
- package/frontend/app/app/agents/[agentId]/page.tsx +21 -0
- package/frontend/app/app/agents/page.tsx +1155 -0
- package/frontend/app/app/audit/page.tsx +225 -0
- package/frontend/app/app/availability/page.tsx +840 -0
- package/frontend/app/app/holds/page.tsx +262 -0
- package/frontend/app/app/layout.tsx +19 -0
- package/frontend/app/app/onboarding/page.tsx +10 -0
- package/frontend/app/app/onboarding/verify/page.tsx +309 -0
- package/frontend/app/app/page.tsx +508 -0
- package/frontend/app/app/settings/page.tsx +399 -0
- package/frontend/app/app/work/page.tsx +426 -0
- package/frontend/app/changelog/page.tsx +93 -0
- package/frontend/app/checkout/page.tsx +25 -0
- package/frontend/app/docs/api/page.tsx +157 -0
- package/frontend/app/docs/page.tsx +296 -0
- package/frontend/app/docs/pilot/page.tsx +127 -0
- package/frontend/app/docs/quickstart/page.tsx +318 -0
- package/frontend/app/docs/reliability/page.tsx +78 -0
- package/frontend/app/docs/sdk/node/page.tsx +166 -0
- package/frontend/app/globals.css +57 -0
- package/frontend/app/icon.png +0 -0
- package/frontend/app/layout.tsx +87 -0
- package/frontend/app/login/page.tsx +14 -0
- package/frontend/app/page.tsx +47 -0
- package/frontend/app/pricing/page.tsx +66 -0
- package/frontend/app/privacy/page.tsx +52 -0
- package/frontend/app/robots.ts +26 -0
- package/frontend/app/security/page.tsx +74 -0
- package/frontend/app/signup/page.tsx +14 -0
- package/frontend/app/sitemap.ts +14 -0
- package/frontend/app/terms/page.tsx +51 -0
- package/frontend/components/brand/AvailsyncLogo.tsx +56 -0
- package/frontend/components/checkout/CheckoutClient.tsx +100 -0
- package/frontend/components/dashboard/AgentForm.tsx +59 -0
- package/frontend/components/dashboard/AppShell.tsx +291 -0
- package/frontend/components/dashboard/AvailabilityChecker.tsx +117 -0
- package/frontend/components/dashboard/AvailabilityWindowForm.tsx +40 -0
- package/frontend/components/dashboard/HoldForm.tsx +133 -0
- package/frontend/components/dashboard/MetricCard.tsx +10 -0
- package/frontend/components/login/LoginForm.tsx +95 -0
- package/frontend/components/marketing/AgentCoordinationStory.tsx +1530 -0
- package/frontend/components/marketing/Faq.tsx +41 -0
- package/frontend/components/marketing/Hero.tsx +73 -0
- package/frontend/components/marketing/HowItWorks.tsx +28 -0
- package/frontend/components/marketing/ObserveModeTeaser.tsx +41 -0
- package/frontend/components/marketing/PricingTeaser.tsx +23 -0
- package/frontend/components/marketing/ProblemSolution.tsx +36 -0
- package/frontend/components/marketing/SiteFooter.tsx +59 -0
- package/frontend/components/marketing/SiteHeader.tsx +45 -0
- package/frontend/components/marketing/UseCases.tsx +27 -0
- package/frontend/components/onboarding/OnboardingClient.tsx +278 -0
- package/frontend/components/pricing/PricingCards.tsx +65 -0
- package/frontend/components/privacy/CookieConsent.tsx +230 -0
- package/frontend/components/privacy/CookieSettingsButton.tsx +15 -0
- package/frontend/components/seo/JsonLd.tsx +10 -0
- package/frontend/components/signup/SignupForm.tsx +55 -0
- package/frontend/components/ui/Badge.tsx +23 -0
- package/frontend/components/ui/Button.tsx +37 -0
- package/frontend/components/ui/Card.tsx +11 -0
- package/frontend/components/ui/ConfirmDialog.tsx +77 -0
- package/frontend/components/ui/EmptyState.tsx +24 -0
- package/frontend/components/ui/Input.tsx +14 -0
- package/frontend/components/ui/KeyDisplay.tsx +49 -0
- package/frontend/components/ui/Select.tsx +14 -0
- package/frontend/components/ui/Skeleton.tsx +24 -0
- package/frontend/components/ui/Tabs.tsx +19 -0
- package/frontend/components/ui/Textarea.tsx +14 -0
- package/frontend/components/ui/Toast.tsx +78 -0
- package/frontend/components/waitlist/WaitlistDialog.tsx +128 -0
- package/frontend/lib/api.ts +1282 -0
- package/frontend/lib/billing.ts +6 -0
- package/frontend/lib/cookieConsent.ts +113 -0
- package/frontend/lib/format.ts +16 -0
- package/frontend/lib/plans.ts +62 -0
- package/frontend/lib/schemas.ts +108 -0
- package/frontend/lib/seo.ts +376 -0
- package/frontend/lib/setupGuides.ts +630 -0
- package/frontend/lib/storage.ts +30 -0
- package/frontend/next-env.d.ts +6 -0
- package/frontend/next.config.mjs +13 -0
- package/frontend/package-lock.json +14409 -0
- package/frontend/package.json +41 -0
- package/frontend/playwright.config.ts +20 -0
- package/frontend/postcss.config.mjs +8 -0
- package/frontend/public/.gitkeep +0 -0
- package/frontend/public/brand/availsync-logo-board.png +0 -0
- package/frontend/public/brand/availsync-logo-dark.png +0 -0
- package/frontend/public/brand/availsync-mark-dark.png +0 -0
- package/frontend/public/brand/availsync-wordmark-dark.png +0 -0
- package/frontend/public/marketing/hero-agent-coordination.png +0 -0
- package/frontend/tailwind.config.ts +53 -0
- package/frontend/tests/smoke.spec.ts +89 -0
- package/frontend/tsconfig.json +23 -0
- package/jest.config.js +7 -0
- package/nixpacks.toml +11 -0
- package/package.json +53 -0
- package/packages/mcp/LICENSE +21 -0
- package/packages/mcp/README.md +60 -0
- package/packages/mcp/jest.config.cjs +8 -0
- package/packages/mcp/package.json +54 -0
- package/packages/mcp/src/helpers.ts +38 -0
- package/packages/mcp/src/index.test.ts +60 -0
- package/packages/mcp/src/index.ts +387 -0
- package/packages/mcp/tsconfig.json +20 -0
- package/packages/mcp/tsconfig.test.json +12 -0
- package/packages/node/LICENSE +21 -0
- package/packages/node/README.md +120 -0
- package/packages/node/jest.config.cjs +8 -0
- package/packages/node/package.json +46 -0
- package/packages/node/src/index.test.ts +360 -0
- package/packages/node/src/index.ts +402 -0
- package/packages/node/tsconfig.json +20 -0
- package/packages/node/tsconfig.test.json +12 -0
- package/plan.md +923 -0
- package/skills/stripe-best-practices/SKILL.md +42 -0
- package/skills/stripe-best-practices/references/billing.md +36 -0
- package/skills/stripe-best-practices/references/connect.md +48 -0
- package/skills/stripe-best-practices/references/payments.md +79 -0
- package/skills/stripe-best-practices/references/security.md +109 -0
- package/skills/stripe-best-practices/references/treasury.md +16 -0
- package/skills/stripe-projects/SKILL.md +139 -0
- package/skills/upgrade-stripe/SKILL.md +185 -0
- package/skills-lock.json +20 -0
- package/src/core/availability.ts +178 -0
- package/src/core/conflict.ts +209 -0
- package/src/core/work.ts +490 -0
- package/src/db/client.ts +17 -0
- package/src/db/migrations/001_init.sql +88 -0
- package/src/db/migrations/002_stripe.sql +2 -0
- package/src/db/migrations/003_workspace_auth.sql +19 -0
- package/src/db/migrations/004_agent_mcp_status.sql +2 -0
- package/src/db/migrations/005_hold_event_actor.sql +4 -0
- package/src/db/migrations/006_agent_activity.sql +35 -0
- package/src/db/migrations/007_work_coordination.sql +60 -0
- package/src/db/migrations/008_work_claim_leases.sql +20 -0
- package/src/db/migrations/009_billing_subscription_state.sql +23 -0
- package/src/db/migrations/010_agent_api_key_prefix.sql +10 -0
- package/src/db/migrations/011_org_verified_and_work_event_retention.sql +11 -0
- package/src/db/migrations/012_agent_enforcement_mode.sql +12 -0
- package/src/db/migrations/013_support_tickets.sql +21 -0
- package/src/db/migrations/014_paid_plan_waitlist.sql +23 -0
- package/src/db/migrations/015_agent_last_seen.sql +2 -0
- package/src/db/migrations.ts +164 -0
- package/src/db/run-migrations.ts +13 -0
- package/src/index.ts +183 -0
- package/src/lib/activity.ts +137 -0
- package/src/lib/apiKeys.ts +32 -0
- package/src/lib/appInfo.ts +26 -0
- package/src/lib/billingConfig.ts +3 -0
- package/src/lib/env.ts +75 -0
- package/src/lib/logger.ts +8 -0
- package/src/lib/plans.ts +204 -0
- package/src/mcp/server.js +5 -0
- package/src/mcp/server.ts +350 -0
- package/src/middleware/auth.ts +342 -0
- package/src/middleware/requestId.ts +16 -0
- package/src/routes/account.ts +168 -0
- package/src/routes/activity.ts +126 -0
- package/src/routes/admin.ts +514 -0
- package/src/routes/audit.ts +68 -0
- package/src/routes/auth.ts +203 -0
- package/src/routes/availability.ts +325 -0
- package/src/routes/billing.ts +406 -0
- package/src/routes/conflicts.ts +131 -0
- package/src/routes/holds.ts +437 -0
- package/src/routes/mcp.ts +57 -0
- package/src/routes/metrics.ts +39 -0
- package/src/routes/onboarding.ts +273 -0
- package/src/routes/orgs.ts +981 -0
- package/src/routes/preferences.ts +132 -0
- package/src/routes/session.ts +16 -0
- package/src/routes/support.ts +77 -0
- package/src/routes/value.ts +186 -0
- package/src/routes/waitlist.ts +63 -0
- package/src/routes/work.ts +1578 -0
- package/src/server.ts +36 -0
- package/src/types/index.ts +109 -0
- package/tests/integration/activity.route.test.ts +103 -0
- package/tests/integration/admin.route.test.ts +143 -0
- package/tests/integration/agent-keys.route.test.ts +237 -0
- package/tests/integration/availability.route.test.ts +125 -0
- package/tests/integration/billing.route.test.ts +393 -0
- package/tests/integration/conflicts.route.test.ts +131 -0
- package/tests/integration/flows.test.ts +154 -0
- package/tests/integration/helpers.ts +134 -0
- package/tests/integration/holds.route.test.ts +185 -0
- package/tests/integration/metrics.route.test.ts +100 -0
- package/tests/integration/onboarding.verify.route.test.ts +163 -0
- package/tests/integration/preferences.route.test.ts +53 -0
- package/tests/integration/session.route.test.ts +97 -0
- package/tests/integration/system.route.test.ts +92 -0
- package/tests/integration/value.route.test.ts +235 -0
- package/tests/integration/work.route.test.ts +745 -0
- package/tests/setup.ts +4 -0
- package/tests/smoke.sh +62 -0
- package/tests/unit/auth.test.ts +114 -0
- package/tests/unit/availability.test.ts +149 -0
- package/tests/unit/conflict.test.ts +118 -0
- package/tests/unit/env.test.ts +69 -0
- package/tests/unit/migrations.test.ts +135 -0
- package/tests/unit/request-id.test.ts +37 -0
- package/tmp-mobile-agents.png +0 -0
- package/tmp-next-mobile.err.log +10 -0
- package/tmp-next-mobile.log +5 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, test } from '@jest/globals';
|
|
2
|
+
import pool from '../../src/db/client';
|
|
3
|
+
import {
|
|
4
|
+
api,
|
|
5
|
+
closeDatabase,
|
|
6
|
+
createAgent,
|
|
7
|
+
createOrgAndAgent,
|
|
8
|
+
getWorkspaceCookie,
|
|
9
|
+
migrateTestDatabase,
|
|
10
|
+
resetDatabase,
|
|
11
|
+
} from './helpers';
|
|
12
|
+
|
|
13
|
+
describe('onboarding verification', () => {
|
|
14
|
+
beforeAll(async () => {
|
|
15
|
+
await migrateTestDatabase();
|
|
16
|
+
await resetDatabase();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
afterAll(async () => {
|
|
20
|
+
await resetDatabase();
|
|
21
|
+
await closeDatabase();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('verifies an org by showing external meeting agent winning over internal agent', async () => {
|
|
25
|
+
const org = await createOrgAndAgent('Sales Bot', 'external_meeting');
|
|
26
|
+
const internal = await createAgent(org.orgId, 'Recruiting Bot', 'internal');
|
|
27
|
+
|
|
28
|
+
const response = await api
|
|
29
|
+
.post('/v1/onboarding/verify')
|
|
30
|
+
.set('Cookie', getWorkspaceCookie(org.orgId))
|
|
31
|
+
.send({ agent_a_id: org.agentId, agent_b_id: internal.agentId })
|
|
32
|
+
.expect(200);
|
|
33
|
+
|
|
34
|
+
expect(response.body).toMatchObject({
|
|
35
|
+
resource_key: expect.stringMatching(/^verify-/),
|
|
36
|
+
winner: {
|
|
37
|
+
agent_id: org.agentId,
|
|
38
|
+
agent_name: 'Sales Bot',
|
|
39
|
+
agent_type: 'external_meeting',
|
|
40
|
+
outcome: 'claimed',
|
|
41
|
+
},
|
|
42
|
+
loser: {
|
|
43
|
+
agent_id: internal.agentId,
|
|
44
|
+
agent_name: 'Recruiting Bot',
|
|
45
|
+
agent_type: 'internal',
|
|
46
|
+
claim_id: null,
|
|
47
|
+
outcome: 'blocked',
|
|
48
|
+
},
|
|
49
|
+
rule_applied: 'agent_type',
|
|
50
|
+
});
|
|
51
|
+
expect(response.body.rule_explanation).toContain("external_meeting");
|
|
52
|
+
expect(response.body.verified_at).toEqual(expect.any(String));
|
|
53
|
+
|
|
54
|
+
const stored = await pool.query('SELECT verified_at FROM orgs WHERE id = $1', [org.orgId]);
|
|
55
|
+
expect(stored.rows[0].verified_at.toISOString()).toBe(response.body.verified_at);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('verification can run again with a new resource key while preserving original verified_at', async () => {
|
|
59
|
+
const org = await createOrgAndAgent('Sales Bot 2', 'external_meeting');
|
|
60
|
+
const internal = await createAgent(org.orgId, 'Recruiting Bot 2', 'internal');
|
|
61
|
+
|
|
62
|
+
const first = await api
|
|
63
|
+
.post('/v1/onboarding/verify')
|
|
64
|
+
.set('Cookie', getWorkspaceCookie(org.orgId))
|
|
65
|
+
.send({ agent_a_id: org.agentId, agent_b_id: internal.agentId })
|
|
66
|
+
.expect(200);
|
|
67
|
+
|
|
68
|
+
const second = await api
|
|
69
|
+
.post('/v1/onboarding/verify')
|
|
70
|
+
.set('Cookie', getWorkspaceCookie(org.orgId))
|
|
71
|
+
.send({ agent_a_id: org.agentId, agent_b_id: internal.agentId })
|
|
72
|
+
.expect(200);
|
|
73
|
+
|
|
74
|
+
expect(second.body.resource_key).not.toBe(first.body.resource_key);
|
|
75
|
+
expect(second.body.verified_at).toBe(first.body.verified_at);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('returns insufficient_agents when org has only one agent', async () => {
|
|
79
|
+
const org = await createOrgAndAgent('Solo Bot', 'generic');
|
|
80
|
+
|
|
81
|
+
const response = await api
|
|
82
|
+
.post('/v1/onboarding/verify')
|
|
83
|
+
.set('Cookie', getWorkspaceCookie(org.orgId))
|
|
84
|
+
.send({})
|
|
85
|
+
.expect(422);
|
|
86
|
+
|
|
87
|
+
expect(response.body).toMatchObject({
|
|
88
|
+
error: 'insufficient_agents',
|
|
89
|
+
message: 'Create at least 2 agents to run verification',
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('returns tie warning when agents tie on type and priority', async () => {
|
|
94
|
+
const org = await createOrgAndAgent('Generic A', 'generic', 0);
|
|
95
|
+
const genericB = await createAgent(org.orgId, 'Generic B', 'generic', 0);
|
|
96
|
+
|
|
97
|
+
const response = await api
|
|
98
|
+
.post('/v1/onboarding/verify')
|
|
99
|
+
.set('Cookie', getWorkspaceCookie(org.orgId))
|
|
100
|
+
.send({ agent_a_id: org.agentId, agent_b_id: genericB.agentId })
|
|
101
|
+
.expect(200);
|
|
102
|
+
|
|
103
|
+
expect(response.body).toMatchObject({
|
|
104
|
+
rule_applied: 'first_created',
|
|
105
|
+
tie_warning: expect.stringContaining('Both agents tied on priority'),
|
|
106
|
+
winner: { agent_id: org.agentId },
|
|
107
|
+
loser: { agent_id: genericB.agentId },
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test('cleanup removes verification resource and keeps work events', async () => {
|
|
112
|
+
const org = await createOrgAndAgent('Sales Bot 3', 'external_meeting');
|
|
113
|
+
const internal = await createAgent(org.orgId, 'Recruiting Bot 3', 'internal');
|
|
114
|
+
|
|
115
|
+
const response = await api
|
|
116
|
+
.post('/v1/onboarding/verify')
|
|
117
|
+
.set('Cookie', getWorkspaceCookie(org.orgId))
|
|
118
|
+
.send({ agent_a_id: org.agentId, agent_b_id: internal.agentId })
|
|
119
|
+
.expect(200);
|
|
120
|
+
|
|
121
|
+
const resource = await pool.query(
|
|
122
|
+
`SELECT COUNT(*)::int AS count
|
|
123
|
+
FROM work_resources
|
|
124
|
+
WHERE org_id = $1
|
|
125
|
+
AND resource_key = $2`,
|
|
126
|
+
[org.orgId, response.body.resource_key],
|
|
127
|
+
);
|
|
128
|
+
expect(resource.rows[0].count).toBe(0);
|
|
129
|
+
|
|
130
|
+
const activeClaims = await pool.query(
|
|
131
|
+
`SELECT COUNT(*)::int AS count
|
|
132
|
+
FROM work_claims
|
|
133
|
+
WHERE org_id = $1
|
|
134
|
+
AND metadata->>'resource_key' = $2`,
|
|
135
|
+
[org.orgId, response.body.resource_key],
|
|
136
|
+
);
|
|
137
|
+
expect(activeClaims.rows[0].count).toBe(0);
|
|
138
|
+
|
|
139
|
+
const events = await pool.query(
|
|
140
|
+
`SELECT event_type, metadata
|
|
141
|
+
FROM work_claim_events
|
|
142
|
+
WHERE org_id = $1
|
|
143
|
+
AND metadata->>'source' = 'onboarding_verification'
|
|
144
|
+
AND metadata->>'resource_key' = $2
|
|
145
|
+
ORDER BY created_at ASC`,
|
|
146
|
+
[org.orgId, response.body.resource_key],
|
|
147
|
+
);
|
|
148
|
+
expect(events.rows.map((row) => row.event_type).sort()).toEqual(['blocked', 'created', 'released']);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test('rejects selected agents outside the workspace', async () => {
|
|
152
|
+
const org = await createOrgAndAgent('Local Bot', 'external_meeting');
|
|
153
|
+
const otherOrg = await createOrgAndAgent('Foreign Bot', 'internal');
|
|
154
|
+
|
|
155
|
+
const response = await api
|
|
156
|
+
.post('/v1/onboarding/verify')
|
|
157
|
+
.set('Cookie', getWorkspaceCookie(org.orgId))
|
|
158
|
+
.send({ agent_a_id: org.agentId, agent_b_id: otherOrg.agentId })
|
|
159
|
+
.expect(403);
|
|
160
|
+
|
|
161
|
+
expect(response.body).toMatchObject({ error: 'forbidden' });
|
|
162
|
+
});
|
|
163
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, test } from '@jest/globals';
|
|
2
|
+
import {
|
|
3
|
+
api,
|
|
4
|
+
closeDatabase,
|
|
5
|
+
createOrgAndAgent,
|
|
6
|
+
migrateTestDatabase,
|
|
7
|
+
resetDatabase,
|
|
8
|
+
} from './helpers';
|
|
9
|
+
|
|
10
|
+
describe('preferences routes', () => {
|
|
11
|
+
let agentId: string;
|
|
12
|
+
let apiKey: string;
|
|
13
|
+
|
|
14
|
+
beforeAll(async () => {
|
|
15
|
+
await migrateTestDatabase();
|
|
16
|
+
await resetDatabase();
|
|
17
|
+
const created = await createOrgAndAgent();
|
|
18
|
+
agentId = created.agentId;
|
|
19
|
+
apiKey = created.apiKey;
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
afterAll(async () => {
|
|
23
|
+
await resetDatabase();
|
|
24
|
+
await closeDatabase();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('GET /v1/preferences returns defaults', async () => {
|
|
28
|
+
const response = await api
|
|
29
|
+
.get(`/v1/preferences/${agentId}`)
|
|
30
|
+
.set('Authorization', `Bearer ${apiKey}`)
|
|
31
|
+
.expect(200);
|
|
32
|
+
|
|
33
|
+
expect(response.body.preferences.buffer_minutes_after).toBe(15);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('PUT /v1/preferences updates buffer_minutes_after', async () => {
|
|
37
|
+
const response = await api
|
|
38
|
+
.put(`/v1/preferences/${agentId}`)
|
|
39
|
+
.set('Authorization', `Bearer ${apiKey}`)
|
|
40
|
+
.send({ buffer_minutes_after: 30 })
|
|
41
|
+
.expect(200);
|
|
42
|
+
|
|
43
|
+
expect(response.body.preferences.buffer_minutes_after).toBe(30);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('PUT /v1/preferences with invalid weekday returns 422', async () => {
|
|
47
|
+
await api
|
|
48
|
+
.put(`/v1/preferences/${agentId}`)
|
|
49
|
+
.set('Authorization', `Bearer ${apiKey}`)
|
|
50
|
+
.send({ focus_blocks: [{ weekday: 7, start_time: '09:00', end_time: '10:00' }] })
|
|
51
|
+
.expect(422);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, test } from '@jest/globals';
|
|
2
|
+
import { api, closeDatabase, migrateTestDatabase, resetDatabase } from './helpers';
|
|
3
|
+
|
|
4
|
+
describe('session routes', () => {
|
|
5
|
+
beforeAll(async () => {
|
|
6
|
+
await migrateTestDatabase();
|
|
7
|
+
await resetDatabase();
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
afterAll(async () => {
|
|
11
|
+
await resetDatabase();
|
|
12
|
+
await closeDatabase();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test('signup creates a workspace session and GET /v1/session restores workspace context', async () => {
|
|
16
|
+
const response = await api
|
|
17
|
+
.post('/v1/auth/signup')
|
|
18
|
+
.send({
|
|
19
|
+
workspace_name: 'Session Org',
|
|
20
|
+
email: 'session@example.com',
|
|
21
|
+
password: 'password123',
|
|
22
|
+
agent: { name: 'Session Bot', agent_type: 'generic' },
|
|
23
|
+
})
|
|
24
|
+
.expect(201);
|
|
25
|
+
const cookie = response.headers['set-cookie'];
|
|
26
|
+
|
|
27
|
+
const session = await api
|
|
28
|
+
.get('/v1/session')
|
|
29
|
+
.set('Cookie', cookie)
|
|
30
|
+
.expect(200);
|
|
31
|
+
|
|
32
|
+
expect(session.body.org.id).toBe(response.body.org.id);
|
|
33
|
+
expect(session.body.user.email).toBe('session@example.com');
|
|
34
|
+
expect(session.body.user.password_hash).toBeUndefined();
|
|
35
|
+
expect(response.body.api_key).toBeTruthy();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('login and logout manage workspace sessions', async () => {
|
|
39
|
+
const signup = await api
|
|
40
|
+
.post('/v1/auth/signup')
|
|
41
|
+
.send({
|
|
42
|
+
workspace_name: 'Login Org',
|
|
43
|
+
email: 'login@example.com',
|
|
44
|
+
password: 'password123',
|
|
45
|
+
agent: { name: 'Login Bot', agent_type: 'generic' },
|
|
46
|
+
})
|
|
47
|
+
.expect(201);
|
|
48
|
+
|
|
49
|
+
const login = await api
|
|
50
|
+
.post('/v1/auth/login')
|
|
51
|
+
.send({ email: 'login@example.com', password: 'password123' })
|
|
52
|
+
.expect(200);
|
|
53
|
+
|
|
54
|
+
await api.get('/v1/session').set('Cookie', login.headers['set-cookie']).expect(200);
|
|
55
|
+
await api.post('/v1/auth/logout').set('Cookie', signup.headers['set-cookie']).expect(200);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('GET /v1/session rejects missing auth', async () => {
|
|
59
|
+
await api.get('/v1/session').expect(401);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('agent management requires a workspace session', async () => {
|
|
63
|
+
const signup = await api
|
|
64
|
+
.post('/v1/auth/signup')
|
|
65
|
+
.send({
|
|
66
|
+
workspace_name: 'Agents Org',
|
|
67
|
+
email: 'agents@example.com',
|
|
68
|
+
password: 'password123',
|
|
69
|
+
agent: { name: 'First Agent', agent_type: 'generic' },
|
|
70
|
+
})
|
|
71
|
+
.expect(201);
|
|
72
|
+
|
|
73
|
+
const orgId = signup.body.org.id;
|
|
74
|
+
const apiKey = signup.body.api_key;
|
|
75
|
+
|
|
76
|
+
await api.get(`/v1/orgs/${orgId}/agents`).expect(401);
|
|
77
|
+
await api
|
|
78
|
+
.post(`/v1/orgs/${orgId}/agents`)
|
|
79
|
+
.set('Authorization', `Bearer ${apiKey}`)
|
|
80
|
+
.send({ name: 'Blocked Agent', agent_type: 'generic' })
|
|
81
|
+
.expect(401);
|
|
82
|
+
|
|
83
|
+
const created = await api
|
|
84
|
+
.post(`/v1/orgs/${orgId}/agents`)
|
|
85
|
+
.set('Cookie', signup.headers['set-cookie'])
|
|
86
|
+
.send({ name: 'Second Agent', agent_type: 'generic' })
|
|
87
|
+
.expect(201);
|
|
88
|
+
|
|
89
|
+
const agents = await api
|
|
90
|
+
.get(`/v1/orgs/${orgId}/agents`)
|
|
91
|
+
.set('Cookie', signup.headers['set-cookie'])
|
|
92
|
+
.expect(200);
|
|
93
|
+
|
|
94
|
+
expect(created.body.api_key).toBeTruthy();
|
|
95
|
+
expect(agents.body.agents).toHaveLength(2);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { afterAll, afterEach, beforeEach, describe, expect, test } from '@jest/globals';
|
|
2
|
+
import pool from '../../src/db/client';
|
|
3
|
+
import {
|
|
4
|
+
api,
|
|
5
|
+
closeDatabase,
|
|
6
|
+
createOrgAndAgent,
|
|
7
|
+
getWorkspaceCookie,
|
|
8
|
+
migrateTestDatabase,
|
|
9
|
+
resetDatabase,
|
|
10
|
+
} from './helpers';
|
|
11
|
+
|
|
12
|
+
async function ownerEmail(orgId: string): Promise<string> {
|
|
13
|
+
const result = await pool.query(
|
|
14
|
+
`SELECT email
|
|
15
|
+
FROM workspace_users
|
|
16
|
+
WHERE org_id = $1
|
|
17
|
+
ORDER BY created_at ASC
|
|
18
|
+
LIMIT 1`,
|
|
19
|
+
[orgId],
|
|
20
|
+
);
|
|
21
|
+
return result.rows[0].email;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
describe('system diagnostics routes', () => {
|
|
25
|
+
const previousAdminEmails = process.env.ADMIN_EMAILS;
|
|
26
|
+
|
|
27
|
+
beforeEach(async () => {
|
|
28
|
+
await migrateTestDatabase();
|
|
29
|
+
await resetDatabase();
|
|
30
|
+
process.env.ADMIN_EMAILS = '';
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
afterEach(() => {
|
|
34
|
+
process.env.ADMIN_EMAILS = previousAdminEmails;
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
afterAll(async () => {
|
|
38
|
+
await resetDatabase();
|
|
39
|
+
await closeDatabase();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('health returns public database and migration status', async () => {
|
|
43
|
+
const response = await api.get('/health').expect((res) => {
|
|
44
|
+
expect([200, 503]).toContain(res.status);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
expect(response.headers['x-request-id']).toEqual(expect.any(String));
|
|
48
|
+
expect(response.body).toMatchObject({
|
|
49
|
+
db: 'connected',
|
|
50
|
+
version: expect.any(String),
|
|
51
|
+
migrations: {
|
|
52
|
+
total: expect.any(Number),
|
|
53
|
+
applied: expect.any(Number),
|
|
54
|
+
failed: expect.any(Number),
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('admin system requires admin session and redacts env values', async () => {
|
|
60
|
+
const { orgId } = await createOrgAndAgent('System Admin', 'generic', 0);
|
|
61
|
+
|
|
62
|
+
await api.get('/v1/admin/system').expect(401);
|
|
63
|
+
await api.get('/v1/admin/system').set('Cookie', getWorkspaceCookie(orgId)).expect(403);
|
|
64
|
+
|
|
65
|
+
process.env.ADMIN_EMAILS = await ownerEmail(orgId);
|
|
66
|
+
process.env.STRIPE_SECRET_KEY = 'sk_test_should_not_leak';
|
|
67
|
+
const response = await api
|
|
68
|
+
.get('/v1/admin/system')
|
|
69
|
+
.set('Cookie', getWorkspaceCookie(orgId))
|
|
70
|
+
.expect(200);
|
|
71
|
+
|
|
72
|
+
expect(response.body).toMatchObject({
|
|
73
|
+
app: {
|
|
74
|
+
version: expect.any(String),
|
|
75
|
+
node_version: expect.any(String),
|
|
76
|
+
},
|
|
77
|
+
database: { connected: true },
|
|
78
|
+
env: {
|
|
79
|
+
required: expect.any(Array),
|
|
80
|
+
missing: expect.any(Array),
|
|
81
|
+
},
|
|
82
|
+
stripe: {
|
|
83
|
+
configured: true,
|
|
84
|
+
},
|
|
85
|
+
migrations: {
|
|
86
|
+
total: expect.any(Number),
|
|
87
|
+
applied: expect.any(Number),
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
expect(JSON.stringify(response.body)).not.toContain('sk_test_should_not_leak');
|
|
91
|
+
});
|
|
92
|
+
});
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, test } from '@jest/globals';
|
|
2
|
+
import {
|
|
3
|
+
api,
|
|
4
|
+
closeDatabase,
|
|
5
|
+
createAgent,
|
|
6
|
+
createOrgAndAgent,
|
|
7
|
+
getWorkspaceCookie,
|
|
8
|
+
migrateTestDatabase,
|
|
9
|
+
resetDatabase,
|
|
10
|
+
} from './helpers';
|
|
11
|
+
import pool from '../../src/db/client';
|
|
12
|
+
|
|
13
|
+
describe('value health route', () => {
|
|
14
|
+
beforeAll(async () => {
|
|
15
|
+
await migrateTestDatabase();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
beforeEach(async () => {
|
|
19
|
+
await resetDatabase();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
afterAll(async () => {
|
|
23
|
+
await resetDatabase();
|
|
24
|
+
await closeDatabase();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('excludes setup and verification traffic from real activity and value metrics', async () => {
|
|
28
|
+
const workspace = await createOrgAndAgent('Setup Only Agent', 'generic');
|
|
29
|
+
await pool.query('UPDATE orgs SET verified_at = NOW() WHERE id = $1', [workspace.orgId]);
|
|
30
|
+
|
|
31
|
+
await pool.query(
|
|
32
|
+
`INSERT INTO agent_activity_events (org_id, agent_id, activity_type, status, metadata)
|
|
33
|
+
VALUES
|
|
34
|
+
($1, $2, 'setup_test_work_claim', 'success', '{"source":"setup_test"}'),
|
|
35
|
+
($1, $2, 'verification_run', 'success', '{"source":"onboarding_verification"}'),
|
|
36
|
+
($1, $2, 'connection_test', 'success', '{}')`,
|
|
37
|
+
[workspace.orgId, workspace.agentId],
|
|
38
|
+
);
|
|
39
|
+
await pool.query(
|
|
40
|
+
`INSERT INTO work_claim_events (event_type, agent_id, org_id, metadata)
|
|
41
|
+
VALUES ('blocked', $1, $2, '{"source":"setup_test"}')`,
|
|
42
|
+
[workspace.agentId, workspace.orgId],
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const response = await api
|
|
46
|
+
.get('/v1/value/health')
|
|
47
|
+
.set('Cookie', getWorkspaceCookie(workspace.orgId))
|
|
48
|
+
.expect(200);
|
|
49
|
+
|
|
50
|
+
expect(response.body.summary).toMatchObject({
|
|
51
|
+
conflicts_prevented_7d: 0,
|
|
52
|
+
blocked_work_runs_7d: 0,
|
|
53
|
+
calendar_conflicts_7d: 0,
|
|
54
|
+
api_calls_7d: 0,
|
|
55
|
+
errors_7d: 0,
|
|
56
|
+
latest_real_activity_at: null,
|
|
57
|
+
});
|
|
58
|
+
expect(response.body.agent_health[0]).toMatchObject({
|
|
59
|
+
agent_id: workspace.agentId,
|
|
60
|
+
status: 'never_connected',
|
|
61
|
+
});
|
|
62
|
+
expect(response.body.next_best_action).toBe('connect_first_agent');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('counts real blocked work runs, calendar conflicts, and recent API activity', async () => {
|
|
66
|
+
const workspace = await createOrgAndAgent('Real Agent', 'external_meeting');
|
|
67
|
+
await pool.query('UPDATE orgs SET verified_at = NOW() WHERE id = $1', [workspace.orgId]);
|
|
68
|
+
|
|
69
|
+
await pool.query(
|
|
70
|
+
`INSERT INTO agent_activity_events (org_id, agent_id, activity_type, status, metadata)
|
|
71
|
+
VALUES ($1, $2, 'work_check', 'success', '{}')`,
|
|
72
|
+
[workspace.orgId, workspace.agentId],
|
|
73
|
+
);
|
|
74
|
+
await pool.query(
|
|
75
|
+
`INSERT INTO work_claim_events (event_type, agent_id, org_id, metadata)
|
|
76
|
+
VALUES ('blocked', $1, $2, '{}')`,
|
|
77
|
+
[workspace.agentId, workspace.orgId],
|
|
78
|
+
);
|
|
79
|
+
const hold = await pool.query<{ id: string }>(
|
|
80
|
+
`INSERT INTO holds (agent_id, org_id, start_at, end_at, status)
|
|
81
|
+
VALUES ($1, $2, NOW() + INTERVAL '1 day', NOW() + INTERVAL '1 day 30 minutes', 'confirmed')
|
|
82
|
+
RETURNING id`,
|
|
83
|
+
[workspace.agentId, workspace.orgId],
|
|
84
|
+
);
|
|
85
|
+
await pool.query(
|
|
86
|
+
`INSERT INTO hold_events (hold_id, event_type, agent_id, org_id, metadata)
|
|
87
|
+
VALUES ($1, 'conflict_won', $2, $3, '{}')`,
|
|
88
|
+
[hold.rows[0].id, workspace.agentId, workspace.orgId],
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const response = await api
|
|
92
|
+
.get('/v1/value/health')
|
|
93
|
+
.set('Cookie', getWorkspaceCookie(workspace.orgId))
|
|
94
|
+
.expect(200);
|
|
95
|
+
|
|
96
|
+
expect(response.body.summary).toMatchObject({
|
|
97
|
+
conflicts_prevented_7d: 2,
|
|
98
|
+
blocked_work_runs_7d: 1,
|
|
99
|
+
would_have_blocked_runs_7d: 0,
|
|
100
|
+
calendar_conflicts_7d: 1,
|
|
101
|
+
api_calls_7d: 1,
|
|
102
|
+
errors_7d: 0,
|
|
103
|
+
});
|
|
104
|
+
expect(response.body.summary.latest_real_activity_at).toEqual(expect.any(String));
|
|
105
|
+
expect(response.body.agent_health[0]).toMatchObject({
|
|
106
|
+
agent_id: workspace.agentId,
|
|
107
|
+
status: 'healthy',
|
|
108
|
+
mcp_status: 'offline',
|
|
109
|
+
});
|
|
110
|
+
expect(response.body.next_best_action).toBe('none');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('counts shadow blocked runs separately from prevented conflicts', async () => {
|
|
114
|
+
const workspace = await createOrgAndAgent('Shadow Agent', 'generic');
|
|
115
|
+
await pool.query('UPDATE orgs SET verified_at = NOW() WHERE id = $1', [workspace.orgId]);
|
|
116
|
+
|
|
117
|
+
await pool.query(
|
|
118
|
+
`INSERT INTO agent_activity_events (org_id, agent_id, activity_type, status, metadata)
|
|
119
|
+
VALUES ($1, $2, 'automation_shadow_blocked', 'success', '{"source":"shadow_mode"}')`,
|
|
120
|
+
[workspace.orgId, workspace.agentId],
|
|
121
|
+
);
|
|
122
|
+
await pool.query(
|
|
123
|
+
`INSERT INTO work_claim_events (event_type, agent_id, org_id, metadata)
|
|
124
|
+
VALUES ('shadow_blocked', $1, $2, '{"source":"shadow_mode"}')`,
|
|
125
|
+
[workspace.agentId, workspace.orgId],
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
const response = await api
|
|
129
|
+
.get('/v1/value/health')
|
|
130
|
+
.set('Cookie', getWorkspaceCookie(workspace.orgId))
|
|
131
|
+
.expect(200);
|
|
132
|
+
|
|
133
|
+
expect(response.body.summary).toMatchObject({
|
|
134
|
+
conflicts_prevented_7d: 0,
|
|
135
|
+
blocked_work_runs_7d: 0,
|
|
136
|
+
would_have_blocked_runs_7d: 1,
|
|
137
|
+
api_calls_7d: 1,
|
|
138
|
+
});
|
|
139
|
+
expect(response.body.agent_health[0]).toMatchObject({
|
|
140
|
+
agent_id: workspace.agentId,
|
|
141
|
+
status: 'healthy',
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test('classifies quiet, error, and never connected agents', async () => {
|
|
146
|
+
const workspace = await createOrgAndAgent('Quiet Agent', 'generic');
|
|
147
|
+
const errorAgent = await createAgent(workspace.orgId, 'Error Agent', 'generic');
|
|
148
|
+
const neverAgent = await createAgent(workspace.orgId, 'Never Agent', 'generic');
|
|
149
|
+
await pool.query('UPDATE orgs SET verified_at = NOW() WHERE id = $1', [workspace.orgId]);
|
|
150
|
+
|
|
151
|
+
await pool.query(
|
|
152
|
+
`INSERT INTO agent_activity_events (org_id, agent_id, activity_type, status, created_at, metadata)
|
|
153
|
+
VALUES
|
|
154
|
+
($1, $2, 'work_check', 'success', NOW() - INTERVAL '4 days', '{}'),
|
|
155
|
+
($1, $3, 'work_claim', 'error', NOW() - INTERVAL '1 hour', '{}')`,
|
|
156
|
+
[workspace.orgId, workspace.agentId, errorAgent.agentId],
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
const response = await api
|
|
160
|
+
.get('/v1/value/health')
|
|
161
|
+
.set('Cookie', getWorkspaceCookie(workspace.orgId))
|
|
162
|
+
.expect(200);
|
|
163
|
+
|
|
164
|
+
const byId = new Map(response.body.agent_health.map((agent: { agent_id: string; status: string }) => [agent.agent_id, agent.status]));
|
|
165
|
+
expect(byId.get(workspace.agentId)).toBe('quiet');
|
|
166
|
+
expect(byId.get(errorAgent.agentId)).toBe('error');
|
|
167
|
+
expect(byId.get(neverAgent.agentId)).toBe('never_connected');
|
|
168
|
+
expect(response.body.next_best_action).toBe('review_errors');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test('uses agent last_seen_at for SDK and REST connectivity without requiring MCP heartbeat', async () => {
|
|
172
|
+
const workspace = await createOrgAndAgent('SDK Agent', 'generic');
|
|
173
|
+
await pool.query('UPDATE orgs SET verified_at = NOW() WHERE id = $1', [workspace.orgId]);
|
|
174
|
+
await pool.query('UPDATE agents SET last_seen_at = NOW() WHERE id = $1', [workspace.agentId]);
|
|
175
|
+
|
|
176
|
+
const response = await api
|
|
177
|
+
.get('/v1/value/health')
|
|
178
|
+
.set('Cookie', getWorkspaceCookie(workspace.orgId))
|
|
179
|
+
.expect(200);
|
|
180
|
+
|
|
181
|
+
expect(response.body.agent_health[0]).toMatchObject({
|
|
182
|
+
agent_id: workspace.agentId,
|
|
183
|
+
status: 'healthy',
|
|
184
|
+
last_seen_at: expect.any(String),
|
|
185
|
+
last_activity_at: null,
|
|
186
|
+
mcp_status: 'offline',
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test('classifies stale last_seen_at as quiet when no newer activity exists', async () => {
|
|
191
|
+
const workspace = await createOrgAndAgent('Old SDK Agent', 'generic');
|
|
192
|
+
await pool.query('UPDATE orgs SET verified_at = NOW() WHERE id = $1', [workspace.orgId]);
|
|
193
|
+
await pool.query("UPDATE agents SET last_seen_at = NOW() - INTERVAL '4 days' WHERE id = $1", [workspace.agentId]);
|
|
194
|
+
|
|
195
|
+
const response = await api
|
|
196
|
+
.get('/v1/value/health')
|
|
197
|
+
.set('Cookie', getWorkspaceCookie(workspace.orgId))
|
|
198
|
+
.expect(200);
|
|
199
|
+
|
|
200
|
+
expect(response.body.agent_health[0]).toMatchObject({
|
|
201
|
+
agent_id: workspace.agentId,
|
|
202
|
+
status: 'quiet',
|
|
203
|
+
mcp_status: 'offline',
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test('returns check_inactive_agents when workspace only has stale activity', async () => {
|
|
208
|
+
const workspace = await createOrgAndAgent('Stale Agent', 'generic');
|
|
209
|
+
await pool.query('UPDATE orgs SET verified_at = NOW() WHERE id = $1', [workspace.orgId]);
|
|
210
|
+
await pool.query(
|
|
211
|
+
`INSERT INTO agent_activity_events (org_id, agent_id, activity_type, status, created_at, metadata)
|
|
212
|
+
VALUES ($1, $2, 'work_check', 'success', NOW() - INTERVAL '4 days', '{}')`,
|
|
213
|
+
[workspace.orgId, workspace.agentId],
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
const response = await api
|
|
217
|
+
.get('/v1/value/health')
|
|
218
|
+
.set('Cookie', getWorkspaceCookie(workspace.orgId))
|
|
219
|
+
.expect(200);
|
|
220
|
+
|
|
221
|
+
expect(response.body.agent_health[0].status).toBe('quiet');
|
|
222
|
+
expect(response.body.next_best_action).toBe('check_inactive_agents');
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test('prioritizes onboarding verification before other next actions', async () => {
|
|
226
|
+
const workspace = await createOrgAndAgent('Unverified Agent', 'generic');
|
|
227
|
+
|
|
228
|
+
const response = await api
|
|
229
|
+
.get('/v1/value/health')
|
|
230
|
+
.set('Cookie', getWorkspaceCookie(workspace.orgId))
|
|
231
|
+
.expect(200);
|
|
232
|
+
|
|
233
|
+
expect(response.body.next_best_action).toBe('run_verification');
|
|
234
|
+
});
|
|
235
|
+
});
|