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,981 @@
1
+ import { randomUUID } from 'crypto';
2
+ import express from 'express';
3
+ import { z } from 'zod';
4
+ import pool from '../db/client';
5
+ import { log } from '../lib/logger';
6
+ import { PLAN_CONFIG, enforcePlanLimit } from '../lib/plans';
7
+ import { createAgentApiKey } from '../lib/apiKeys';
8
+ import { getAvailableSlots } from '../core/availability';
9
+ import { previewConflict } from '../core/conflict';
10
+ import {
11
+ initialWorkExpiresAt,
12
+ maxWorkExpiresAt,
13
+ previewWorkClaim,
14
+ upsertWorkResource,
15
+ workWindow,
16
+ } from '../core/work';
17
+ import {
18
+ requireWorkspaceSession,
19
+ type WorkspaceAuthenticatedRequest,
20
+ } from '../middleware/auth';
21
+ import { durationSince, logActivity, workspaceActor } from '../lib/activity';
22
+ import type { Hold, WorkClaim } from '../types';
23
+
24
+ const router = express.Router();
25
+
26
+ const OrgSchema = z.object({
27
+ name: z.string().min(1),
28
+ });
29
+
30
+ const AgentSchema = z.object({
31
+ name: z.string().min(1),
32
+ agent_type: z
33
+ .enum(['external_meeting', 'internal', 'focus', 'generic'])
34
+ .optional()
35
+ .default('generic'),
36
+ priority: z.number().int().optional().default(0),
37
+ });
38
+
39
+ const AgentUpdateSchema = z.object({
40
+ name: z.string().min(1),
41
+ agent_type: z.enum(['external_meeting', 'internal', 'focus', 'generic']),
42
+ priority: z.number().int(),
43
+ enforcement_mode: z.enum(['enforce', 'observe']).optional(),
44
+ });
45
+
46
+ const SetupTestSchema = z.object({
47
+ resource_key: z.string().min(1).max(300).optional().default('setup/demo-repo'),
48
+ });
49
+
50
+ const AgentSelect = `
51
+ id,
52
+ org_id,
53
+ name,
54
+ agent_type,
55
+ priority,
56
+ enforcement_mode,
57
+ last_seen_at,
58
+ mcp_last_seen_at,
59
+ mcp_client_name,
60
+ created_at,
61
+ (SELECT MAX(created_at) FROM agent_activity_events WHERE agent_id = agents.id AND status = 'success') AS last_used_at,
62
+ (SELECT MAX(created_at) FROM agent_activity_events WHERE agent_id = agents.id) AS last_activity_at,
63
+ (SELECT MAX(created_at) FROM agent_activity_events WHERE agent_id = agents.id AND status = 'error') AS last_error_at,
64
+ (SELECT error_code FROM agent_activity_events WHERE agent_id = agents.id AND status = 'error' ORDER BY created_at DESC LIMIT 1) AS last_error_code
65
+ `;
66
+
67
+ type SetupStepStatus = 'pending' | 'running' | 'success' | 'warning' | 'blocked' | 'error';
68
+
69
+ type SetupStep = {
70
+ name: string;
71
+ status: SetupStepStatus;
72
+ latency_ms: number;
73
+ summary: string;
74
+ next_action: string | null;
75
+ details?: Record<string, unknown>;
76
+ };
77
+
78
+ function mcpStatusFromLastSeen(lastSeen: Date | string | null | undefined) {
79
+ if (!lastSeen) return 'offline';
80
+ const date = new Date(lastSeen);
81
+ const diff = Date.now() - date.getTime();
82
+ if (diff < 2 * 60 * 1000) return 'online';
83
+ if (diff < 15 * 60 * 1000) return 'recent';
84
+ return 'offline';
85
+ }
86
+
87
+ async function insertSetupWorkEvent(input: {
88
+ claim_id?: string | null;
89
+ event_type: 'created' | 'blocked' | 'released' | 'extended';
90
+ agent_id: string;
91
+ org_id: string;
92
+ resource_id?: string | null;
93
+ conflicting_claim_id?: string | null;
94
+ rule_applied?: string | null;
95
+ metadata?: Record<string, unknown>;
96
+ actor_type?: string | null;
97
+ actor_id?: string | null;
98
+ actor_label?: string | null;
99
+ }) {
100
+ await pool.query(
101
+ `INSERT INTO work_claim_events (
102
+ claim_id,
103
+ event_type,
104
+ agent_id,
105
+ org_id,
106
+ resource_id,
107
+ conflicting_claim_id,
108
+ rule_applied,
109
+ metadata,
110
+ actor_type,
111
+ actor_id,
112
+ actor_label
113
+ )
114
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)`,
115
+ [
116
+ input.claim_id ?? null,
117
+ input.event_type,
118
+ input.agent_id,
119
+ input.org_id,
120
+ input.resource_id ?? null,
121
+ input.conflicting_claim_id ?? null,
122
+ input.rule_applied ?? null,
123
+ JSON.stringify(input.metadata ?? {}),
124
+ input.actor_type ?? null,
125
+ input.actor_id ?? null,
126
+ input.actor_label ?? null,
127
+ ],
128
+ );
129
+ }
130
+
131
+ async function logSetupActivity(input: {
132
+ org_id: string;
133
+ agent_id: string;
134
+ setup_type: string;
135
+ readiness_type?: string;
136
+ status?: 'success' | 'error';
137
+ status_code?: number;
138
+ latency_ms?: number;
139
+ error_code?: string | null;
140
+ error_message?: string | null;
141
+ metadata?: Record<string, unknown>;
142
+ actor: ReturnType<typeof workspaceActor>;
143
+ }) {
144
+ const metadata = { source: 'setup_test', ...(input.metadata ?? {}) };
145
+ await logActivity({
146
+ org_id: input.org_id,
147
+ agent_id: input.agent_id,
148
+ activity_type: input.setup_type,
149
+ endpoint: '/v1/orgs/:org_id/agents/:agent_id/setup-test',
150
+ method: 'POST',
151
+ status_code: input.status_code ?? (input.status === 'error' ? 500 : 200),
152
+ status: input.status ?? 'success',
153
+ ...input.actor,
154
+ latency_ms: input.latency_ms,
155
+ error_code: input.error_code ?? null,
156
+ error_message: input.error_message ?? null,
157
+ client_name: 'dashboard-setup-test',
158
+ metadata,
159
+ });
160
+
161
+ if (input.readiness_type) {
162
+ await logActivity({
163
+ org_id: input.org_id,
164
+ agent_id: input.agent_id,
165
+ activity_type: input.readiness_type,
166
+ endpoint: '/v1/orgs/:org_id/agents/:agent_id/setup-test',
167
+ method: 'POST',
168
+ status_code: input.status_code ?? (input.status === 'error' ? 500 : 200),
169
+ status: input.status ?? 'success',
170
+ ...input.actor,
171
+ latency_ms: input.latency_ms,
172
+ error_code: input.error_code ?? null,
173
+ error_message: input.error_message ?? null,
174
+ client_name: 'dashboard-setup-test',
175
+ metadata,
176
+ });
177
+ }
178
+ }
179
+
180
+ router.post('/orgs', async (req, res) => {
181
+ const result = OrgSchema.safeParse(req.body);
182
+ if (!result.success) {
183
+ return res.status(422).json({ error: 'validation_error', details: result.error.flatten() });
184
+ }
185
+
186
+ try {
187
+ const created = await pool.query(
188
+ "INSERT INTO orgs (name, plan, agent_limit, subscription_status) VALUES ($1, 'free', $2, 'free') RETURNING *",
189
+ [result.data.name, PLAN_CONFIG.free.db_agent_limit],
190
+ );
191
+ return res.status(201).json({ org: created.rows[0] });
192
+ } catch (err) {
193
+ log.error('[orgs] unexpected error', { error: (err as Error).message });
194
+ return res.status(500).json({ error: 'internal_error', message: 'An unexpected error occurred' });
195
+ }
196
+ });
197
+
198
+ router.post('/orgs/:org_id/agents', requireWorkspaceSession, async (req, res) => {
199
+ const result = AgentSchema.safeParse(req.body);
200
+ if (!result.success) {
201
+ return res.status(422).json({ error: 'validation_error', details: result.error.flatten() });
202
+ }
203
+
204
+ const authReq = req as WorkspaceAuthenticatedRequest;
205
+ if (authReq.org.id !== req.params.org_id) {
206
+ return res.status(403).json({ error: 'forbidden', message: 'Organization is outside your workspace' });
207
+ }
208
+
209
+ try {
210
+ const org = await pool.query('SELECT * FROM orgs WHERE id = $1', [req.params.org_id]);
211
+ if (org.rows.length === 0) {
212
+ return res.status(404).json({ error: 'org_not_found', message: 'Organization not found' });
213
+ }
214
+
215
+ if (!(await enforcePlanLimit({
216
+ org_id: req.params.org_id,
217
+ limit_type: 'agents',
218
+ res,
219
+ }))) {
220
+ return;
221
+ }
222
+
223
+ const { apiKey, apiKeyHash, apiKeyPrefix } = await createAgentApiKey();
224
+
225
+ const client = await pool.connect();
226
+ try {
227
+ await client.query('BEGIN');
228
+ const created = await client.query(
229
+ `INSERT INTO agents (org_id, name, api_key_hash, api_key_prefix, agent_type, priority)
230
+ VALUES ($1, $2, $3, $4, $5, $6)
231
+ RETURNING *`,
232
+ [
233
+ req.params.org_id,
234
+ result.data.name,
235
+ apiKeyHash,
236
+ apiKeyPrefix,
237
+ result.data.agent_type,
238
+ result.data.priority,
239
+ ],
240
+ );
241
+ await client.query('INSERT INTO agent_preferences (agent_id) VALUES ($1)', [
242
+ created.rows[0].id,
243
+ ]);
244
+ await client.query('COMMIT');
245
+
246
+ const { api_key_hash: _apiKeyHash, api_key_prefix: _apiKeyPrefix, ...agent } = created.rows[0];
247
+ return res.status(201).json({ agent, api_key: apiKey });
248
+ } catch (err) {
249
+ await client.query('ROLLBACK');
250
+ throw err;
251
+ } finally {
252
+ client.release();
253
+ }
254
+ } catch (err) {
255
+ log.error('[orgs] unexpected error', { error: (err as Error).message });
256
+ return res.status(500).json({ error: 'internal_error', message: 'An unexpected error occurred' });
257
+ }
258
+ });
259
+
260
+ router.get('/orgs/:org_id/agents', requireWorkspaceSession, async (req, res) => {
261
+ const authReq = req as WorkspaceAuthenticatedRequest;
262
+ if (authReq.org.id !== req.params.org_id) {
263
+ return res.status(403).json({ error: 'forbidden', message: 'Organization is outside your workspace' });
264
+ }
265
+
266
+ try {
267
+ const agents = await pool.query(
268
+ `SELECT ${AgentSelect}
269
+ FROM agents
270
+ WHERE org_id = $1
271
+ ORDER BY created_at ASC`,
272
+ [req.params.org_id],
273
+ );
274
+ return res.json({ agents: agents.rows });
275
+ } catch (err) {
276
+ log.error('[orgs] unexpected error', { error: (err as Error).message });
277
+ return res.status(500).json({ error: 'internal_error', message: 'An unexpected error occurred' });
278
+ }
279
+ });
280
+
281
+ router.put('/orgs/:org_id/agents/:agent_id', requireWorkspaceSession, async (req, res) => {
282
+ const authReq = req as WorkspaceAuthenticatedRequest;
283
+ if (authReq.org.id !== req.params.org_id) {
284
+ return res.status(403).json({ error: 'forbidden', message: 'Organization is outside your workspace' });
285
+ }
286
+
287
+ const agentId = z.string().uuid().safeParse(req.params.agent_id);
288
+ if (!agentId.success) {
289
+ return res.status(400).json({ error: 'validation_error', details: agentId.error.flatten() });
290
+ }
291
+
292
+ const parsed = AgentUpdateSchema.safeParse(req.body);
293
+ if (!parsed.success) {
294
+ return res.status(422).json({ error: 'validation_error', details: parsed.error.flatten() });
295
+ }
296
+
297
+ try {
298
+ const updated = await pool.query(
299
+ `UPDATE agents
300
+ SET name = $1,
301
+ agent_type = $2,
302
+ priority = $3,
303
+ enforcement_mode = COALESCE($4, enforcement_mode)
304
+ WHERE id = $5
305
+ AND org_id = $6
306
+ RETURNING id, org_id, name, agent_type, priority, enforcement_mode, last_seen_at, mcp_last_seen_at, mcp_client_name, created_at`,
307
+ [
308
+ parsed.data.name,
309
+ parsed.data.agent_type,
310
+ parsed.data.priority,
311
+ parsed.data.enforcement_mode,
312
+ agentId.data,
313
+ authReq.org.id,
314
+ ],
315
+ );
316
+
317
+ if (updated.rows.length === 0) {
318
+ return res.status(404).json({ error: 'agent_not_found', message: 'Agent not found' });
319
+ }
320
+
321
+ return res.json({ agent: updated.rows[0] });
322
+ } catch (err) {
323
+ log.error('[orgs] unexpected error', { error: (err as Error).message });
324
+ return res.status(500).json({ error: 'internal_error', message: 'An unexpected error occurred' });
325
+ }
326
+ });
327
+
328
+ router.delete('/orgs/:org_id/agents/:agent_id', requireWorkspaceSession, async (req, res) => {
329
+ const authReq = req as WorkspaceAuthenticatedRequest;
330
+ if (authReq.org.id !== req.params.org_id) {
331
+ return res.status(403).json({ error: 'forbidden', message: 'Organization is outside your workspace' });
332
+ }
333
+
334
+ const agentId = z.string().uuid().safeParse(req.params.agent_id);
335
+ if (!agentId.success) {
336
+ return res.status(400).json({ error: 'validation_error', details: agentId.error.flatten() });
337
+ }
338
+
339
+ try {
340
+ const count = await pool.query('SELECT COUNT(*)::int AS count FROM agents WHERE org_id = $1', [
341
+ authReq.org.id,
342
+ ]);
343
+ if (count.rows[0].count <= 1) {
344
+ return res.status(400).json({ error: 'last_agent', message: 'Create another agent before deleting this one' });
345
+ }
346
+
347
+ const deleted = await pool.query(
348
+ `DELETE FROM agents
349
+ WHERE id = $1
350
+ AND org_id = $2
351
+ RETURNING id, org_id, name, agent_type, priority, enforcement_mode, last_seen_at, mcp_last_seen_at, mcp_client_name, created_at`,
352
+ [agentId.data, authReq.org.id],
353
+ );
354
+
355
+ if (deleted.rows.length === 0) {
356
+ return res.status(404).json({ error: 'agent_not_found', message: 'Agent not found' });
357
+ }
358
+
359
+ return res.json({ agent: deleted.rows[0] });
360
+ } catch (err) {
361
+ log.error('[orgs] unexpected error', { error: (err as Error).message });
362
+ return res.status(500).json({ error: 'internal_error', message: 'An unexpected error occurred' });
363
+ }
364
+ });
365
+
366
+ router.post('/orgs/:org_id/agents/:agent_id/rotate-key', requireWorkspaceSession, async (req, res) => {
367
+ const authReq = req as WorkspaceAuthenticatedRequest;
368
+ if (authReq.org.id !== req.params.org_id) {
369
+ return res.status(403).json({ error: 'forbidden', message: 'Organization is outside your workspace' });
370
+ }
371
+
372
+ const agentId = z.string().uuid().safeParse(req.params.agent_id);
373
+ if (!agentId.success) {
374
+ return res.status(400).json({ error: 'validation_error', details: agentId.error.flatten() });
375
+ }
376
+
377
+ try {
378
+ const { apiKey, apiKeyHash, apiKeyPrefix } = await createAgentApiKey();
379
+
380
+ const updated = await pool.query(
381
+ `UPDATE agents
382
+ SET api_key_hash = $1,
383
+ api_key_prefix = $2
384
+ WHERE id = $3
385
+ AND org_id = $4
386
+ RETURNING id, org_id, name, agent_type, priority, enforcement_mode, last_seen_at, mcp_last_seen_at, mcp_client_name, created_at`,
387
+ [apiKeyHash, apiKeyPrefix, agentId.data, authReq.org.id],
388
+ );
389
+
390
+ if (updated.rows.length === 0) {
391
+ return res.status(404).json({ error: 'agent_not_found', message: 'Agent not found' });
392
+ }
393
+
394
+ await logActivity({
395
+ org_id: authReq.org.id,
396
+ agent_id: agentId.data,
397
+ activity_type: 'key_rotated',
398
+ endpoint: `/v1/orgs/${req.params.org_id}/agents/${agentId.data}/rotate-key`,
399
+ method: 'POST',
400
+ status_code: 200,
401
+ status: 'success',
402
+ ...workspaceActor(authReq),
403
+ });
404
+
405
+ return res.json({ agent: updated.rows[0], api_key: apiKey });
406
+ } catch (err) {
407
+ log.error('[orgs] rotate key error', { error: (err as Error).message });
408
+ return res.status(500).json({ error: 'internal_error', message: 'An unexpected error occurred' });
409
+ }
410
+ });
411
+
412
+ router.post('/orgs/:org_id/agents/:agent_id/setup-test', requireWorkspaceSession, async (req, res) => {
413
+ const parsed = SetupTestSchema.safeParse(req.body ?? {});
414
+ if (!parsed.success) {
415
+ return res.status(422).json({ error: 'validation_error', details: parsed.error.flatten() });
416
+ }
417
+
418
+ const authReq = req as WorkspaceAuthenticatedRequest;
419
+ if (authReq.org.id !== req.params.org_id) {
420
+ return res.status(403).json({ error: 'forbidden', message: 'Organization is outside your workspace' });
421
+ }
422
+
423
+ const agentId = z.string().uuid().safeParse(req.params.agent_id);
424
+ if (!agentId.success) {
425
+ return res.status(400).json({ error: 'validation_error', details: agentId.error.flatten() });
426
+ }
427
+
428
+ const runId = randomUUID();
429
+ const startedAt = new Date();
430
+ const steps: SetupStep[] = [];
431
+ const actor = workspaceActor(authReq);
432
+ let createdClaimId: string | null = null;
433
+ let createdResourceId: string | null = null;
434
+ let finalStatus: 'success' | 'warning' | 'blocked' | 'error' = 'success';
435
+
436
+ const addStep = (step: SetupStep) => {
437
+ steps.push(step);
438
+ if (step.status === 'error') finalStatus = 'error';
439
+ if (step.status === 'blocked' && finalStatus !== 'error') finalStatus = 'blocked';
440
+ if (step.status === 'warning' && finalStatus === 'success') finalStatus = 'warning';
441
+ };
442
+
443
+ try {
444
+ const agent = await pool.query(
445
+ `SELECT ${AgentSelect}
446
+ FROM agents
447
+ WHERE id = $1
448
+ AND org_id = $2`,
449
+ [agentId.data, authReq.org.id],
450
+ );
451
+
452
+ if (agent.rows.length === 0) {
453
+ return res.status(404).json({ error: 'agent_not_found', message: 'Agent not found' });
454
+ }
455
+
456
+ const mcpStarted = Date.now();
457
+ const mcpStatus = mcpStatusFromLastSeen(agent.rows[0].mcp_last_seen_at);
458
+ addStep({
459
+ name: 'mcp_status_observed',
460
+ status: mcpStatus === 'online' ? 'success' : 'warning',
461
+ latency_ms: durationSince(mcpStarted),
462
+ summary: `MCP status is ${mcpStatus}.`,
463
+ next_action: mcpStatus === 'online' ? null : 'Start the MCP server to send a real heartbeat.',
464
+ details: {
465
+ mcp_status: mcpStatus,
466
+ mcp_last_seen_at: agent.rows[0].mcp_last_seen_at ?? null,
467
+ mcp_client_name: agent.rows[0].mcp_client_name ?? null,
468
+ },
469
+ });
470
+
471
+ const from = new Date();
472
+ const to = new Date(from.getTime() + 8 * 60 * 60 * 1000);
473
+ const availabilityStarted = Date.now();
474
+ const slots = await getAvailableSlots({
475
+ agent_id: agentId.data,
476
+ org_id: authReq.org.id,
477
+ from,
478
+ to,
479
+ duration_minutes: 30,
480
+ include_competing_agents: true,
481
+ });
482
+ await logSetupActivity({
483
+ org_id: authReq.org.id,
484
+ agent_id: agentId.data,
485
+ setup_type: 'setup_test_availability',
486
+ readiness_type: 'availability_check',
487
+ latency_ms: durationSince(availabilityStarted),
488
+ metadata: { run_id: runId, slot_count: slots.length },
489
+ actor,
490
+ });
491
+ addStep({
492
+ name: 'availability_check',
493
+ status: slots.length > 0 ? 'success' : 'warning',
494
+ latency_ms: durationSince(availabilityStarted),
495
+ summary: slots.length > 0 ? `${slots.length} available slots found.` : 'No availability windows produced open slots.',
496
+ next_action: slots.length > 0 ? null : 'Create an availability window for this agent.',
497
+ details: {
498
+ slot_count: slots.length,
499
+ first_slot: slots[0]
500
+ ? { start: slots[0].start.toISOString(), end: slots[0].end.toISOString() }
501
+ : null,
502
+ },
503
+ });
504
+
505
+ const previewStart = slots[0]?.start ?? new Date(Date.now() + 30 * 60 * 1000);
506
+ const previewEnd = slots[0]?.end ?? new Date(previewStart.getTime() + 30 * 60 * 1000);
507
+ const conflictStarted = Date.now();
508
+ const conflict = await previewConflict(
509
+ {
510
+ agent_id: agentId.data,
511
+ org_id: authReq.org.id,
512
+ start_at: previewStart,
513
+ end_at: previewEnd,
514
+ reason: 'Availsync setup test',
515
+ status: 'confirmed',
516
+ metadata: { source: 'setup_test', run_id: runId },
517
+ } as Omit<Hold, 'id' | 'created_at'>,
518
+ pool as never,
519
+ );
520
+ await logSetupActivity({
521
+ org_id: authReq.org.id,
522
+ agent_id: agentId.data,
523
+ setup_type: 'setup_test_conflict_preview',
524
+ readiness_type: 'conflict_preview',
525
+ latency_ms: durationSince(conflictStarted),
526
+ metadata: { run_id: runId, status: conflict.status, rule_applied: conflict.rule_applied },
527
+ actor,
528
+ });
529
+ addStep({
530
+ name: 'conflict_preview',
531
+ status: conflict.status === 'would_lose' ? 'warning' : 'success',
532
+ latency_ms: durationSince(conflictStarted),
533
+ summary: `Conflict preview returned ${conflict.status}.`,
534
+ next_action: conflict.status === 'would_lose' ? 'Review existing holds or agent priority rules.' : null,
535
+ details: {
536
+ status: conflict.status,
537
+ rule_applied: conflict.rule_applied,
538
+ winning_hold_id: conflict.winning_hold_id,
539
+ losing_hold_ids: conflict.losing_hold_ids,
540
+ },
541
+ });
542
+
543
+ const workCheckStarted = Date.now();
544
+ const claimWindow = workWindow({ duration_minutes: 45 });
545
+ const client = await pool.connect();
546
+ try {
547
+ await client.query('BEGIN');
548
+ await client.query('SELECT pg_advisory_xact_lock(hashtext($1))', [
549
+ `${authReq.org.id}:repo:${parsed.data.resource_key}`,
550
+ ]);
551
+
552
+ const resource = await upsertWorkResource(client, {
553
+ org_id: authReq.org.id,
554
+ resource_type: 'repo',
555
+ resource_key: parsed.data.resource_key,
556
+ label: parsed.data.resource_key,
557
+ });
558
+ createdResourceId = resource.id;
559
+
560
+ const workCheck = await previewWorkClaim(client, {
561
+ org_id: authReq.org.id,
562
+ agent_id: agentId.data,
563
+ resource_id: resource.id,
564
+ start_at: claimWindow.startAt,
565
+ end_at: claimWindow.endAt,
566
+ reason: 'Availsync setup test',
567
+ metadata: { source: 'setup_test', run_id: runId },
568
+ });
569
+ await logSetupActivity({
570
+ org_id: authReq.org.id,
571
+ agent_id: agentId.data,
572
+ setup_type: 'setup_test_work_check',
573
+ readiness_type: 'work_check',
574
+ latency_ms: durationSince(workCheckStarted),
575
+ metadata: {
576
+ run_id: runId,
577
+ resource_key: parsed.data.resource_key,
578
+ status: workCheck.status,
579
+ rule_applied: workCheck.rule_applied,
580
+ },
581
+ actor,
582
+ });
583
+ addStep({
584
+ name: 'work_check',
585
+ status: workCheck.status === 'available' ? 'success' : 'blocked',
586
+ latency_ms: durationSince(workCheckStarted),
587
+ summary: `Work check returned ${workCheck.status} for ${parsed.data.resource_key}.`,
588
+ next_action: workCheck.status === 'available' ? null : 'Use a different setup resource or release the existing active claim.',
589
+ details: {
590
+ resource_key: parsed.data.resource_key,
591
+ status: workCheck.status,
592
+ winning_claim_id: workCheck.winning_claim_id,
593
+ losing_claim_ids: workCheck.losing_claim_ids,
594
+ rule_applied: workCheck.rule_applied,
595
+ },
596
+ });
597
+
598
+ if (workCheck.status !== 'available') {
599
+ await insertSetupWorkEvent({
600
+ event_type: 'blocked',
601
+ agent_id: agentId.data,
602
+ org_id: authReq.org.id,
603
+ resource_id: resource.id,
604
+ conflicting_claim_id: workCheck.winning_claim_id,
605
+ rule_applied: workCheck.rule_applied,
606
+ metadata: { source: 'setup_test', run_id: runId, resource_key: parsed.data.resource_key },
607
+ ...actor,
608
+ });
609
+ await logSetupActivity({
610
+ org_id: authReq.org.id,
611
+ agent_id: agentId.data,
612
+ setup_type: 'setup_test_work_blocked',
613
+ status: 'error',
614
+ status_code: 409,
615
+ error_code: 'work_claim_blocked',
616
+ error_message: 'Setup test work claim was blocked by an active claim',
617
+ metadata: { run_id: runId, resource_key: parsed.data.resource_key, winning_claim_id: workCheck.winning_claim_id },
618
+ actor,
619
+ });
620
+ await client.query('COMMIT');
621
+ const finishedAt = new Date();
622
+ await logSetupActivity({
623
+ org_id: authReq.org.id,
624
+ agent_id: agentId.data,
625
+ setup_type: 'setup_test',
626
+ status: 'error',
627
+ status_code: 409,
628
+ error_code: 'work_claim_blocked',
629
+ error_message: 'Setup test blocked by active work claim',
630
+ latency_ms: finishedAt.getTime() - startedAt.getTime(),
631
+ metadata: { run_id: runId, final_status: 'blocked' },
632
+ actor,
633
+ });
634
+ return res.status(409).json({
635
+ run_id: runId,
636
+ status: 'blocked',
637
+ steps,
638
+ started_at: startedAt.toISOString(),
639
+ finished_at: finishedAt.toISOString(),
640
+ });
641
+ }
642
+
643
+ const workClaimStarted = Date.now();
644
+ const created = await client.query<WorkClaim>(
645
+ `INSERT INTO work_claims (
646
+ org_id,
647
+ agent_id,
648
+ resource_id,
649
+ start_at,
650
+ end_at,
651
+ status,
652
+ reason,
653
+ metadata,
654
+ expires_at,
655
+ last_renewed_at,
656
+ renewal_count
657
+ )
658
+ VALUES ($1, $2, $3, $4, $5, 'active', $6, $7, $8, NOW(), 0)
659
+ RETURNING *`,
660
+ [
661
+ authReq.org.id,
662
+ agentId.data,
663
+ resource.id,
664
+ claimWindow.startAt,
665
+ claimWindow.endAt,
666
+ 'Availsync setup test',
667
+ JSON.stringify({ source: 'setup_test', run_id: runId }),
668
+ initialWorkExpiresAt(claimWindow.startAt, claimWindow.endAt),
669
+ ],
670
+ );
671
+ createdClaimId = created.rows[0].id;
672
+ await insertSetupWorkEvent({
673
+ claim_id: createdClaimId,
674
+ event_type: 'created',
675
+ agent_id: agentId.data,
676
+ org_id: authReq.org.id,
677
+ resource_id: resource.id,
678
+ rule_applied: workCheck.rule_applied,
679
+ metadata: { source: 'setup_test', run_id: runId, resource_key: parsed.data.resource_key },
680
+ ...actor,
681
+ });
682
+ await logSetupActivity({
683
+ org_id: authReq.org.id,
684
+ agent_id: agentId.data,
685
+ setup_type: 'setup_test_work_claim',
686
+ readiness_type: 'work_claim',
687
+ latency_ms: durationSince(workClaimStarted),
688
+ metadata: { source: 'setup_test', run_id: runId, claim_id: createdClaimId, resource_key: parsed.data.resource_key },
689
+ actor,
690
+ });
691
+ addStep({
692
+ name: 'work_claim',
693
+ status: 'success',
694
+ latency_ms: durationSince(workClaimStarted),
695
+ summary: 'Temporary setup claim created.',
696
+ next_action: null,
697
+ details: { claim_id: createdClaimId, resource_key: parsed.data.resource_key },
698
+ });
699
+
700
+ const extendStarted = Date.now();
701
+ const maxExpires = maxWorkExpiresAt(created.rows[0].start_at);
702
+ const requestedExpires = new Date(Date.now() + 15 * 60_000);
703
+ const nextExpires = requestedExpires < maxExpires ? requestedExpires : maxExpires;
704
+ const extended = await client.query<WorkClaim>(
705
+ `UPDATE work_claims
706
+ SET expires_at = $1,
707
+ last_renewed_at = NOW(),
708
+ renewal_count = renewal_count + 1
709
+ WHERE id = $2
710
+ RETURNING *`,
711
+ [nextExpires, createdClaimId],
712
+ );
713
+ await insertSetupWorkEvent({
714
+ claim_id: createdClaimId,
715
+ event_type: 'extended',
716
+ agent_id: agentId.data,
717
+ org_id: authReq.org.id,
718
+ resource_id: resource.id,
719
+ metadata: {
720
+ source: 'setup_test',
721
+ run_id: runId,
722
+ expires_at: extended.rows[0].expires_at.toISOString(),
723
+ renewal_count: extended.rows[0].renewal_count,
724
+ },
725
+ ...actor,
726
+ });
727
+ await logSetupActivity({
728
+ org_id: authReq.org.id,
729
+ agent_id: agentId.data,
730
+ setup_type: 'setup_test_work_extend',
731
+ readiness_type: 'work_extend',
732
+ latency_ms: durationSince(extendStarted),
733
+ metadata: { run_id: runId, claim_id: createdClaimId, renewal_count: extended.rows[0].renewal_count },
734
+ actor,
735
+ });
736
+ addStep({
737
+ name: 'work_extend',
738
+ status: 'success',
739
+ latency_ms: durationSince(extendStarted),
740
+ summary: 'Temporary setup claim extended.',
741
+ next_action: null,
742
+ details: { claim_id: createdClaimId, renewal_count: extended.rows[0].renewal_count },
743
+ });
744
+
745
+ const releaseStarted = Date.now();
746
+ const released = await client.query<WorkClaim>(
747
+ `UPDATE work_claims
748
+ SET status = 'released'
749
+ WHERE id = $1
750
+ AND status = 'active'
751
+ RETURNING *`,
752
+ [createdClaimId],
753
+ );
754
+ await insertSetupWorkEvent({
755
+ claim_id: createdClaimId,
756
+ event_type: 'released',
757
+ agent_id: agentId.data,
758
+ org_id: authReq.org.id,
759
+ resource_id: resource.id,
760
+ metadata: { source: 'setup_test', run_id: runId, cleanup: true },
761
+ ...actor,
762
+ });
763
+ await logSetupActivity({
764
+ org_id: authReq.org.id,
765
+ agent_id: agentId.data,
766
+ setup_type: 'setup_test_work_release',
767
+ readiness_type: 'work_release',
768
+ latency_ms: durationSince(releaseStarted),
769
+ metadata: { run_id: runId, claim_id: createdClaimId, released: released.rows.length > 0 },
770
+ actor,
771
+ });
772
+ addStep({
773
+ name: 'work_release',
774
+ status: 'success',
775
+ latency_ms: durationSince(releaseStarted),
776
+ summary: 'Temporary setup claim released.',
777
+ next_action: null,
778
+ details: { claim_id: createdClaimId },
779
+ });
780
+
781
+ await client.query('COMMIT');
782
+ } catch (err) {
783
+ await client.query('ROLLBACK').catch(() => {});
784
+ throw err;
785
+ } finally {
786
+ client.release();
787
+ }
788
+
789
+ const finishedAt = new Date();
790
+ await logSetupActivity({
791
+ org_id: authReq.org.id,
792
+ agent_id: agentId.data,
793
+ setup_type: 'setup_test',
794
+ latency_ms: finishedAt.getTime() - startedAt.getTime(),
795
+ metadata: { run_id: runId, final_status: finalStatus, resource_id: createdResourceId },
796
+ actor,
797
+ });
798
+
799
+ return res.json({
800
+ run_id: runId,
801
+ status: finalStatus,
802
+ steps,
803
+ started_at: startedAt.toISOString(),
804
+ finished_at: finishedAt.toISOString(),
805
+ });
806
+ } catch (err) {
807
+ if (createdClaimId) {
808
+ await pool.query(
809
+ `UPDATE work_claims
810
+ SET status = 'released'
811
+ WHERE id = $1
812
+ AND org_id = $2
813
+ AND status = 'active'`,
814
+ [createdClaimId, authReq.org.id],
815
+ ).catch(() => {});
816
+ addStep({
817
+ name: 'work_release',
818
+ status: 'warning',
819
+ latency_ms: 0,
820
+ summary: 'Cleanup attempted after setup test failure.',
821
+ next_action: 'Check Work Coordination for any active setup/demo-repo claim.',
822
+ details: { claim_id: createdClaimId },
823
+ });
824
+ }
825
+
826
+ const message = (err as Error).message;
827
+ log.error('[orgs] setup test error', { error: message, run_id: runId });
828
+ const finishedAt = new Date();
829
+ await logSetupActivity({
830
+ org_id: authReq.org.id,
831
+ agent_id: agentId.success ? agentId.data : req.params.agent_id,
832
+ setup_type: 'setup_test',
833
+ status: 'error',
834
+ status_code: 500,
835
+ error_code: 'setup_test_failed',
836
+ error_message: message,
837
+ latency_ms: finishedAt.getTime() - startedAt.getTime(),
838
+ metadata: { run_id: runId },
839
+ actor,
840
+ }).catch(() => {});
841
+ return res.status(500).json({
842
+ run_id: runId,
843
+ status: 'error',
844
+ steps,
845
+ started_at: startedAt.toISOString(),
846
+ finished_at: finishedAt.toISOString(),
847
+ error: 'setup_test_failed',
848
+ message: 'Setup test failed before completion',
849
+ });
850
+ }
851
+ });
852
+
853
+ router.post('/orgs/:org_id/agents/:agent_id/test-connection', requireWorkspaceSession, async (req, res) => {
854
+ const authReq = req as WorkspaceAuthenticatedRequest;
855
+ if (authReq.org.id !== req.params.org_id) {
856
+ return res.status(403).json({ error: 'forbidden', message: 'Organization is outside your workspace' });
857
+ }
858
+
859
+ const agentId = z.string().uuid().safeParse(req.params.agent_id);
860
+ if (!agentId.success) {
861
+ return res.status(400).json({ error: 'validation_error', details: agentId.error.flatten() });
862
+ }
863
+
864
+ try {
865
+ const agent = await pool.query(
866
+ `SELECT ${AgentSelect}
867
+ FROM agents
868
+ WHERE id = $1
869
+ AND org_id = $2`,
870
+ [agentId.data, authReq.org.id],
871
+ );
872
+
873
+ if (agent.rows.length === 0) {
874
+ return res.status(404).json({ error: 'agent_not_found', message: 'Agent not found' });
875
+ }
876
+
877
+ const lastSuccess = await pool.query(
878
+ `SELECT activity_type, endpoint, method, status_code, client_name, created_at
879
+ FROM agent_activity_events
880
+ WHERE agent_id = $1
881
+ AND status = 'success'
882
+ ORDER BY created_at DESC
883
+ LIMIT 1`,
884
+ [agentId.data],
885
+ );
886
+ const lastError = await pool.query(
887
+ `SELECT activity_type, endpoint, method, status_code, error_code, error_message, created_at
888
+ FROM agent_activity_events
889
+ WHERE agent_id = $1
890
+ AND status = 'error'
891
+ ORDER BY created_at DESC
892
+ LIMIT 1`,
893
+ [agentId.data],
894
+ );
895
+ const readiness = await pool.query(
896
+ `SELECT
897
+ EXISTS(SELECT 1 FROM availability_windows WHERE agent_id = $1) AS availability_window_created,
898
+ EXISTS(SELECT 1 FROM agent_activity_events WHERE agent_id = $1 AND activity_type = 'mcp_heartbeat') AS mcp_heartbeat_seen,
899
+ EXISTS(SELECT 1 FROM agent_activity_events WHERE agent_id = $1 AND activity_type = 'availability_check') AS first_availability_check,
900
+ EXISTS(SELECT 1 FROM agent_activity_events WHERE agent_id = $1 AND activity_type = 'conflict_preview') AS first_conflict_preview,
901
+ EXISTS(SELECT 1 FROM holds WHERE agent_id = $1) AS first_hold_created,
902
+ EXISTS(SELECT 1 FROM agent_activity_events WHERE agent_id = $1 AND activity_type = 'work_check') AS first_work_check,
903
+ EXISTS(SELECT 1 FROM agent_activity_events WHERE agent_id = $1 AND activity_type = 'work_claim') AS first_work_claim,
904
+ EXISTS(SELECT 1 FROM agent_activity_events WHERE agent_id = $1 AND activity_type = 'work_extend') AS first_work_extend,
905
+ EXISTS(SELECT 1 FROM agent_activity_events WHERE agent_id = $1 AND activity_type = 'work_release') AS first_work_release,
906
+ (SELECT created_at FROM agent_activity_events WHERE agent_id = $1 ORDER BY created_at DESC LIMIT 1) AS latest_activity_at,
907
+ (SELECT activity_type FROM agent_activity_events WHERE agent_id = $1 ORDER BY created_at DESC LIMIT 1) AS latest_activity_type`,
908
+ [agentId.data],
909
+ );
910
+ const readinessRow = readiness.rows[0];
911
+
912
+ const mcpLastSeen = agent.rows[0].mcp_last_seen_at ? new Date(agent.rows[0].mcp_last_seen_at) : null;
913
+ const mcpStatus =
914
+ mcpLastSeen && Date.now() - mcpLastSeen.getTime() < 2 * 60 * 1000
915
+ ? 'online'
916
+ : mcpLastSeen && Date.now() - mcpLastSeen.getTime() < 15 * 60 * 1000
917
+ ? 'recent'
918
+ : 'offline';
919
+
920
+ await logActivity({
921
+ org_id: authReq.org.id,
922
+ agent_id: agentId.data,
923
+ activity_type: 'connection_test',
924
+ endpoint: `/v1/orgs/${req.params.org_id}/agents/${agentId.data}/test-connection`,
925
+ method: 'POST',
926
+ status_code: 200,
927
+ status: 'success',
928
+ ...workspaceActor(authReq),
929
+ });
930
+
931
+ return res.json({
932
+ agent_id: agentId.data,
933
+ last_seen_at: agent.rows[0].last_seen_at ?? null,
934
+ mcp_status: mcpStatus,
935
+ mcp_last_seen_at: agent.rows[0].mcp_last_seen_at,
936
+ mcp_client_name: agent.rows[0].mcp_client_name,
937
+ last_successful_api_call: lastSuccess.rows[0] ?? null,
938
+ last_error: lastError.rows[0] ?? null,
939
+ setup_readiness: {
940
+ agent_created: true,
941
+ availability_window_created: readinessRow.availability_window_created,
942
+ mcp_heartbeat_seen: readinessRow.mcp_heartbeat_seen,
943
+ first_availability_check: readinessRow.first_availability_check,
944
+ first_conflict_preview: readinessRow.first_conflict_preview,
945
+ first_hold_created: readinessRow.first_hold_created,
946
+ first_work_check: readinessRow.first_work_check,
947
+ first_work_claim: readinessRow.first_work_claim,
948
+ first_work_extend: readinessRow.first_work_extend,
949
+ first_work_release: readinessRow.first_work_release,
950
+ latest_activity_at: readinessRow.latest_activity_at,
951
+ latest_activity_type: readinessRow.latest_activity_type,
952
+ mcp: {
953
+ heartbeat_seen: readinessRow.mcp_heartbeat_seen,
954
+ status: mcpStatus,
955
+ last_seen_at: agent.rows[0].mcp_last_seen_at,
956
+ },
957
+ calendar: {
958
+ availability_window_created: readinessRow.availability_window_created,
959
+ first_availability_check: readinessRow.first_availability_check,
960
+ first_conflict_preview: readinessRow.first_conflict_preview,
961
+ first_hold_created: readinessRow.first_hold_created,
962
+ },
963
+ work: {
964
+ first_work_check: readinessRow.first_work_check,
965
+ first_work_claim: readinessRow.first_work_claim,
966
+ first_work_extend: readinessRow.first_work_extend,
967
+ first_work_release: readinessRow.first_work_release,
968
+ },
969
+ activity: {
970
+ latest_activity_at: readinessRow.latest_activity_at,
971
+ latest_activity_type: readinessRow.latest_activity_type,
972
+ },
973
+ },
974
+ });
975
+ } catch (err) {
976
+ log.error('[orgs] test connection error', { error: (err as Error).message });
977
+ return res.status(500).json({ error: 'internal_error', message: 'An unexpected error occurred' });
978
+ }
979
+ });
980
+
981
+ export default router;