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.
Files changed (460) hide show
  1. package/.adal/skills/stripe-best-practices/SKILL.md +42 -0
  2. package/.adal/skills/stripe-best-practices/references/billing.md +36 -0
  3. package/.adal/skills/stripe-best-practices/references/connect.md +48 -0
  4. package/.adal/skills/stripe-best-practices/references/payments.md +79 -0
  5. package/.adal/skills/stripe-best-practices/references/security.md +109 -0
  6. package/.adal/skills/stripe-best-practices/references/treasury.md +16 -0
  7. package/.adal/skills/stripe-projects/SKILL.md +139 -0
  8. package/.adal/skills/upgrade-stripe/SKILL.md +185 -0
  9. package/.agents/skills/stripe-best-practices/SKILL.md +42 -0
  10. package/.agents/skills/stripe-best-practices/references/billing.md +36 -0
  11. package/.agents/skills/stripe-best-practices/references/connect.md +48 -0
  12. package/.agents/skills/stripe-best-practices/references/payments.md +79 -0
  13. package/.agents/skills/stripe-best-practices/references/security.md +109 -0
  14. package/.agents/skills/stripe-best-practices/references/treasury.md +16 -0
  15. package/.agents/skills/stripe-projects/SKILL.md +139 -0
  16. package/.agents/skills/upgrade-stripe/SKILL.md +185 -0
  17. package/.augment/skills/stripe-best-practices/SKILL.md +42 -0
  18. package/.augment/skills/stripe-best-practices/references/billing.md +36 -0
  19. package/.augment/skills/stripe-best-practices/references/connect.md +48 -0
  20. package/.augment/skills/stripe-best-practices/references/payments.md +79 -0
  21. package/.augment/skills/stripe-best-practices/references/security.md +109 -0
  22. package/.augment/skills/stripe-best-practices/references/treasury.md +16 -0
  23. package/.augment/skills/stripe-projects/SKILL.md +139 -0
  24. package/.augment/skills/upgrade-stripe/SKILL.md +185 -0
  25. package/.bob/skills/stripe-best-practices/SKILL.md +42 -0
  26. package/.bob/skills/stripe-best-practices/references/billing.md +36 -0
  27. package/.bob/skills/stripe-best-practices/references/connect.md +48 -0
  28. package/.bob/skills/stripe-best-practices/references/payments.md +79 -0
  29. package/.bob/skills/stripe-best-practices/references/security.md +109 -0
  30. package/.bob/skills/stripe-best-practices/references/treasury.md +16 -0
  31. package/.bob/skills/stripe-projects/SKILL.md +139 -0
  32. package/.bob/skills/upgrade-stripe/SKILL.md +185 -0
  33. package/.claude/settings.local.json +7 -0
  34. package/.claude/skills/stripe-best-practices/SKILL.md +42 -0
  35. package/.claude/skills/stripe-best-practices/references/billing.md +36 -0
  36. package/.claude/skills/stripe-best-practices/references/connect.md +48 -0
  37. package/.claude/skills/stripe-best-practices/references/payments.md +79 -0
  38. package/.claude/skills/stripe-best-practices/references/security.md +109 -0
  39. package/.claude/skills/stripe-best-practices/references/treasury.md +16 -0
  40. package/.claude/skills/stripe-projects/SKILL.md +139 -0
  41. package/.claude/skills/upgrade-stripe/SKILL.md +185 -0
  42. package/.codebuddy/skills/stripe-best-practices/SKILL.md +42 -0
  43. package/.codebuddy/skills/stripe-best-practices/references/billing.md +36 -0
  44. package/.codebuddy/skills/stripe-best-practices/references/connect.md +48 -0
  45. package/.codebuddy/skills/stripe-best-practices/references/payments.md +79 -0
  46. package/.codebuddy/skills/stripe-best-practices/references/security.md +109 -0
  47. package/.codebuddy/skills/stripe-best-practices/references/treasury.md +16 -0
  48. package/.codebuddy/skills/stripe-projects/SKILL.md +139 -0
  49. package/.codebuddy/skills/upgrade-stripe/SKILL.md +185 -0
  50. package/.commandcode/skills/stripe-best-practices/SKILL.md +42 -0
  51. package/.commandcode/skills/stripe-best-practices/references/billing.md +36 -0
  52. package/.commandcode/skills/stripe-best-practices/references/connect.md +48 -0
  53. package/.commandcode/skills/stripe-best-practices/references/payments.md +79 -0
  54. package/.commandcode/skills/stripe-best-practices/references/security.md +109 -0
  55. package/.commandcode/skills/stripe-best-practices/references/treasury.md +16 -0
  56. package/.commandcode/skills/stripe-projects/SKILL.md +139 -0
  57. package/.commandcode/skills/upgrade-stripe/SKILL.md +185 -0
  58. package/.continue/skills/stripe-best-practices/SKILL.md +42 -0
  59. package/.continue/skills/stripe-best-practices/references/billing.md +36 -0
  60. package/.continue/skills/stripe-best-practices/references/connect.md +48 -0
  61. package/.continue/skills/stripe-best-practices/references/payments.md +79 -0
  62. package/.continue/skills/stripe-best-practices/references/security.md +109 -0
  63. package/.continue/skills/stripe-best-practices/references/treasury.md +16 -0
  64. package/.continue/skills/stripe-projects/SKILL.md +139 -0
  65. package/.continue/skills/upgrade-stripe/SKILL.md +185 -0
  66. package/.cortex/skills/stripe-best-practices/SKILL.md +42 -0
  67. package/.cortex/skills/stripe-best-practices/references/billing.md +36 -0
  68. package/.cortex/skills/stripe-best-practices/references/connect.md +48 -0
  69. package/.cortex/skills/stripe-best-practices/references/payments.md +79 -0
  70. package/.cortex/skills/stripe-best-practices/references/security.md +109 -0
  71. package/.cortex/skills/stripe-best-practices/references/treasury.md +16 -0
  72. package/.cortex/skills/stripe-projects/SKILL.md +139 -0
  73. package/.cortex/skills/upgrade-stripe/SKILL.md +185 -0
  74. package/.crush/skills/stripe-best-practices/SKILL.md +42 -0
  75. package/.crush/skills/stripe-best-practices/references/billing.md +36 -0
  76. package/.crush/skills/stripe-best-practices/references/connect.md +48 -0
  77. package/.crush/skills/stripe-best-practices/references/payments.md +79 -0
  78. package/.crush/skills/stripe-best-practices/references/security.md +109 -0
  79. package/.crush/skills/stripe-best-practices/references/treasury.md +16 -0
  80. package/.crush/skills/stripe-projects/SKILL.md +139 -0
  81. package/.crush/skills/upgrade-stripe/SKILL.md +185 -0
  82. package/.env.example +20 -0
  83. package/.factory/skills/stripe-best-practices/SKILL.md +42 -0
  84. package/.factory/skills/stripe-best-practices/references/billing.md +36 -0
  85. package/.factory/skills/stripe-best-practices/references/connect.md +48 -0
  86. package/.factory/skills/stripe-best-practices/references/payments.md +79 -0
  87. package/.factory/skills/stripe-best-practices/references/security.md +109 -0
  88. package/.factory/skills/stripe-best-practices/references/treasury.md +16 -0
  89. package/.factory/skills/stripe-projects/SKILL.md +139 -0
  90. package/.factory/skills/upgrade-stripe/SKILL.md +185 -0
  91. package/.goose/skills/stripe-best-practices/SKILL.md +42 -0
  92. package/.goose/skills/stripe-best-practices/references/billing.md +36 -0
  93. package/.goose/skills/stripe-best-practices/references/connect.md +48 -0
  94. package/.goose/skills/stripe-best-practices/references/payments.md +79 -0
  95. package/.goose/skills/stripe-best-practices/references/security.md +109 -0
  96. package/.goose/skills/stripe-best-practices/references/treasury.md +16 -0
  97. package/.goose/skills/stripe-projects/SKILL.md +139 -0
  98. package/.goose/skills/upgrade-stripe/SKILL.md +185 -0
  99. package/.iflow/skills/stripe-best-practices/SKILL.md +42 -0
  100. package/.iflow/skills/stripe-best-practices/references/billing.md +36 -0
  101. package/.iflow/skills/stripe-best-practices/references/connect.md +48 -0
  102. package/.iflow/skills/stripe-best-practices/references/payments.md +79 -0
  103. package/.iflow/skills/stripe-best-practices/references/security.md +109 -0
  104. package/.iflow/skills/stripe-best-practices/references/treasury.md +16 -0
  105. package/.iflow/skills/stripe-projects/SKILL.md +139 -0
  106. package/.iflow/skills/upgrade-stripe/SKILL.md +185 -0
  107. package/.junie/skills/stripe-best-practices/SKILL.md +42 -0
  108. package/.junie/skills/stripe-best-practices/references/billing.md +36 -0
  109. package/.junie/skills/stripe-best-practices/references/connect.md +48 -0
  110. package/.junie/skills/stripe-best-practices/references/payments.md +79 -0
  111. package/.junie/skills/stripe-best-practices/references/security.md +109 -0
  112. package/.junie/skills/stripe-best-practices/references/treasury.md +16 -0
  113. package/.junie/skills/stripe-projects/SKILL.md +139 -0
  114. package/.junie/skills/upgrade-stripe/SKILL.md +185 -0
  115. package/.kilocode/skills/stripe-best-practices/SKILL.md +42 -0
  116. package/.kilocode/skills/stripe-best-practices/references/billing.md +36 -0
  117. package/.kilocode/skills/stripe-best-practices/references/connect.md +48 -0
  118. package/.kilocode/skills/stripe-best-practices/references/payments.md +79 -0
  119. package/.kilocode/skills/stripe-best-practices/references/security.md +109 -0
  120. package/.kilocode/skills/stripe-best-practices/references/treasury.md +16 -0
  121. package/.kilocode/skills/stripe-projects/SKILL.md +139 -0
  122. package/.kilocode/skills/upgrade-stripe/SKILL.md +185 -0
  123. package/.kiro/skills/stripe-best-practices/SKILL.md +42 -0
  124. package/.kiro/skills/stripe-best-practices/references/billing.md +36 -0
  125. package/.kiro/skills/stripe-best-practices/references/connect.md +48 -0
  126. package/.kiro/skills/stripe-best-practices/references/payments.md +79 -0
  127. package/.kiro/skills/stripe-best-practices/references/security.md +109 -0
  128. package/.kiro/skills/stripe-best-practices/references/treasury.md +16 -0
  129. package/.kiro/skills/stripe-projects/SKILL.md +139 -0
  130. package/.kiro/skills/upgrade-stripe/SKILL.md +185 -0
  131. package/.kode/skills/stripe-best-practices/SKILL.md +42 -0
  132. package/.kode/skills/stripe-best-practices/references/billing.md +36 -0
  133. package/.kode/skills/stripe-best-practices/references/connect.md +48 -0
  134. package/.kode/skills/stripe-best-practices/references/payments.md +79 -0
  135. package/.kode/skills/stripe-best-practices/references/security.md +109 -0
  136. package/.kode/skills/stripe-best-practices/references/treasury.md +16 -0
  137. package/.kode/skills/stripe-projects/SKILL.md +139 -0
  138. package/.kode/skills/upgrade-stripe/SKILL.md +185 -0
  139. package/.mcpjam/skills/stripe-best-practices/SKILL.md +42 -0
  140. package/.mcpjam/skills/stripe-best-practices/references/billing.md +36 -0
  141. package/.mcpjam/skills/stripe-best-practices/references/connect.md +48 -0
  142. package/.mcpjam/skills/stripe-best-practices/references/payments.md +79 -0
  143. package/.mcpjam/skills/stripe-best-practices/references/security.md +109 -0
  144. package/.mcpjam/skills/stripe-best-practices/references/treasury.md +16 -0
  145. package/.mcpjam/skills/stripe-projects/SKILL.md +139 -0
  146. package/.mcpjam/skills/upgrade-stripe/SKILL.md +185 -0
  147. package/.mux/skills/stripe-best-practices/SKILL.md +42 -0
  148. package/.mux/skills/stripe-best-practices/references/billing.md +36 -0
  149. package/.mux/skills/stripe-best-practices/references/connect.md +48 -0
  150. package/.mux/skills/stripe-best-practices/references/payments.md +79 -0
  151. package/.mux/skills/stripe-best-practices/references/security.md +109 -0
  152. package/.mux/skills/stripe-best-practices/references/treasury.md +16 -0
  153. package/.mux/skills/stripe-projects/SKILL.md +139 -0
  154. package/.mux/skills/upgrade-stripe/SKILL.md +185 -0
  155. package/.neovate/skills/stripe-best-practices/SKILL.md +42 -0
  156. package/.neovate/skills/stripe-best-practices/references/billing.md +36 -0
  157. package/.neovate/skills/stripe-best-practices/references/connect.md +48 -0
  158. package/.neovate/skills/stripe-best-practices/references/payments.md +79 -0
  159. package/.neovate/skills/stripe-best-practices/references/security.md +109 -0
  160. package/.neovate/skills/stripe-best-practices/references/treasury.md +16 -0
  161. package/.neovate/skills/stripe-projects/SKILL.md +139 -0
  162. package/.neovate/skills/upgrade-stripe/SKILL.md +185 -0
  163. package/.nixpacksignore +14 -0
  164. package/.openhands/skills/stripe-best-practices/SKILL.md +42 -0
  165. package/.openhands/skills/stripe-best-practices/references/billing.md +36 -0
  166. package/.openhands/skills/stripe-best-practices/references/connect.md +48 -0
  167. package/.openhands/skills/stripe-best-practices/references/payments.md +79 -0
  168. package/.openhands/skills/stripe-best-practices/references/security.md +109 -0
  169. package/.openhands/skills/stripe-best-practices/references/treasury.md +16 -0
  170. package/.openhands/skills/stripe-projects/SKILL.md +139 -0
  171. package/.openhands/skills/upgrade-stripe/SKILL.md +185 -0
  172. package/.pi/skills/stripe-best-practices/SKILL.md +42 -0
  173. package/.pi/skills/stripe-best-practices/references/billing.md +36 -0
  174. package/.pi/skills/stripe-best-practices/references/connect.md +48 -0
  175. package/.pi/skills/stripe-best-practices/references/payments.md +79 -0
  176. package/.pi/skills/stripe-best-practices/references/security.md +109 -0
  177. package/.pi/skills/stripe-best-practices/references/treasury.md +16 -0
  178. package/.pi/skills/stripe-projects/SKILL.md +139 -0
  179. package/.pi/skills/upgrade-stripe/SKILL.md +185 -0
  180. package/.pochi/skills/stripe-best-practices/SKILL.md +42 -0
  181. package/.pochi/skills/stripe-best-practices/references/billing.md +36 -0
  182. package/.pochi/skills/stripe-best-practices/references/connect.md +48 -0
  183. package/.pochi/skills/stripe-best-practices/references/payments.md +79 -0
  184. package/.pochi/skills/stripe-best-practices/references/security.md +109 -0
  185. package/.pochi/skills/stripe-best-practices/references/treasury.md +16 -0
  186. package/.pochi/skills/stripe-projects/SKILL.md +139 -0
  187. package/.pochi/skills/upgrade-stripe/SKILL.md +185 -0
  188. package/.qoder/skills/stripe-best-practices/SKILL.md +42 -0
  189. package/.qoder/skills/stripe-best-practices/references/billing.md +36 -0
  190. package/.qoder/skills/stripe-best-practices/references/connect.md +48 -0
  191. package/.qoder/skills/stripe-best-practices/references/payments.md +79 -0
  192. package/.qoder/skills/stripe-best-practices/references/security.md +109 -0
  193. package/.qoder/skills/stripe-best-practices/references/treasury.md +16 -0
  194. package/.qoder/skills/stripe-projects/SKILL.md +139 -0
  195. package/.qoder/skills/upgrade-stripe/SKILL.md +185 -0
  196. package/.qwen/skills/stripe-best-practices/SKILL.md +42 -0
  197. package/.qwen/skills/stripe-best-practices/references/billing.md +36 -0
  198. package/.qwen/skills/stripe-best-practices/references/connect.md +48 -0
  199. package/.qwen/skills/stripe-best-practices/references/payments.md +79 -0
  200. package/.qwen/skills/stripe-best-practices/references/security.md +109 -0
  201. package/.qwen/skills/stripe-best-practices/references/treasury.md +16 -0
  202. package/.qwen/skills/stripe-projects/SKILL.md +139 -0
  203. package/.qwen/skills/upgrade-stripe/SKILL.md +185 -0
  204. package/.roo/skills/stripe-best-practices/SKILL.md +42 -0
  205. package/.roo/skills/stripe-best-practices/references/billing.md +36 -0
  206. package/.roo/skills/stripe-best-practices/references/connect.md +48 -0
  207. package/.roo/skills/stripe-best-practices/references/payments.md +79 -0
  208. package/.roo/skills/stripe-best-practices/references/security.md +109 -0
  209. package/.roo/skills/stripe-best-practices/references/treasury.md +16 -0
  210. package/.roo/skills/stripe-projects/SKILL.md +139 -0
  211. package/.roo/skills/upgrade-stripe/SKILL.md +185 -0
  212. package/.trae/skills/stripe-best-practices/SKILL.md +42 -0
  213. package/.trae/skills/stripe-best-practices/references/billing.md +36 -0
  214. package/.trae/skills/stripe-best-practices/references/connect.md +48 -0
  215. package/.trae/skills/stripe-best-practices/references/payments.md +79 -0
  216. package/.trae/skills/stripe-best-practices/references/security.md +109 -0
  217. package/.trae/skills/stripe-best-practices/references/treasury.md +16 -0
  218. package/.trae/skills/stripe-projects/SKILL.md +139 -0
  219. package/.trae/skills/upgrade-stripe/SKILL.md +185 -0
  220. package/.vibe/skills/stripe-best-practices/SKILL.md +42 -0
  221. package/.vibe/skills/stripe-best-practices/references/billing.md +36 -0
  222. package/.vibe/skills/stripe-best-practices/references/connect.md +48 -0
  223. package/.vibe/skills/stripe-best-practices/references/payments.md +79 -0
  224. package/.vibe/skills/stripe-best-practices/references/security.md +109 -0
  225. package/.vibe/skills/stripe-best-practices/references/treasury.md +16 -0
  226. package/.vibe/skills/stripe-projects/SKILL.md +139 -0
  227. package/.vibe/skills/upgrade-stripe/SKILL.md +185 -0
  228. package/.windsurf/skills/stripe-best-practices/SKILL.md +42 -0
  229. package/.windsurf/skills/stripe-best-practices/references/billing.md +36 -0
  230. package/.windsurf/skills/stripe-best-practices/references/connect.md +48 -0
  231. package/.windsurf/skills/stripe-best-practices/references/payments.md +79 -0
  232. package/.windsurf/skills/stripe-best-practices/references/security.md +109 -0
  233. package/.windsurf/skills/stripe-best-practices/references/treasury.md +16 -0
  234. package/.windsurf/skills/stripe-projects/SKILL.md +139 -0
  235. package/.windsurf/skills/upgrade-stripe/SKILL.md +185 -0
  236. package/.zencoder/skills/stripe-best-practices/SKILL.md +42 -0
  237. package/.zencoder/skills/stripe-best-practices/references/billing.md +36 -0
  238. package/.zencoder/skills/stripe-best-practices/references/connect.md +48 -0
  239. package/.zencoder/skills/stripe-best-practices/references/payments.md +79 -0
  240. package/.zencoder/skills/stripe-best-practices/references/security.md +109 -0
  241. package/.zencoder/skills/stripe-best-practices/references/treasury.md +16 -0
  242. package/.zencoder/skills/stripe-projects/SKILL.md +139 -0
  243. package/.zencoder/skills/upgrade-stripe/SKILL.md +185 -0
  244. package/AUDIT.md +95 -0
  245. package/BLOCKERS.md +0 -0
  246. package/COOLIFY.md +51 -0
  247. package/MCP_SETUP.md +23 -0
  248. package/PRODUCTION_CHECKLIST.md +246 -0
  249. package/README.md +47 -0
  250. package/ROADMAP.md +91 -0
  251. package/docs/superpowers/plans/2026-05-11-availsync-frontend-sales-flow.md +2445 -0
  252. package/frontend/.env.example +2 -0
  253. package/frontend/app/admin/layout.tsx +13 -0
  254. package/frontend/app/admin/page.tsx +747 -0
  255. package/frontend/app/app/activity/page.tsx +257 -0
  256. package/frontend/app/app/agents/[agentId]/page.tsx +21 -0
  257. package/frontend/app/app/agents/page.tsx +1155 -0
  258. package/frontend/app/app/audit/page.tsx +225 -0
  259. package/frontend/app/app/availability/page.tsx +840 -0
  260. package/frontend/app/app/holds/page.tsx +262 -0
  261. package/frontend/app/app/layout.tsx +19 -0
  262. package/frontend/app/app/onboarding/page.tsx +10 -0
  263. package/frontend/app/app/onboarding/verify/page.tsx +309 -0
  264. package/frontend/app/app/page.tsx +508 -0
  265. package/frontend/app/app/settings/page.tsx +399 -0
  266. package/frontend/app/app/work/page.tsx +426 -0
  267. package/frontend/app/changelog/page.tsx +93 -0
  268. package/frontend/app/checkout/page.tsx +25 -0
  269. package/frontend/app/docs/api/page.tsx +157 -0
  270. package/frontend/app/docs/page.tsx +296 -0
  271. package/frontend/app/docs/pilot/page.tsx +127 -0
  272. package/frontend/app/docs/quickstart/page.tsx +318 -0
  273. package/frontend/app/docs/reliability/page.tsx +78 -0
  274. package/frontend/app/docs/sdk/node/page.tsx +166 -0
  275. package/frontend/app/globals.css +57 -0
  276. package/frontend/app/icon.png +0 -0
  277. package/frontend/app/layout.tsx +87 -0
  278. package/frontend/app/login/page.tsx +14 -0
  279. package/frontend/app/page.tsx +47 -0
  280. package/frontend/app/pricing/page.tsx +66 -0
  281. package/frontend/app/privacy/page.tsx +52 -0
  282. package/frontend/app/robots.ts +26 -0
  283. package/frontend/app/security/page.tsx +74 -0
  284. package/frontend/app/signup/page.tsx +14 -0
  285. package/frontend/app/sitemap.ts +14 -0
  286. package/frontend/app/terms/page.tsx +51 -0
  287. package/frontend/components/brand/AvailsyncLogo.tsx +56 -0
  288. package/frontend/components/checkout/CheckoutClient.tsx +100 -0
  289. package/frontend/components/dashboard/AgentForm.tsx +59 -0
  290. package/frontend/components/dashboard/AppShell.tsx +291 -0
  291. package/frontend/components/dashboard/AvailabilityChecker.tsx +117 -0
  292. package/frontend/components/dashboard/AvailabilityWindowForm.tsx +40 -0
  293. package/frontend/components/dashboard/HoldForm.tsx +133 -0
  294. package/frontend/components/dashboard/MetricCard.tsx +10 -0
  295. package/frontend/components/login/LoginForm.tsx +95 -0
  296. package/frontend/components/marketing/AgentCoordinationStory.tsx +1530 -0
  297. package/frontend/components/marketing/Faq.tsx +41 -0
  298. package/frontend/components/marketing/Hero.tsx +73 -0
  299. package/frontend/components/marketing/HowItWorks.tsx +28 -0
  300. package/frontend/components/marketing/ObserveModeTeaser.tsx +41 -0
  301. package/frontend/components/marketing/PricingTeaser.tsx +23 -0
  302. package/frontend/components/marketing/ProblemSolution.tsx +36 -0
  303. package/frontend/components/marketing/SiteFooter.tsx +59 -0
  304. package/frontend/components/marketing/SiteHeader.tsx +45 -0
  305. package/frontend/components/marketing/UseCases.tsx +27 -0
  306. package/frontend/components/onboarding/OnboardingClient.tsx +278 -0
  307. package/frontend/components/pricing/PricingCards.tsx +65 -0
  308. package/frontend/components/privacy/CookieConsent.tsx +230 -0
  309. package/frontend/components/privacy/CookieSettingsButton.tsx +15 -0
  310. package/frontend/components/seo/JsonLd.tsx +10 -0
  311. package/frontend/components/signup/SignupForm.tsx +55 -0
  312. package/frontend/components/ui/Badge.tsx +23 -0
  313. package/frontend/components/ui/Button.tsx +37 -0
  314. package/frontend/components/ui/Card.tsx +11 -0
  315. package/frontend/components/ui/ConfirmDialog.tsx +77 -0
  316. package/frontend/components/ui/EmptyState.tsx +24 -0
  317. package/frontend/components/ui/Input.tsx +14 -0
  318. package/frontend/components/ui/KeyDisplay.tsx +49 -0
  319. package/frontend/components/ui/Select.tsx +14 -0
  320. package/frontend/components/ui/Skeleton.tsx +24 -0
  321. package/frontend/components/ui/Tabs.tsx +19 -0
  322. package/frontend/components/ui/Textarea.tsx +14 -0
  323. package/frontend/components/ui/Toast.tsx +78 -0
  324. package/frontend/components/waitlist/WaitlistDialog.tsx +128 -0
  325. package/frontend/lib/api.ts +1282 -0
  326. package/frontend/lib/billing.ts +6 -0
  327. package/frontend/lib/cookieConsent.ts +113 -0
  328. package/frontend/lib/format.ts +16 -0
  329. package/frontend/lib/plans.ts +62 -0
  330. package/frontend/lib/schemas.ts +108 -0
  331. package/frontend/lib/seo.ts +376 -0
  332. package/frontend/lib/setupGuides.ts +630 -0
  333. package/frontend/lib/storage.ts +30 -0
  334. package/frontend/next-env.d.ts +6 -0
  335. package/frontend/next.config.mjs +13 -0
  336. package/frontend/package-lock.json +14409 -0
  337. package/frontend/package.json +41 -0
  338. package/frontend/playwright.config.ts +20 -0
  339. package/frontend/postcss.config.mjs +8 -0
  340. package/frontend/public/.gitkeep +0 -0
  341. package/frontend/public/brand/availsync-logo-board.png +0 -0
  342. package/frontend/public/brand/availsync-logo-dark.png +0 -0
  343. package/frontend/public/brand/availsync-mark-dark.png +0 -0
  344. package/frontend/public/brand/availsync-wordmark-dark.png +0 -0
  345. package/frontend/public/marketing/hero-agent-coordination.png +0 -0
  346. package/frontend/tailwind.config.ts +53 -0
  347. package/frontend/tests/smoke.spec.ts +89 -0
  348. package/frontend/tsconfig.json +23 -0
  349. package/jest.config.js +7 -0
  350. package/nixpacks.toml +11 -0
  351. package/package.json +53 -0
  352. package/packages/mcp/LICENSE +21 -0
  353. package/packages/mcp/README.md +60 -0
  354. package/packages/mcp/jest.config.cjs +8 -0
  355. package/packages/mcp/package.json +54 -0
  356. package/packages/mcp/src/helpers.ts +38 -0
  357. package/packages/mcp/src/index.test.ts +60 -0
  358. package/packages/mcp/src/index.ts +387 -0
  359. package/packages/mcp/tsconfig.json +20 -0
  360. package/packages/mcp/tsconfig.test.json +12 -0
  361. package/packages/node/LICENSE +21 -0
  362. package/packages/node/README.md +120 -0
  363. package/packages/node/jest.config.cjs +8 -0
  364. package/packages/node/package.json +46 -0
  365. package/packages/node/src/index.test.ts +360 -0
  366. package/packages/node/src/index.ts +402 -0
  367. package/packages/node/tsconfig.json +20 -0
  368. package/packages/node/tsconfig.test.json +12 -0
  369. package/plan.md +923 -0
  370. package/skills/stripe-best-practices/SKILL.md +42 -0
  371. package/skills/stripe-best-practices/references/billing.md +36 -0
  372. package/skills/stripe-best-practices/references/connect.md +48 -0
  373. package/skills/stripe-best-practices/references/payments.md +79 -0
  374. package/skills/stripe-best-practices/references/security.md +109 -0
  375. package/skills/stripe-best-practices/references/treasury.md +16 -0
  376. package/skills/stripe-projects/SKILL.md +139 -0
  377. package/skills/upgrade-stripe/SKILL.md +185 -0
  378. package/skills-lock.json +20 -0
  379. package/src/core/availability.ts +178 -0
  380. package/src/core/conflict.ts +209 -0
  381. package/src/core/work.ts +490 -0
  382. package/src/db/client.ts +17 -0
  383. package/src/db/migrations/001_init.sql +88 -0
  384. package/src/db/migrations/002_stripe.sql +2 -0
  385. package/src/db/migrations/003_workspace_auth.sql +19 -0
  386. package/src/db/migrations/004_agent_mcp_status.sql +2 -0
  387. package/src/db/migrations/005_hold_event_actor.sql +4 -0
  388. package/src/db/migrations/006_agent_activity.sql +35 -0
  389. package/src/db/migrations/007_work_coordination.sql +60 -0
  390. package/src/db/migrations/008_work_claim_leases.sql +20 -0
  391. package/src/db/migrations/009_billing_subscription_state.sql +23 -0
  392. package/src/db/migrations/010_agent_api_key_prefix.sql +10 -0
  393. package/src/db/migrations/011_org_verified_and_work_event_retention.sql +11 -0
  394. package/src/db/migrations/012_agent_enforcement_mode.sql +12 -0
  395. package/src/db/migrations/013_support_tickets.sql +21 -0
  396. package/src/db/migrations/014_paid_plan_waitlist.sql +23 -0
  397. package/src/db/migrations/015_agent_last_seen.sql +2 -0
  398. package/src/db/migrations.ts +164 -0
  399. package/src/db/run-migrations.ts +13 -0
  400. package/src/index.ts +183 -0
  401. package/src/lib/activity.ts +137 -0
  402. package/src/lib/apiKeys.ts +32 -0
  403. package/src/lib/appInfo.ts +26 -0
  404. package/src/lib/billingConfig.ts +3 -0
  405. package/src/lib/env.ts +75 -0
  406. package/src/lib/logger.ts +8 -0
  407. package/src/lib/plans.ts +204 -0
  408. package/src/mcp/server.js +5 -0
  409. package/src/mcp/server.ts +350 -0
  410. package/src/middleware/auth.ts +342 -0
  411. package/src/middleware/requestId.ts +16 -0
  412. package/src/routes/account.ts +168 -0
  413. package/src/routes/activity.ts +126 -0
  414. package/src/routes/admin.ts +514 -0
  415. package/src/routes/audit.ts +68 -0
  416. package/src/routes/auth.ts +203 -0
  417. package/src/routes/availability.ts +325 -0
  418. package/src/routes/billing.ts +406 -0
  419. package/src/routes/conflicts.ts +131 -0
  420. package/src/routes/holds.ts +437 -0
  421. package/src/routes/mcp.ts +57 -0
  422. package/src/routes/metrics.ts +39 -0
  423. package/src/routes/onboarding.ts +273 -0
  424. package/src/routes/orgs.ts +981 -0
  425. package/src/routes/preferences.ts +132 -0
  426. package/src/routes/session.ts +16 -0
  427. package/src/routes/support.ts +77 -0
  428. package/src/routes/value.ts +186 -0
  429. package/src/routes/waitlist.ts +63 -0
  430. package/src/routes/work.ts +1578 -0
  431. package/src/server.ts +36 -0
  432. package/src/types/index.ts +109 -0
  433. package/tests/integration/activity.route.test.ts +103 -0
  434. package/tests/integration/admin.route.test.ts +143 -0
  435. package/tests/integration/agent-keys.route.test.ts +237 -0
  436. package/tests/integration/availability.route.test.ts +125 -0
  437. package/tests/integration/billing.route.test.ts +393 -0
  438. package/tests/integration/conflicts.route.test.ts +131 -0
  439. package/tests/integration/flows.test.ts +154 -0
  440. package/tests/integration/helpers.ts +134 -0
  441. package/tests/integration/holds.route.test.ts +185 -0
  442. package/tests/integration/metrics.route.test.ts +100 -0
  443. package/tests/integration/onboarding.verify.route.test.ts +163 -0
  444. package/tests/integration/preferences.route.test.ts +53 -0
  445. package/tests/integration/session.route.test.ts +97 -0
  446. package/tests/integration/system.route.test.ts +92 -0
  447. package/tests/integration/value.route.test.ts +235 -0
  448. package/tests/integration/work.route.test.ts +745 -0
  449. package/tests/setup.ts +4 -0
  450. package/tests/smoke.sh +62 -0
  451. package/tests/unit/auth.test.ts +114 -0
  452. package/tests/unit/availability.test.ts +149 -0
  453. package/tests/unit/conflict.test.ts +118 -0
  454. package/tests/unit/env.test.ts +69 -0
  455. package/tests/unit/migrations.test.ts +135 -0
  456. package/tests/unit/request-id.test.ts +37 -0
  457. package/tmp-mobile-agents.png +0 -0
  458. package/tmp-next-mobile.err.log +10 -0
  459. package/tmp-next-mobile.log +5 -0
  460. package/tsconfig.json +16 -0
@@ -0,0 +1,154 @@
1
+ import { afterAll, beforeAll, beforeEach, describe, expect, test } from '@jest/globals';
2
+ import { addDays } from 'date-fns';
3
+ import { api, closeDatabase, createOrgAndAgent, createAgent, createWindow, migrateTestDatabase, resetDatabase } from './helpers';
4
+ import pool from '../../src/db/client';
5
+
6
+ describe('Flow C: Multi-agent conflict resolution', () => {
7
+ let orgId: string;
8
+ let salesBotId: string;
9
+ let salesBotKey: string;
10
+ let opsBotId: string;
11
+ let opsBotKey: string;
12
+
13
+ const tomorrow = addDays(new Date(), 1);
14
+ const startAt = new Date(tomorrow);
15
+ startAt.setUTCHours(9, 0, 0, 0);
16
+ const endAt = new Date(tomorrow);
17
+ endAt.setUTCHours(17, 0, 0, 0);
18
+ const holdStart = new Date(tomorrow);
19
+ holdStart.setUTCHours(10, 0, 0, 0);
20
+ const holdEnd = new Date(tomorrow);
21
+ holdEnd.setUTCHours(10, 30, 0, 0);
22
+
23
+ beforeAll(async () => {
24
+ await migrateTestDatabase();
25
+ });
26
+
27
+ beforeEach(async () => {
28
+ await resetDatabase();
29
+
30
+ // Create org with sales_bot (external_meeting) and ops_bot (internal)
31
+ const setup = await createOrgAndAgent('sales_bot', 'external_meeting', 10);
32
+ orgId = setup.orgId;
33
+ salesBotId = setup.agentId;
34
+ salesBotKey = setup.apiKey;
35
+
36
+ const opsSetup = await createAgent(orgId, 'ops_bot', 'internal', 5);
37
+ opsBotId = opsSetup.agentId;
38
+ opsBotKey = opsSetup.apiKey;
39
+
40
+ // Both agents have availability covering the same time
41
+ await createWindow(salesBotId, salesBotKey, startAt.toISOString(), endAt.toISOString());
42
+ await createWindow(opsBotId, opsBotKey, startAt.toISOString(), endAt.toISOString());
43
+ });
44
+
45
+ afterAll(async () => {
46
+ await resetDatabase();
47
+ await closeDatabase();
48
+ });
49
+
50
+ test('external_meeting agent wins over internal agent for same slot', async () => {
51
+ // Step 1: sales_bot books 10:00–10:30
52
+ const salesHold = await api
53
+ .post('/v1/holds')
54
+ .set('Authorization', `Bearer ${salesBotKey}`)
55
+ .send({
56
+ agent_id: salesBotId,
57
+ start_at: holdStart.toISOString(),
58
+ end_at: holdEnd.toISOString(),
59
+ reason: 'Client meeting',
60
+ })
61
+ .expect(201);
62
+
63
+ expect(salesHold.body.hold.status).toBe('confirmed');
64
+
65
+ // Step 2: ops_bot attempts to book same 10:00–10:30
66
+ const opsHold = await api
67
+ .post('/v1/holds')
68
+ .set('Authorization', `Bearer ${opsBotKey}`)
69
+ .send({
70
+ agent_id: opsBotId,
71
+ start_at: holdStart.toISOString(),
72
+ end_at: holdEnd.toISOString(),
73
+ reason: 'Team standup',
74
+ })
75
+ .expect(409);
76
+
77
+ expect(opsHold.body.error).toBe('slot_taken');
78
+ expect(opsHold.body.winning_hold_id).toBe(salesHold.body.hold.id);
79
+ expect(opsHold.body.suggested_alternatives).toBeDefined();
80
+ expect(opsHold.body.suggested_alternatives.length).toBeGreaterThan(0);
81
+
82
+ // Step 3: Verify no double-booking exists
83
+ const confirmedHolds = await pool.query(
84
+ `SELECT * FROM holds WHERE org_id = $1 AND status = 'confirmed'
85
+ AND start_at < $3 AND end_at > $2`,
86
+ [orgId, holdStart, holdEnd],
87
+ );
88
+ expect(confirmedHolds.rows.length).toBe(1);
89
+ expect(confirmedHolds.rows[0].agent_id).toBe(salesBotId);
90
+
91
+ // Step 4: Verify audit log
92
+ const events = await pool.query(
93
+ `SELECT event_type, agent_id FROM hold_events WHERE org_id = $1 ORDER BY created_at ASC`,
94
+ [orgId],
95
+ );
96
+ const eventTypes = events.rows.map((r: { event_type: string }) => r.event_type);
97
+ expect(eventTypes).toContain('created');
98
+ });
99
+
100
+ test('higher-priority agent supersedes lower-priority agent for same slot', async () => {
101
+ // Step 1: ops_bot (internal, lower priority) books first
102
+ const opsHold = await api
103
+ .post('/v1/holds')
104
+ .set('Authorization', `Bearer ${opsBotKey}`)
105
+ .send({
106
+ agent_id: opsBotId,
107
+ start_at: holdStart.toISOString(),
108
+ end_at: holdEnd.toISOString(),
109
+ reason: 'Team standup',
110
+ })
111
+ .expect(201);
112
+
113
+ expect(opsHold.body.hold.status).toBe('confirmed');
114
+
115
+ // Step 2: sales_bot (external_meeting, higher priority) books same slot and wins
116
+ const salesHold = await api
117
+ .post('/v1/holds')
118
+ .set('Authorization', `Bearer ${salesBotKey}`)
119
+ .send({
120
+ agent_id: salesBotId,
121
+ start_at: holdStart.toISOString(),
122
+ end_at: holdEnd.toISOString(),
123
+ reason: 'Client meeting',
124
+ })
125
+ .expect(201);
126
+
127
+ expect(salesHold.body.hold.status).toBe('confirmed');
128
+ expect(salesHold.body.conflict_resolved).toBe(true);
129
+ expect(salesHold.body.superseded_hold_id).toBe(opsHold.body.hold.id);
130
+
131
+ // Step 3: Verify ops_bot hold is superseded
132
+ const superseded = await pool.query('SELECT status FROM holds WHERE id = $1', [
133
+ opsHold.body.hold.id,
134
+ ]);
135
+ expect(superseded.rows[0].status).toBe('superseded');
136
+
137
+ // Step 4: Only one confirmed hold for the slot
138
+ const confirmedHolds = await pool.query(
139
+ `SELECT * FROM holds WHERE org_id = $1 AND status = 'confirmed'
140
+ AND start_at < $3 AND end_at > $2`,
141
+ [orgId, holdStart, holdEnd],
142
+ );
143
+ expect(confirmedHolds.rows.length).toBe(1);
144
+ expect(confirmedHolds.rows[0].agent_id).toBe(salesBotId);
145
+
146
+ // Step 5: Audit log shows superseded event
147
+ const events = await pool.query(
148
+ `SELECT event_type FROM hold_events WHERE org_id = $1 ORDER BY created_at ASC`,
149
+ [orgId],
150
+ );
151
+ const eventTypes = events.rows.map((r: { event_type: string }) => r.event_type);
152
+ expect(eventTypes).toContain('superseded');
153
+ });
154
+ });
@@ -0,0 +1,134 @@
1
+ import request from 'supertest';
2
+ import app from '../../src/index';
3
+ import pool from '../../src/db/client';
4
+ import { runMigrations } from '../../src/db/migrations';
5
+
6
+ export const api = request(app);
7
+ const workspaceCookiesByOrg = new Map<string, string>();
8
+ let databaseReady = false;
9
+
10
+ function formatTestDbError(err: unknown) {
11
+ const nestedErrors = (err as { errors?: unknown[] } | null)?.errors;
12
+ if (Array.isArray(nestedErrors)) {
13
+ const messages = nestedErrors
14
+ .map((nested) => (nested instanceof Error ? nested.message : String(nested)))
15
+ .filter(Boolean);
16
+ return messages.length > 0 ? messages.join('; ') : 'AggregateError';
17
+ }
18
+
19
+ if (err instanceof Error) {
20
+ return err.message || err.name;
21
+ }
22
+
23
+ return String(err);
24
+ }
25
+
26
+ export async function migrateTestDatabase(): Promise<void> {
27
+ databaseReady = false;
28
+ try {
29
+ await pool.query('SELECT 1');
30
+ await runMigrations(pool);
31
+ databaseReady = true;
32
+ } catch (err) {
33
+ const message = formatTestDbError(err);
34
+ throw new Error(
35
+ `Unable to connect to the integration test database. Set TEST_DATABASE_URL to a reachable PostgreSQL database. Original error: ${message}`,
36
+ );
37
+ }
38
+ }
39
+
40
+ export async function resetDatabase(): Promise<void> {
41
+ if (!databaseReady) {
42
+ return;
43
+ }
44
+
45
+ workspaceCookiesByOrg.clear();
46
+ try {
47
+ await pool.query('DELETE FROM workspace_sessions');
48
+ await pool.query('DELETE FROM workspace_users');
49
+ await pool.query('DELETE FROM agent_activity_events');
50
+ await pool.query('DELETE FROM work_claim_events');
51
+ await pool.query('DELETE FROM work_claims');
52
+ await pool.query('DELETE FROM work_resources');
53
+ await pool.query('DELETE FROM hold_events');
54
+ await pool.query('DELETE FROM holds');
55
+ await pool.query('DELETE FROM availability_windows');
56
+ await pool.query('DELETE FROM agent_preferences');
57
+ await pool.query('DELETE FROM agents');
58
+ await pool.query('DELETE FROM orgs');
59
+ } catch (err) {
60
+ const message = formatTestDbError(err);
61
+ throw new Error(
62
+ `Unable to reset the integration test database. Check TEST_DATABASE_URL and database permissions. Original error: ${message}`,
63
+ );
64
+ }
65
+ }
66
+
67
+ export async function closeDatabase(): Promise<void> {
68
+ await pool.end();
69
+ }
70
+
71
+ export async function createOrgAndAgent(
72
+ name = 'Test Bot',
73
+ agent_type: 'external_meeting' | 'internal' | 'focus' | 'generic' = 'external_meeting',
74
+ priority = 0,
75
+ ): Promise<{ orgId: string; agentId: string; apiKey: string }> {
76
+ const response = await api
77
+ .post('/v1/auth/signup')
78
+ .send({
79
+ workspace_name: `Org ${Date.now()} ${Math.random()}`,
80
+ email: `test-${Date.now()}-${Math.random()}@example.com`,
81
+ password: 'password123',
82
+ agent: { name, agent_type, priority },
83
+ })
84
+ .expect(201);
85
+ const orgId = response.body.org.id;
86
+ workspaceCookiesByOrg.set(orgId, response.headers['set-cookie']);
87
+
88
+ return {
89
+ orgId,
90
+ agentId: response.body.agent.id,
91
+ apiKey: response.body.api_key,
92
+ };
93
+ }
94
+
95
+ export function getWorkspaceCookie(orgId: string): string {
96
+ return workspaceCookiesByOrg.get(orgId) || '';
97
+ }
98
+
99
+ export async function createAgent(
100
+ orgId: string,
101
+ name: string,
102
+ agent_type: 'external_meeting' | 'internal' | 'focus' | 'generic',
103
+ priority = 0,
104
+ ): Promise<{ agentId: string; apiKey: string }> {
105
+ const cookies = workspaceCookiesByOrg.get(orgId);
106
+ const response = await api
107
+ .post(`/v1/orgs/${orgId}/agents`)
108
+ .set('Cookie', cookies || '')
109
+ .send({ name, agent_type, priority })
110
+ .expect(201);
111
+
112
+ return {
113
+ agentId: response.body.agent.id,
114
+ apiKey: response.body.api_key,
115
+ };
116
+ }
117
+
118
+ export async function createWindow(
119
+ agentId: string,
120
+ apiKey: string,
121
+ startAt: string,
122
+ endAt: string,
123
+ ): Promise<void> {
124
+ await api
125
+ .post('/v1/availability-windows')
126
+ .set('Authorization', `Bearer ${apiKey}`)
127
+ .send({
128
+ agent_id: agentId,
129
+ start_at: startAt,
130
+ end_at: endAt,
131
+ window_type: 'available',
132
+ })
133
+ .expect(201);
134
+ }
@@ -0,0 +1,185 @@
1
+ import { afterAll, beforeAll, describe, expect, test } from '@jest/globals';
2
+ import {
3
+ api,
4
+ closeDatabase,
5
+ createAgent,
6
+ createOrgAndAgent,
7
+ createWindow,
8
+ getWorkspaceCookie,
9
+ migrateTestDatabase,
10
+ resetDatabase,
11
+ } from './helpers';
12
+ import pool from '../../src/db/client';
13
+
14
+ describe('holds routes', () => {
15
+ let orgId: string;
16
+ let externalAgentId: string;
17
+ let externalApiKey: string;
18
+ let internalAgentId: string;
19
+ let internalApiKey: string;
20
+
21
+ beforeAll(async () => {
22
+ await migrateTestDatabase();
23
+ await resetDatabase();
24
+ const external = await createOrgAndAgent('External', 'external_meeting');
25
+ orgId = external.orgId;
26
+ externalAgentId = external.agentId;
27
+ externalApiKey = external.apiKey;
28
+ const internal = await createAgent(orgId, 'Internal', 'internal');
29
+ internalAgentId = internal.agentId;
30
+ internalApiKey = internal.apiKey;
31
+ await createWindow(externalAgentId, externalApiKey, '2030-01-02T09:00:00Z', '2030-01-02T17:00:00Z');
32
+ await createWindow(internalAgentId, internalApiKey, '2030-01-02T09:00:00Z', '2030-01-02T17:00:00Z');
33
+ });
34
+
35
+ afterAll(async () => {
36
+ await resetDatabase();
37
+ await closeDatabase();
38
+ });
39
+
40
+ test('POST /v1/holds books successfully and returns 201', async () => {
41
+ const response = await api
42
+ .post('/v1/holds')
43
+ .set('Authorization', `Bearer ${externalApiKey}`)
44
+ .send({
45
+ agent_id: externalAgentId,
46
+ start_at: '2030-01-02T09:00:00Z',
47
+ end_at: '2030-01-02T09:30:00Z',
48
+ reason: 'Intro call',
49
+ })
50
+ .expect(201);
51
+
52
+ expect(response.body.hold.status).toBe('confirmed');
53
+ const event = await pool.query(
54
+ 'SELECT actor_type, actor_id, actor_label FROM hold_events WHERE hold_id = $1 AND event_type = $2',
55
+ [response.body.hold.id, 'created'],
56
+ );
57
+ expect(event.rows[0]).toMatchObject({
58
+ actor_type: 'agent',
59
+ actor_id: externalAgentId,
60
+ actor_label: 'External',
61
+ });
62
+ });
63
+
64
+ test('workspace-created hold events include workspace actor metadata', async () => {
65
+ const response = await api
66
+ .post('/v1/holds')
67
+ .set('Cookie', getWorkspaceCookie(orgId))
68
+ .send({
69
+ agent_id: externalAgentId,
70
+ start_at: '2030-01-02T09:30:00Z',
71
+ end_at: '2030-01-02T10:00:00Z',
72
+ reason: 'Dashboard hold',
73
+ })
74
+ .expect(201);
75
+
76
+ const event = await pool.query(
77
+ 'SELECT actor_type, actor_id, actor_label FROM hold_events WHERE hold_id = $1 AND event_type = $2',
78
+ [response.body.hold.id, 'created'],
79
+ );
80
+ expect(event.rows[0]).toMatchObject({
81
+ actor_type: 'workspace',
82
+ actor_label: expect.stringContaining('@'),
83
+ });
84
+ expect(event.rows[0].actor_id).toEqual(expect.any(String));
85
+ });
86
+
87
+ test('agent key cannot create holds for another agent', async () => {
88
+ await api
89
+ .post('/v1/holds')
90
+ .set('Authorization', `Bearer ${externalApiKey}`)
91
+ .send({
92
+ agent_id: internalAgentId,
93
+ start_at: '2030-01-02T09:30:00Z',
94
+ end_at: '2030-01-02T10:00:00Z',
95
+ })
96
+ .expect(403);
97
+ });
98
+
99
+ test('lower-priority overlapping hold is superseded by higher-priority agent', async () => {
100
+ await api
101
+ .post('/v1/holds')
102
+ .set('Authorization', `Bearer ${internalApiKey}`)
103
+ .send({
104
+ agent_id: internalAgentId,
105
+ start_at: '2030-01-02T10:00:00Z',
106
+ end_at: '2030-01-02T10:30:00Z',
107
+ })
108
+ .expect(201);
109
+
110
+ const response = await api
111
+ .post('/v1/holds')
112
+ .set('Authorization', `Bearer ${externalApiKey}`)
113
+ .send({
114
+ agent_id: externalAgentId,
115
+ start_at: '2030-01-02T10:00:00Z',
116
+ end_at: '2030-01-02T10:30:00Z',
117
+ })
118
+ .expect(201);
119
+
120
+ expect(response.body.conflict_resolved).toBe(true);
121
+ expect(response.body.superseded_hold_id).toBeTruthy();
122
+ });
123
+
124
+ test('higher-priority overlapping hold returns 409 with alternatives', async () => {
125
+ await api
126
+ .post('/v1/holds')
127
+ .set('Authorization', `Bearer ${externalApiKey}`)
128
+ .send({
129
+ agent_id: externalAgentId,
130
+ start_at: '2030-01-02T11:00:00Z',
131
+ end_at: '2030-01-02T11:30:00Z',
132
+ })
133
+ .expect(201);
134
+
135
+ const response = await api
136
+ .post('/v1/holds')
137
+ .set('Authorization', `Bearer ${internalApiKey}`)
138
+ .send({
139
+ agent_id: internalAgentId,
140
+ start_at: '2030-01-02T11:00:00Z',
141
+ end_at: '2030-01-02T11:30:00Z',
142
+ })
143
+ .expect(409);
144
+
145
+ expect(response.body.error).toBe('slot_taken');
146
+ expect(response.body.suggested_alternatives).toHaveLength(3);
147
+ });
148
+
149
+ test('DELETE /v1/holds/:id releases a hold', async () => {
150
+ const created = await api
151
+ .post('/v1/holds')
152
+ .set('Authorization', `Bearer ${externalApiKey}`)
153
+ .send({
154
+ agent_id: externalAgentId,
155
+ start_at: '2030-01-02T12:00:00Z',
156
+ end_at: '2030-01-02T12:30:00Z',
157
+ })
158
+ .expect(201);
159
+
160
+ const response = await api
161
+ .delete(`/v1/holds/${created.body.hold.id}`)
162
+ .set('Authorization', `Bearer ${externalApiKey}`)
163
+ .expect(200);
164
+
165
+ expect(response.body.hold.status).toBe('released');
166
+ });
167
+
168
+ test('concurrent booking allows exactly one successful hold for the same slot', async () => {
169
+ const attempts = Array.from({ length: 10 }, () =>
170
+ api
171
+ .post('/v1/holds')
172
+ .set('Authorization', `Bearer ${internalApiKey}`)
173
+ .send({
174
+ agent_id: internalAgentId,
175
+ start_at: '2030-01-02T13:00:00Z',
176
+ end_at: '2030-01-02T13:30:00Z',
177
+ }),
178
+ );
179
+
180
+ const responses = await Promise.all(attempts);
181
+ const successes = responses.filter((response) => response.status === 201);
182
+
183
+ expect(successes).toHaveLength(1);
184
+ });
185
+ });
@@ -0,0 +1,100 @@
1
+ import { afterAll, beforeAll, describe, expect, test } from '@jest/globals';
2
+ import {
3
+ api,
4
+ closeDatabase,
5
+ createAgent,
6
+ createOrgAndAgent,
7
+ createWindow,
8
+ getWorkspaceCookie,
9
+ migrateTestDatabase,
10
+ resetDatabase,
11
+ } from './helpers';
12
+
13
+ describe('metrics routes', () => {
14
+ let orgId: string;
15
+ let externalAgentId: string;
16
+ let externalApiKey: string;
17
+ let internalAgentId: string;
18
+ let internalApiKey: string;
19
+
20
+ beforeAll(async () => {
21
+ await migrateTestDatabase();
22
+ await resetDatabase();
23
+ const external = await createOrgAndAgent('Metrics External', 'external_meeting');
24
+ orgId = external.orgId;
25
+ externalAgentId = external.agentId;
26
+ externalApiKey = external.apiKey;
27
+ const internal = await createAgent(orgId, 'Metrics Internal', 'internal');
28
+ internalAgentId = internal.agentId;
29
+ internalApiKey = internal.apiKey;
30
+ await createWindow(externalAgentId, externalApiKey, '2030-03-01T09:00:00Z', '2030-03-01T17:00:00Z');
31
+ await createWindow(internalAgentId, internalApiKey, '2030-03-01T09:00:00Z', '2030-03-01T17:00:00Z');
32
+ });
33
+
34
+ afterAll(async () => {
35
+ await resetDatabase();
36
+ await closeDatabase();
37
+ });
38
+
39
+ test('returns product metrics for the workspace', async () => {
40
+ const released = await api
41
+ .post('/v1/holds')
42
+ .set('Authorization', `Bearer ${externalApiKey}`)
43
+ .send({
44
+ agent_id: externalAgentId,
45
+ start_at: '2030-03-01T09:00:00Z',
46
+ end_at: '2030-03-01T09:30:00Z',
47
+ })
48
+ .expect(201);
49
+
50
+ await api
51
+ .delete(`/v1/holds/${released.body.hold.id}`)
52
+ .set('Authorization', `Bearer ${externalApiKey}`)
53
+ .expect(200);
54
+
55
+ await api
56
+ .post('/v1/holds')
57
+ .set('Authorization', `Bearer ${internalApiKey}`)
58
+ .send({
59
+ agent_id: internalAgentId,
60
+ start_at: '2030-03-01T10:00:00Z',
61
+ end_at: '2030-03-01T10:30:00Z',
62
+ })
63
+ .expect(201);
64
+
65
+ await api
66
+ .post('/v1/holds')
67
+ .set('Authorization', `Bearer ${externalApiKey}`)
68
+ .send({
69
+ agent_id: externalAgentId,
70
+ start_at: '2030-03-01T10:00:00Z',
71
+ end_at: '2030-03-01T10:30:00Z',
72
+ })
73
+ .expect(201);
74
+
75
+ await api
76
+ .post('/v1/holds')
77
+ .set('Authorization', `Bearer ${internalApiKey}`)
78
+ .send({
79
+ agent_id: internalAgentId,
80
+ start_at: '2030-03-01T10:00:00Z',
81
+ end_at: '2030-03-01T10:30:00Z',
82
+ })
83
+ .expect(409);
84
+
85
+ const response = await api
86
+ .get('/v1/metrics/overview')
87
+ .set('Cookie', getWorkspaceCookie(orgId))
88
+ .expect(200);
89
+
90
+ expect(response.body).toMatchObject({
91
+ active_agents: 2,
92
+ mcp_online: 0,
93
+ confirmed_holds: 1,
94
+ conflicts_resolved: 1,
95
+ conflicts_blocked: 1,
96
+ released_holds: 1,
97
+ });
98
+ expect(response.body.latest_event_at).toEqual(expect.any(String));
99
+ });
100
+ });