@wopr-network/platform-ui-core 1.27.7 → 1.27.9

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 (353) hide show
  1. package/next.config.ts +1 -2
  2. package/package.json +17 -17
  3. package/src/__tests__/account-switcher.test.tsx +21 -20
  4. package/src/__tests__/activity-page.test.tsx +2 -6
  5. package/src/__tests__/add-payment-method-dialog.test.tsx +9 -32
  6. package/src/__tests__/admin-api.test.ts +1 -6
  7. package/src/__tests__/admin-gpu-api.test.ts +1 -3
  8. package/src/__tests__/admin-marketplace-api.test.ts +1 -4
  9. package/src/__tests__/admin-middleware.test.ts +76 -83
  10. package/src/__tests__/affiliate-dashboard.test.tsx +3 -3
  11. package/src/__tests__/api-401-redirect.test.ts +46 -9
  12. package/src/__tests__/api-client.test.ts +3 -5
  13. package/src/__tests__/api-config.test.ts +22 -42
  14. package/src/__tests__/api-fleet-resources.test.ts +1 -2
  15. package/src/__tests__/api-fleet-trpc.test.ts +2 -8
  16. package/src/__tests__/api-null-guards.test.ts +3 -1
  17. package/src/__tests__/audit-log-table-pagination.test.tsx +2 -6
  18. package/src/__tests__/auth-password-reset.test.tsx +7 -21
  19. package/src/__tests__/auth-redirect.test.tsx +8 -2
  20. package/src/__tests__/auth.test.tsx +25 -23
  21. package/src/__tests__/auto-topup-card.test.tsx +4 -12
  22. package/src/__tests__/backups-tab.test.tsx +3 -4
  23. package/src/__tests__/billing-layout-nav-hidden.test.tsx +5 -37
  24. package/src/__tests__/billing-payment-org-invoices.test.tsx +2 -18
  25. package/src/__tests__/billing.test.tsx +8 -39
  26. package/src/__tests__/bot-settings/resources-tab.test.tsx +1 -3
  27. package/src/__tests__/bot-settings/storage-tab.test.tsx +1 -3
  28. package/src/__tests__/bot-settings/vps-upgrade-card.test.tsx +1 -3
  29. package/src/__tests__/bot-settings-restart.test.tsx +1 -3
  30. package/src/__tests__/bot-settings.test.tsx +2 -6
  31. package/src/__tests__/brand.test.ts +6 -26
  32. package/src/__tests__/buy-credits-panel.test.tsx +1 -3
  33. package/src/__tests__/buy-crypto-credits-panel.test.tsx +101 -119
  34. package/src/__tests__/capability-conflicts.test.ts +2 -8
  35. package/src/__tests__/capability-resolver.test.tsx +2 -12
  36. package/src/__tests__/channel-wizard.test.tsx +4 -17
  37. package/src/__tests__/chat/chat-panel.test.tsx +1 -4
  38. package/src/__tests__/chat-store.test.ts +5 -15
  39. package/src/__tests__/command-center.test.tsx +10 -12
  40. package/src/__tests__/compliance-retention-edit.test.tsx +3 -6
  41. package/src/__tests__/confirmation-tracker.test.tsx +3 -18
  42. package/src/__tests__/coupon-input.test.tsx +1 -3
  43. package/src/__tests__/create-instance.test.tsx +1 -3
  44. package/src/__tests__/credit-balance.test.tsx +4 -12
  45. package/src/__tests__/credits.test.tsx +32 -85
  46. package/src/__tests__/email-verification-banner.test.tsx +2 -6
  47. package/src/__tests__/error-boundaries.test.tsx +0 -1
  48. package/src/__tests__/fetch-pricing.test.ts +2 -1
  49. package/src/__tests__/field-oauth.test.tsx +2 -6
  50. package/src/__tests__/fixtures/mock-manifests-data.js +1 -3
  51. package/src/__tests__/fixtures/mock-manifests.ts +2 -4
  52. package/src/__tests__/fleet-health-timestamp.test.tsx +1 -8
  53. package/src/__tests__/fleet-health-update.test.tsx +1 -8
  54. package/src/__tests__/gpu-dashboard.test.tsx +2 -6
  55. package/src/__tests__/instance-detail.test.tsx +3 -9
  56. package/src/__tests__/instance-list.test.tsx +1 -5
  57. package/src/__tests__/layout-snapshots.test.tsx +64 -11
  58. package/src/__tests__/marketplace-admin.test.tsx +2 -6
  59. package/src/__tests__/marketplace.test.tsx +11 -35
  60. package/src/__tests__/merge-api-rates.test.ts +1 -6
  61. package/src/__tests__/middleware.test.ts +32 -219
  62. package/src/__tests__/next-config-headers.test.ts +1 -3
  63. package/src/__tests__/notifications.test.tsx +4 -11
  64. package/src/__tests__/oauth-buttons.test.tsx +36 -59
  65. package/src/__tests__/oauth-error-mapping.test.tsx +2 -6
  66. package/src/__tests__/observability.test.tsx +23 -36
  67. package/src/__tests__/onboarding-page.test.tsx +4 -6
  68. package/src/__tests__/org-billing-api.test.tsx +1 -6
  69. package/src/__tests__/plugin-install-flow.test.tsx +28 -58
  70. package/src/__tests__/plugin-registry.test.tsx +3 -11
  71. package/src/__tests__/plugin-tool-sync.test.ts +1 -3
  72. package/src/__tests__/plugins-catalog-error.test.tsx +2 -6
  73. package/src/__tests__/plugins-toggle-race.test.tsx +3 -5
  74. package/src/__tests__/portfolio-chart.test.tsx +2 -6
  75. package/src/__tests__/promotion-form.test.tsx +2 -6
  76. package/src/__tests__/promotions-list.test.tsx +1 -3
  77. package/src/__tests__/provider-key-api.test.ts +2 -1
  78. package/src/__tests__/resend-verification-button.test.tsx +8 -24
  79. package/src/__tests__/secrets-audit-pagination.test.tsx +1 -3
  80. package/src/__tests__/settings.test.tsx +11 -21
  81. package/src/__tests__/setup-checklist.test.tsx +3 -9
  82. package/src/__tests__/setup.ts +25 -6
  83. package/src/__tests__/snapshot-api.test.ts +2 -1
  84. package/src/__tests__/step-superpowers.test.tsx +1 -3
  85. package/src/__tests__/tenant-context.test.tsx +1 -6
  86. package/src/__tests__/tenant-keys-api.test.ts +3 -4
  87. package/src/__tests__/tenant-table-pagination.test.tsx +2 -6
  88. package/src/__tests__/terminal-log-cleanup.test.tsx +0 -1
  89. package/src/__tests__/transaction-history.test.tsx +190 -238
  90. package/src/__tests__/trpc-types.test.ts +2 -6
  91. package/src/__tests__/use-chat.test.ts +1 -3
  92. package/src/__tests__/use-plugin-setup-chat-stale-closure.test.ts +1 -4
  93. package/src/__tests__/use-sidecar-bridge.test.tsx +105 -0
  94. package/src/__tests__/use-webmcp.test.ts +1 -3
  95. package/src/__tests__/validate-elevenlabs-key.test.ts +2 -1
  96. package/src/__tests__/verify-page.test.tsx +4 -13
  97. package/src/__tests__/verify-redirect.test.tsx +2 -6
  98. package/src/app/(auth)/error.tsx +1 -7
  99. package/src/app/(auth)/forgot-password/page.tsx +4 -18
  100. package/src/app/(auth)/login/page.tsx +5 -22
  101. package/src/app/(auth)/reset-password/page.tsx +2 -12
  102. package/src/app/(auth)/signup/page.tsx +10 -44
  103. package/src/app/(auth)/verify/page.tsx +47 -0
  104. package/src/app/(dashboard)/billing/credits/page.tsx +14 -67
  105. package/src/app/(dashboard)/billing/error.tsx +2 -10
  106. package/src/app/(dashboard)/billing/layout.tsx +12 -62
  107. package/src/app/(dashboard)/billing/payment/page.tsx +17 -68
  108. package/src/app/(dashboard)/billing/plans/page.tsx +3 -9
  109. package/src/app/(dashboard)/billing/usage/hosted/page.tsx +8 -25
  110. package/src/app/(dashboard)/billing/usage/page.tsx +63 -103
  111. package/src/app/(dashboard)/changesets/[id]/changeset-detail-client.tsx +9 -27
  112. package/src/app/(dashboard)/changesets/[id]/error.tsx +2 -6
  113. package/src/app/(dashboard)/changesets/error.tsx +1 -7
  114. package/src/app/(dashboard)/chat/page.tsx +2 -6
  115. package/src/app/(dashboard)/dashboard/network/page.tsx +5 -19
  116. package/src/app/(dashboard)/error.tsx +1 -7
  117. package/src/app/(dashboard)/layout.tsx +15 -36
  118. package/src/app/(dashboard)/marketplace/[plugin]/page.tsx +14 -51
  119. package/src/app/(dashboard)/marketplace/error.tsx +1 -7
  120. package/src/app/(dashboard)/marketplace/page.tsx +6 -27
  121. package/src/app/(dashboard)/not-found.tsx +2 -5
  122. package/src/app/(dashboard)/onboarding/page.tsx +5 -22
  123. package/src/app/(dashboard)/settings/account/page.tsx +1 -6
  124. package/src/app/(dashboard)/settings/activity/page.tsx +8 -34
  125. package/src/app/(dashboard)/settings/api-keys/page.tsx +15 -60
  126. package/src/app/(dashboard)/settings/brain/page.tsx +9 -31
  127. package/src/app/(dashboard)/settings/error.tsx +2 -10
  128. package/src/app/(dashboard)/settings/notifications/page.tsx +2 -6
  129. package/src/app/(dashboard)/settings/org/page.tsx +13 -56
  130. package/src/app/(dashboard)/settings/page.tsx +1 -0
  131. package/src/app/(dashboard)/settings/profile/page.tsx +126 -73
  132. package/src/app/(dashboard)/settings/providers/page.tsx +21 -78
  133. package/src/app/(dashboard)/settings/secrets/page.tsx +13 -58
  134. package/src/app/(dashboard)/settings/security/page.tsx +31 -111
  135. package/src/app/admin/email-templates/email-templates-client.tsx +15 -58
  136. package/src/app/admin/error.tsx +1 -7
  137. package/src/app/admin/fleet-updates/error.tsx +1 -7
  138. package/src/app/admin/fleet-updates/fleet-updates-client.tsx +10 -50
  139. package/src/app/admin/layout.tsx +4 -0
  140. package/src/app/admin/payment-methods/page.tsx +9 -38
  141. package/src/app/admin/products/error.tsx +2 -7
  142. package/src/app/admin/products/page.tsx +1 -4
  143. package/src/app/admin/promotions/[id]/page.tsx +9 -38
  144. package/src/app/admin/promotions/page.tsx +9 -36
  145. package/src/app/admin/rate-overrides/page.tsx +9 -45
  146. package/src/app/auth/callback/[provider]/page.tsx +1 -8
  147. package/src/app/auth/verify/page.tsx +9 -36
  148. package/src/app/channels/error.tsx +2 -10
  149. package/src/app/channels/layout.tsx +9 -0
  150. package/src/app/channels/page.tsx +8 -20
  151. package/src/app/channels/setup/[plugin]/page.tsx +3 -5
  152. package/src/app/error.tsx +1 -7
  153. package/src/app/fleet/error.tsx +1 -7
  154. package/src/app/fleet/layout.tsx +5 -0
  155. package/src/app/fleet/settings/page.tsx +1 -3
  156. package/src/app/global-error.tsx +2 -10
  157. package/src/app/globals.css +1 -4
  158. package/src/app/instances/[id]/instance-detail-client.tsx +51 -125
  159. package/src/app/instances/error.tsx +2 -10
  160. package/src/app/instances/instance-list-client.tsx +20 -69
  161. package/src/app/instances/layout.tsx +9 -0
  162. package/src/app/instances/new/create-instance-client.tsx +10 -31
  163. package/src/app/layout.tsx +2 -10
  164. package/src/app/not-found.tsx +1 -3
  165. package/src/app/page.tsx +1 -2
  166. package/src/app/plugins/error.tsx +2 -10
  167. package/src/app/plugins/layout.tsx +5 -0
  168. package/src/app/plugins/page.tsx +16 -48
  169. package/src/app/pricing/error.tsx +1 -7
  170. package/src/app/privacy/page.tsx +93 -150
  171. package/src/app/status/error.tsx +1 -7
  172. package/src/app/terms/page.tsx +89 -144
  173. package/src/components/account-switcher.tsx +25 -52
  174. package/src/components/admin/accounting-dashboard.tsx +1 -3
  175. package/src/components/admin/admin-guard.tsx +1 -3
  176. package/src/components/admin/admin-nav.tsx +1 -3
  177. package/src/components/admin/affiliate-dashboard.tsx +25 -94
  178. package/src/components/admin/audit-log-table.tsx +13 -49
  179. package/src/components/admin/billing-health-dashboard.tsx +7 -25
  180. package/src/components/admin/bulk-actions-bar.test.tsx +1 -7
  181. package/src/components/admin/bulk-actions-bar.tsx +1 -3
  182. package/src/components/admin/bulk-export-dialog.test.tsx +1 -7
  183. package/src/components/admin/bulk-export-dialog.tsx +6 -32
  184. package/src/components/admin/bulk-grant-dialog.test.tsx +2 -6
  185. package/src/components/admin/bulk-grant-dialog.tsx +4 -15
  186. package/src/components/admin/bulk-preview-dialog.tsx +3 -12
  187. package/src/components/admin/bulk-reactivate-dialog.tsx +1 -7
  188. package/src/components/admin/bulk-select-all-banner.tsx +1 -6
  189. package/src/components/admin/bulk-suspend-dialog.tsx +5 -12
  190. package/src/components/admin/bulk-undo-toast.tsx +1 -2
  191. package/src/components/admin/compliance-dashboard.tsx +31 -101
  192. package/src/components/admin/gpu-dashboard.tsx +21 -70
  193. package/src/components/admin/grant-credits-dialog.tsx +4 -17
  194. package/src/components/admin/incident-dashboard.tsx +10 -25
  195. package/src/components/admin/inference-dashboard.tsx +14 -54
  196. package/src/components/admin/marketplace-admin.tsx +18 -60
  197. package/src/components/admin/migrations-dashboard.tsx +9 -42
  198. package/src/components/admin/onboarding-dashboard.tsx +14 -64
  199. package/src/components/admin/pool-config-dashboard.tsx +4 -10
  200. package/src/components/admin/products/fleet-form.tsx +2 -11
  201. package/src/components/admin/products/nav-editor.tsx +3 -10
  202. package/src/components/admin/promotions/promotion-form.tsx +9 -42
  203. package/src/components/admin/roles-dashboard.tsx +7 -34
  204. package/src/components/admin/suspend-dialog.tsx +4 -11
  205. package/src/components/admin/tenant-notes-panel.tsx +1 -3
  206. package/src/components/admin/tenant-row-actions.tsx +4 -20
  207. package/src/components/admin/tenant-table.tsx +12 -49
  208. package/src/components/auth/auth-redirect.tsx +11 -3
  209. package/src/components/auth/email-verification-result-banner.tsx +1 -3
  210. package/src/components/auth/resend-verification-button.tsx +2 -10
  211. package/src/components/auth/wopr-wordmark.tsx +1 -3
  212. package/src/components/billing/add-payment-method-dialog.tsx +1 -2
  213. package/src/components/billing/affiliate-dashboard.tsx +4 -16
  214. package/src/components/billing/amount-selector.tsx +1 -3
  215. package/src/components/billing/auto-topup-card.tsx +2 -11
  216. package/src/components/billing/buy-credits-panel.tsx +14 -17
  217. package/src/components/billing/byok-callout.tsx +6 -8
  218. package/src/components/billing/confirmation-tracker.tsx +4 -14
  219. package/src/components/billing/credit-balance-badge.tsx +22 -0
  220. package/src/components/billing/credit-balance.tsx +3 -9
  221. package/src/components/billing/crypto-checkout.tsx +5 -24
  222. package/src/components/billing/degraded-state-banner.tsx +1 -3
  223. package/src/components/billing/deposit-view.tsx +301 -41
  224. package/src/components/billing/dividend-banner.tsx +1 -3
  225. package/src/components/billing/dividend-eligibility.tsx +3 -12
  226. package/src/components/billing/dividend-pool-stats.tsx +6 -20
  227. package/src/components/billing/first-dividend-dialog.tsx +2 -2
  228. package/src/components/billing/org-billing-page.tsx +8 -31
  229. package/src/components/billing/payment-method-picker.tsx +2 -10
  230. package/src/components/billing/suspension-banner.tsx +2 -7
  231. package/src/components/billing/transaction-history.tsx +10 -58
  232. package/src/components/billing/unified-checkout.tsx +547 -0
  233. package/src/components/bot-settings/backups-tab.tsx +9 -33
  234. package/src/components/bot-settings/bot-settings-client.tsx +32 -134
  235. package/src/components/bot-settings/resources-tab.tsx +2 -9
  236. package/src/components/bot-settings/storage-tab.tsx +19 -48
  237. package/src/components/bot-settings/vps-info-panel.tsx +3 -11
  238. package/src/components/bot-settings/vps-upgrade-card.tsx +3 -4
  239. package/src/components/brand-hydrator.tsx +13 -0
  240. package/src/components/channel-wizard/field-interactive.tsx +1 -3
  241. package/src/components/channel-wizard/field-qr.tsx +10 -39
  242. package/src/components/channel-wizard/step-renderer.tsx +5 -28
  243. package/src/components/channel-wizard/wizard.tsx +6 -31
  244. package/src/components/chat/chat-message.tsx +1 -4
  245. package/src/components/chat/chat-panel.tsx +4 -18
  246. package/src/components/chat/chat-widget.tsx +3 -14
  247. package/src/components/dashboard/command-center.tsx +15 -61
  248. package/src/components/fleet/update-settings-card.tsx +7 -23
  249. package/src/components/instance-update-banner.tsx +130 -0
  250. package/src/components/instances/friends-tab.test.tsx +2 -9
  251. package/src/components/instances/friends-tab.tsx +18 -74
  252. package/src/components/instances/update-available-badge.tsx +2 -11
  253. package/src/components/landing/hero.tsx +3 -9
  254. package/src/components/landing/landing-page.tsx +1 -3
  255. package/src/components/landing/portfolio-chart.tsx +4 -9
  256. package/src/components/landing/story-sections.tsx +1 -3
  257. package/src/components/landing/terminal-sequence.tsx +4 -17
  258. package/src/components/marketplace/empty-state.tsx +2 -6
  259. package/src/components/marketplace/first-visit-hero.tsx +1 -3
  260. package/src/components/marketplace/install-wizard.tsx +20 -77
  261. package/src/components/marketplace/marketplace-tabs.tsx +1 -4
  262. package/src/components/marketplace/plugin-card.tsx +2 -9
  263. package/src/components/marketplace/superpower-content.tsx +1 -3
  264. package/src/components/marketplace/terminal-search.tsx +2 -8
  265. package/src/components/oauth-buttons.tsx +29 -14
  266. package/src/components/observability/fleet-health.tsx +5 -18
  267. package/src/components/observability/health-overview.tsx +7 -20
  268. package/src/components/observability/logs-viewer.tsx +8 -32
  269. package/src/components/observability/metrics-dashboard.tsx +2 -15
  270. package/src/components/onboarding/fallback-setup.tsx +6 -25
  271. package/src/components/onboarding/setup-checklist.tsx +18 -51
  272. package/src/components/onboarding/step-superpowers.tsx +1 -4
  273. package/src/components/plugin-setup/setup-chat-panel.tsx +6 -22
  274. package/src/components/pricing/dividend-calculator.tsx +6 -12
  275. package/src/components/pricing/dividend-stats.tsx +5 -17
  276. package/src/components/pricing/pricing-page.tsx +17 -36
  277. package/src/components/settings/create-org-wizard.tsx +2 -5
  278. package/src/components/sidebar.tsx +7 -42
  279. package/src/components/sidecar-frame.tsx +78 -0
  280. package/src/components/status/status-page.tsx +6 -28
  281. package/src/components/ui/alert-dialog.tsx +8 -25
  282. package/src/components/ui/badge.tsx +2 -8
  283. package/src/components/ui/banner.tsx +1 -6
  284. package/src/components/ui/card.tsx +5 -24
  285. package/src/components/ui/checkbox.tsx +1 -5
  286. package/src/components/ui/collapsible.tsx +3 -8
  287. package/src/components/ui/dialog.tsx +4 -10
  288. package/src/components/ui/dropdown-menu.tsx +9 -18
  289. package/src/components/ui/form.tsx +2 -16
  290. package/src/components/ui/popover.tsx +3 -23
  291. package/src/components/ui/progress.tsx +1 -5
  292. package/src/components/ui/radio-group.tsx +3 -15
  293. package/src/components/ui/select.tsx +4 -17
  294. package/src/components/ui/sheet.tsx +5 -19
  295. package/src/components/ui/skeleton.tsx +1 -7
  296. package/src/components/ui/table.tsx +5 -22
  297. package/src/components/ui/tabs.tsx +3 -13
  298. package/src/components/ui/tooltip.tsx +1 -1
  299. package/src/components/unified-sidebar.tsx +493 -0
  300. package/src/hooks/__tests__/use-fleet-sse.test.ts +1 -4
  301. package/src/hooks/__tests__/use-save-queue.test.ts +2 -8
  302. package/src/hooks/use-credit-balance.ts +27 -0
  303. package/src/hooks/use-my-org-role.ts +1 -3
  304. package/src/hooks/use-plugin-registry.ts +8 -14
  305. package/src/hooks/use-plugin-setup-chat.ts +2 -5
  306. package/src/hooks/use-sidecar-bridge.tsx +148 -0
  307. package/src/hooks/use-webmcp.ts +1 -4
  308. package/src/lib/__tests__/admin-api.test.ts +1 -3
  309. package/src/lib/__tests__/api-bot-crud.test.ts +8 -18
  310. package/src/lib/__tests__/api-fetch.test.ts +4 -16
  311. package/src/lib/__tests__/org-billing-api.test.ts +1 -3
  312. package/src/lib/__tests__/pricing-data.test.ts +0 -8
  313. package/src/lib/__tests__/settings-api.test.ts +1 -3
  314. package/src/lib/admin-affiliate-api.ts +2 -7
  315. package/src/lib/admin-api.ts +6 -26
  316. package/src/lib/admin-incident-api.ts +11 -19
  317. package/src/lib/admin-marketplace-api.ts +1 -5
  318. package/src/lib/api-config.test.ts +5 -50
  319. package/src/lib/api.ts +143 -122
  320. package/src/lib/auth-client.ts +1 -2
  321. package/src/lib/bot-settings-data.ts +11 -36
  322. package/src/lib/brand-config.ts +56 -115
  323. package/src/lib/brand.ts +2 -15
  324. package/src/lib/chat/use-chat.ts +2 -7
  325. package/src/lib/cost-comparison-data.test.ts +1 -3
  326. package/src/lib/cost-comparison-data.ts +1 -4
  327. package/src/lib/errors.ts +1 -4
  328. package/src/lib/fetch-utils.test.ts +26 -9
  329. package/src/lib/fetch-utils.ts +40 -11
  330. package/src/lib/logger.ts +2 -0
  331. package/src/lib/marketplace-data.ts +3 -11
  332. package/src/lib/oauth-errors.ts +2 -4
  333. package/src/lib/onboarding-data.ts +3 -11
  334. package/src/lib/org-api.ts +2 -10
  335. package/src/lib/org-billing-api.ts +5 -19
  336. package/src/lib/plugin/tool-definitions.ts +1 -2
  337. package/src/lib/require-auth.ts +57 -0
  338. package/src/lib/settings-api.ts +1 -4
  339. package/src/lib/sidecar-routes.ts +43 -0
  340. package/src/lib/trpc-server.ts +49 -0
  341. package/src/lib/trpc-types.ts +4 -6
  342. package/src/lib/trpc.tsx +12 -4
  343. package/src/lib/validate-redirect-url.ts +1 -4
  344. package/src/lib/webmcp/marketplace-onboarding-tools.ts +6 -16
  345. package/src/lib/webmcp/register.ts +1 -4
  346. package/src/lib/webmcp/tools.ts +2 -9
  347. package/src/proxy.ts +35 -212
  348. package/src/types/missing-deps.d.ts +2 -8
  349. package/tsconfig.json +1 -8
  350. package/biome.json +0 -52
  351. package/src/__tests__/__snapshots__/layout-snapshots.test.tsx.snap +0 -741
  352. package/src/__tests__/billing-byok-callout.test.tsx +0 -76
  353. package/src/lib/__tests__/__snapshots__/pricing-data.test.ts.snap +0 -112
@@ -0,0 +1,493 @@
1
+ "use client";
2
+
3
+ import {
4
+ Bot,
5
+ ChevronRight,
6
+ DollarSign,
7
+ FolderKanban,
8
+ Inbox,
9
+ LayoutDashboard,
10
+ LogOutIcon,
11
+ Plus,
12
+ PlusCircle,
13
+ Repeat,
14
+ SettingsIcon,
15
+ Shield,
16
+ Target,
17
+ UserIcon,
18
+ Users,
19
+ Wallet,
20
+ Zap,
21
+ } from "lucide-react";
22
+ import Image from "next/image";
23
+ import { usePathname, useRouter } from "next/navigation";
24
+ import { useState } from "react";
25
+ import { CreditBalanceBadge } from "@/components/billing/credit-balance-badge";
26
+ import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
27
+ import {
28
+ DropdownMenu,
29
+ DropdownMenuContent,
30
+ DropdownMenuItem,
31
+ DropdownMenuLabel,
32
+ DropdownMenuSeparator,
33
+ DropdownMenuTrigger,
34
+ } from "@/components/ui/dropdown-menu";
35
+ import { Skeleton } from "@/components/ui/skeleton";
36
+ import { type SidebarAgent, type SidebarProject, useSidecarBridge } from "@/hooks/use-sidecar-bridge";
37
+ import { signOut, useSession } from "@/lib/auth-client";
38
+ import { productName } from "@/lib/brand-config";
39
+ import { getRouteType } from "@/lib/sidecar-routes";
40
+ import { cn } from "@/lib/utils";
41
+
42
+ // ---------------------------------------------------------------------------
43
+ // Helpers
44
+ // ---------------------------------------------------------------------------
45
+
46
+ function getInitials(name: string): string {
47
+ return name
48
+ .split(" ")
49
+ .map((part) => part[0])
50
+ .filter(Boolean)
51
+ .slice(0, 2)
52
+ .join("")
53
+ .toUpperCase();
54
+ }
55
+
56
+ function isActive(
57
+ href: string,
58
+ type: "iframe" | "native",
59
+ currentSidecarPath: string | null,
60
+ pathname: string,
61
+ ): boolean {
62
+ if (type === "iframe") {
63
+ if (!currentSidecarPath) return false;
64
+ if (href === currentSidecarPath) return true;
65
+ return currentSidecarPath.startsWith(`${href}/`);
66
+ }
67
+ // Native routes
68
+ if (href === pathname) return true;
69
+ return pathname.startsWith(`${href}/`);
70
+ }
71
+
72
+ // ---------------------------------------------------------------------------
73
+ // Section header
74
+ // ---------------------------------------------------------------------------
75
+
76
+ function SectionLabel({ children }: { children: React.ReactNode }) {
77
+ return (
78
+ <div className="px-3 pt-4 pb-1 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground/60">
79
+ {children}
80
+ </div>
81
+ );
82
+ }
83
+
84
+ // ---------------------------------------------------------------------------
85
+ // Nav item
86
+ // ---------------------------------------------------------------------------
87
+
88
+ interface NavItemProps {
89
+ icon: React.ElementType;
90
+ label: string;
91
+ href: string;
92
+ badge?: React.ReactNode;
93
+ onClick: () => void;
94
+ active: boolean;
95
+ }
96
+
97
+ function NavItem({ icon: Icon, label, href, badge, onClick, active }: NavItemProps) {
98
+ return (
99
+ <button
100
+ type="button"
101
+ onClick={onClick}
102
+ data-href={href}
103
+ className={cn(
104
+ "flex w-full items-center justify-between rounded-md px-3 py-2 text-sm font-medium transition-colors hover:bg-sidebar-accent hover:text-foreground",
105
+ active ? "bg-terminal/5 border-l-2 border-terminal text-terminal" : "text-muted-foreground",
106
+ )}
107
+ >
108
+ <span className="flex items-center gap-2.5">
109
+ <Icon className="size-4 shrink-0 opacity-70" />
110
+ {label}
111
+ </span>
112
+ {badge}
113
+ </button>
114
+ );
115
+ }
116
+
117
+ // ---------------------------------------------------------------------------
118
+ // Collapsible list section (Projects / Agents)
119
+ // ---------------------------------------------------------------------------
120
+
121
+ interface CollapsibleSectionProps {
122
+ title: string;
123
+ onAdd: () => void;
124
+ children: React.ReactNode;
125
+ defaultOpen?: boolean;
126
+ }
127
+
128
+ function CollapsibleSection({ title, onAdd, children, defaultOpen = true }: CollapsibleSectionProps) {
129
+ const [open, setOpen] = useState(defaultOpen);
130
+ return (
131
+ <Collapsible open={open} onOpenChange={setOpen}>
132
+ <div className="flex items-center justify-between px-3 pt-4 pb-1">
133
+ <CollapsibleTrigger className="flex items-center gap-1 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground/60 hover:text-muted-foreground transition-colors">
134
+ <ChevronRight className={cn("size-3 transition-transform", open && "rotate-90")} />
135
+ {title}
136
+ </CollapsibleTrigger>
137
+ <button
138
+ type="button"
139
+ onClick={(e) => {
140
+ e.stopPropagation();
141
+ onAdd();
142
+ }}
143
+ className="text-muted-foreground/60 hover:text-muted-foreground transition-colors"
144
+ >
145
+ <Plus className="size-3.5" />
146
+ </button>
147
+ </div>
148
+ <CollapsibleContent>{children}</CollapsibleContent>
149
+ </Collapsible>
150
+ );
151
+ }
152
+
153
+ // ---------------------------------------------------------------------------
154
+ // Agent status indicator
155
+ // ---------------------------------------------------------------------------
156
+
157
+ function AgentStatusBadge({ agent }: { agent: SidebarAgent }) {
158
+ if (agent.pauseReason === "budget") {
159
+ return (
160
+ <span className="flex items-center gap-1 text-[10px] font-mono text-amber-500">
161
+ <DollarSign className="size-3" />
162
+ </span>
163
+ );
164
+ }
165
+ if (agent.liveRun) {
166
+ return (
167
+ <span className="flex items-center gap-1 text-[10px] font-mono text-blue-400">
168
+ <span className="relative flex size-2">
169
+ <span className="absolute inline-flex size-full animate-ping rounded-full bg-blue-400 opacity-75" />
170
+ <span className="relative inline-flex size-2 rounded-full bg-blue-500" />
171
+ </span>
172
+ {agent.liveRunCount > 1 ? agent.liveRunCount : "live"}
173
+ </span>
174
+ );
175
+ }
176
+ if (agent.status === "idle") {
177
+ return <span className="text-[10px] font-mono text-muted-foreground/50">idle</span>;
178
+ }
179
+ return null;
180
+ }
181
+
182
+ // ---------------------------------------------------------------------------
183
+ // Badge helpers
184
+ // ---------------------------------------------------------------------------
185
+
186
+ function CountBadge({ count }: { count: number }) {
187
+ if (count <= 0) return null;
188
+ return (
189
+ <span className="flex size-5 items-center justify-center rounded-full bg-terminal/10 text-[10px] font-mono font-semibold text-terminal">
190
+ {count > 99 ? "99+" : count}
191
+ </span>
192
+ );
193
+ }
194
+
195
+ // ---------------------------------------------------------------------------
196
+ // Main export
197
+ // ---------------------------------------------------------------------------
198
+
199
+ export function UnifiedSidebarContent({ onNavigate }: { onNavigate?: () => void }) {
200
+ const pathname = usePathname();
201
+ const router = useRouter();
202
+ const { data: session, isPending } = useSession();
203
+ const { sidebarData, navigate: sidecarNavigate, command, currentSidecarPath } = useSidecarBridge();
204
+
205
+ const user = session?.user;
206
+ const isAdmin = (user as { role?: string } | undefined)?.role === "platform_admin";
207
+
208
+ // Navigation handler — iframe routes go to sidecar, native routes use router
209
+ function handleNav(href: string) {
210
+ const type = getRouteType(href);
211
+ if (type === "iframe") {
212
+ sidecarNavigate(href);
213
+ window.history.pushState(null, "", href);
214
+ } else {
215
+ router.push(href);
216
+ }
217
+ onNavigate?.();
218
+ }
219
+
220
+ function handleCommand(action: string) {
221
+ command(action);
222
+ }
223
+
224
+ function checkActive(href: string): boolean {
225
+ const type = getRouteType(href);
226
+ return isActive(href, type, currentSidecarPath, pathname);
227
+ }
228
+
229
+ async function handleSignOut() {
230
+ try {
231
+ await signOut();
232
+ } catch {
233
+ // Continue to redirect even if signOut throws
234
+ }
235
+ router.push("/login");
236
+ }
237
+
238
+ // Dynamic data from sidecar
239
+ const projects: SidebarProject[] = sidebarData?.projects ?? [];
240
+ const agents: SidebarAgent[] = sidebarData?.agents ?? [];
241
+ const inboxBadge = sidebarData?.inboxBadge ?? 0;
242
+ const liveRunCount = sidebarData?.liveRunCount ?? 0;
243
+
244
+ return (
245
+ <div data-slot="sidebar" className="flex h-full flex-col">
246
+ {/* Brand header */}
247
+ <div className="flex h-14 items-center border-b border-sidebar-border px-6">
248
+ <span
249
+ className="text-lg font-semibold tracking-tight text-terminal"
250
+ style={{
251
+ textShadow: "0 0 12px var(--terminal-glow, rgba(0, 255, 65, 0.4))",
252
+ }}
253
+ >
254
+ {productName()}
255
+ </span>
256
+ </div>
257
+
258
+ {/* Scrollable nav body */}
259
+ <nav className="flex-1 overflow-y-auto px-3 py-2">
260
+ {/* Quick action */}
261
+ <div className="py-1">
262
+ <button
263
+ type="button"
264
+ onClick={() => handleCommand("openNewIssue")}
265
+ className="flex w-full items-center gap-2.5 rounded-md px-3 py-2 text-sm font-medium text-muted-foreground transition-colors hover:bg-sidebar-accent hover:text-foreground"
266
+ >
267
+ <PlusCircle className="size-4 shrink-0 opacity-70" />
268
+ New Issue
269
+ </button>
270
+ </div>
271
+
272
+ {/* Primary nav */}
273
+ <div className="space-y-0.5 py-1">
274
+ <NavItem
275
+ icon={LayoutDashboard}
276
+ label="Dashboard"
277
+ href="/dashboard"
278
+ badge={<CountBadge count={liveRunCount} />}
279
+ onClick={() => handleNav("/dashboard")}
280
+ active={checkActive("/dashboard")}
281
+ />
282
+ <NavItem
283
+ icon={Inbox}
284
+ label="Inbox"
285
+ href="/inbox"
286
+ badge={<CountBadge count={inboxBadge} />}
287
+ onClick={() => handleNav("/inbox")}
288
+ active={checkActive("/inbox")}
289
+ />
290
+ </div>
291
+
292
+ {/* Work section */}
293
+ <SectionLabel>Work</SectionLabel>
294
+ <div className="space-y-0.5">
295
+ <NavItem
296
+ icon={FolderKanban}
297
+ label="Issues"
298
+ href="/issues"
299
+ onClick={() => handleNav("/issues")}
300
+ active={checkActive("/issues")}
301
+ />
302
+ <NavItem
303
+ icon={Repeat}
304
+ label="Routines"
305
+ href="/routines"
306
+ onClick={() => handleNav("/routines")}
307
+ active={checkActive("/routines")}
308
+ />
309
+ <NavItem
310
+ icon={Target}
311
+ label="Goals"
312
+ href="/goals"
313
+ onClick={() => handleNav("/goals")}
314
+ active={checkActive("/goals")}
315
+ />
316
+ </div>
317
+
318
+ {/* Projects — hide the whole section when the sidecar tells us the
319
+ feature is off (hosted mode). When showProjects is undefined
320
+ (older sidecar) or true, render normally including the
321
+ empty-state CTA so standalone users with zero projects still
322
+ have an entry point. */}
323
+ {sidebarData?.showProjects !== false && (
324
+ <CollapsibleSection title="Projects" onAdd={() => handleCommand("openNewProject")}>
325
+ <div className="space-y-0.5 pl-1">
326
+ {projects.map((project) => {
327
+ const href = `/projects/${project.urlKey}/issues`;
328
+ return (
329
+ <button
330
+ key={project.id}
331
+ type="button"
332
+ onClick={() => handleNav(href)}
333
+ className={cn(
334
+ "flex w-full items-center gap-2.5 rounded-md px-3 py-1.5 text-sm transition-colors hover:bg-sidebar-accent hover:text-foreground",
335
+ checkActive(href)
336
+ ? "bg-terminal/5 border-l-2 border-terminal text-terminal"
337
+ : "text-muted-foreground",
338
+ )}
339
+ >
340
+ <span
341
+ className="size-2.5 rounded-full shrink-0"
342
+ style={{
343
+ backgroundColor: project.color ?? "var(--muted-foreground)",
344
+ }}
345
+ />
346
+ <span className="truncate">{project.name}</span>
347
+ </button>
348
+ );
349
+ })}
350
+ {projects.length === 0 && (
351
+ <span className="block px-3 py-1.5 text-xs text-muted-foreground/50">No projects yet</span>
352
+ )}
353
+ </div>
354
+ </CollapsibleSection>
355
+ )}
356
+
357
+ {/* Agents — collapsible, dynamic from sidecar */}
358
+ <CollapsibleSection title="Agents" onAdd={() => handleCommand("openNewAgent")}>
359
+ <div className="space-y-0.5 pl-1">
360
+ {agents.map((agent) => {
361
+ const href = `/agents/${agent.id}`;
362
+ return (
363
+ <button
364
+ key={agent.id}
365
+ type="button"
366
+ onClick={() => handleNav(href)}
367
+ className={cn(
368
+ "flex w-full items-center justify-between rounded-md px-3 py-1.5 text-sm transition-colors hover:bg-sidebar-accent hover:text-foreground",
369
+ checkActive(href)
370
+ ? "bg-terminal/5 border-l-2 border-terminal text-terminal"
371
+ : "text-muted-foreground",
372
+ )}
373
+ >
374
+ <span className="flex items-center gap-2.5 truncate">
375
+ <Bot className="size-4 shrink-0 opacity-70" />
376
+ <span className="truncate">{agent.name}</span>
377
+ </span>
378
+ <AgentStatusBadge agent={agent} />
379
+ </button>
380
+ );
381
+ })}
382
+ {agents.length === 0 && (
383
+ <span className="block px-3 py-1.5 text-xs text-muted-foreground/50">No agents yet</span>
384
+ )}
385
+ </div>
386
+ </CollapsibleSection>
387
+
388
+ {/* Company section */}
389
+ <SectionLabel>Company</SectionLabel>
390
+ <div className="space-y-0.5">
391
+ <NavItem
392
+ icon={Users}
393
+ label="Org"
394
+ href="/org"
395
+ onClick={() => handleNav("/org")}
396
+ active={checkActive("/org")}
397
+ />
398
+ <NavItem
399
+ icon={Zap}
400
+ label="Skills"
401
+ href="/skills"
402
+ onClick={() => handleNav("/skills")}
403
+ active={checkActive("/skills")}
404
+ />
405
+ </div>
406
+
407
+ {/* Account section */}
408
+ <SectionLabel>Account</SectionLabel>
409
+ <div className="space-y-0.5">
410
+ <NavItem
411
+ icon={Wallet}
412
+ label="Credits"
413
+ href="/billing/credits"
414
+ badge={<CreditBalanceBadge />}
415
+ onClick={() => handleNav("/billing/credits")}
416
+ active={checkActive("/billing/credits")}
417
+ />
418
+ <NavItem
419
+ icon={SettingsIcon}
420
+ label="Settings"
421
+ href="/settings"
422
+ onClick={() => handleNav("/settings")}
423
+ active={checkActive("/settings")}
424
+ />
425
+ {isAdmin && (
426
+ <NavItem
427
+ icon={Shield}
428
+ label="Admin"
429
+ href="/admin"
430
+ onClick={() => handleNav("/admin")}
431
+ active={checkActive("/admin")}
432
+ />
433
+ )}
434
+ </div>
435
+ </nav>
436
+
437
+ {/* User footer */}
438
+ <div className="border-t border-sidebar-border px-3 py-3">
439
+ {isPending ? (
440
+ <div className="flex items-center gap-3 px-3 py-2">
441
+ <Skeleton className="size-8 rounded-full" />
442
+ <Skeleton className="h-4 w-24" />
443
+ </div>
444
+ ) : user ? (
445
+ <DropdownMenu>
446
+ <DropdownMenuTrigger className="flex w-full items-center gap-3 rounded-md px-3 py-2 text-sm font-medium transition-colors hover:bg-sidebar-accent hover:text-sidebar-accent-foreground outline-none">
447
+ {user.image ? (
448
+ <Image
449
+ src={user.image}
450
+ alt={user.name ?? "User avatar"}
451
+ width={32}
452
+ height={32}
453
+ className="size-8 rounded-full object-cover"
454
+ />
455
+ ) : (
456
+ <span className="flex size-8 items-center justify-center rounded-full bg-sidebar-accent text-xs font-semibold ring-1 ring-terminal/20">
457
+ {user.name?.trim() ? getInitials(user.name) : <UserIcon className="size-4" />}
458
+ </span>
459
+ )}
460
+ <span className="truncate">{user.name ?? user.email}</span>
461
+ </DropdownMenuTrigger>
462
+ <DropdownMenuContent side="top" align="start" className="w-56">
463
+ <DropdownMenuLabel className="font-normal">
464
+ <div className="flex flex-col gap-1">
465
+ {user.name && <span className="text-sm font-medium">{user.name}</span>}
466
+ {user.email && <span className="text-xs text-muted-foreground">{user.email}</span>}
467
+ </div>
468
+ </DropdownMenuLabel>
469
+ <DropdownMenuSeparator />
470
+ <DropdownMenuItem onClick={() => router.push("/settings")}>
471
+ <SettingsIcon />
472
+ Settings
473
+ </DropdownMenuItem>
474
+ <DropdownMenuSeparator />
475
+ <DropdownMenuItem onClick={handleSignOut}>
476
+ <LogOutIcon />
477
+ Sign out
478
+ </DropdownMenuItem>
479
+ </DropdownMenuContent>
480
+ </DropdownMenu>
481
+ ) : (
482
+ <button
483
+ type="button"
484
+ onClick={() => router.push("/login")}
485
+ className="flex items-center rounded-md px-3 py-2 text-sm font-medium text-sidebar-foreground/70 transition-colors hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
486
+ >
487
+ Sign in
488
+ </button>
489
+ )}
490
+ </div>
491
+ </div>
492
+ );
493
+ }
@@ -140,10 +140,7 @@ describe("useFleetSSE", () => {
140
140
  rerender({ cb: second });
141
141
 
142
142
  act(() => {
143
- latestES().emit(
144
- "fleet",
145
- JSON.stringify({ type: "bot.stopped", botId: "bot-2", timestamp: "t" }),
146
- );
143
+ latestES().emit("fleet", JSON.stringify({ type: "bot.stopped", botId: "bot-2", timestamp: "t" }));
147
144
  });
148
145
 
149
146
  expect(first).not.toHaveBeenCalled();
@@ -74,10 +74,7 @@ describe("useSaveQueue", () => {
74
74
  });
75
75
 
76
76
  it("sets error on save failure and clears it on next successful save", async () => {
77
- const saveFn = vi
78
- .fn()
79
- .mockRejectedValueOnce(new Error("network error"))
80
- .mockResolvedValueOnce(undefined);
77
+ const saveFn = vi.fn().mockRejectedValueOnce(new Error("network error")).mockResolvedValueOnce(undefined);
81
78
 
82
79
  const { result } = renderHook(() => useSaveQueue(saveFn));
83
80
 
@@ -102,10 +99,7 @@ describe("useSaveQueue", () => {
102
99
  resolveSecond = resolve;
103
100
  });
104
101
 
105
- const saveFn = vi
106
- .fn()
107
- .mockRejectedValueOnce(new Error("oops"))
108
- .mockReturnValueOnce(secondPromise);
102
+ const saveFn = vi.fn().mockRejectedValueOnce(new Error("oops")).mockReturnValueOnce(secondPromise);
109
103
 
110
104
  const { result } = renderHook(() => useSaveQueue(saveFn));
111
105
 
@@ -0,0 +1,27 @@
1
+ "use client";
2
+
3
+ import { trpc } from "@/lib/trpc";
4
+
5
+ /** Shared credit balance hook — backed by tRPC React Query.
6
+ * Invalidating queryKey [["billing"]] updates every consumer. */
7
+ export function useCreditBalance() {
8
+ const { data: raw, isLoading, error, refetch } = trpc.billing.creditsBalance.useQuery({});
9
+
10
+ const balance =
11
+ raw != null
12
+ ? ((raw as { balance_credits?: number; balance_cents?: number }).balance_credits ??
13
+ (raw as { balance_cents?: number }).balance_cents ??
14
+ 0) / 100
15
+ : null;
16
+
17
+ const dailyBurn =
18
+ raw != null
19
+ ? ((raw as { daily_burn_credits?: number; daily_burn_cents?: number }).daily_burn_credits ??
20
+ (raw as { daily_burn_cents?: number }).daily_burn_cents ??
21
+ 0) / 100
22
+ : null;
23
+
24
+ const runway = raw != null ? ((raw as { runway_days?: number | null }).runway_days ?? null) : null;
25
+
26
+ return { balance, dailyBurn, runway, isLoading, error, refetch };
27
+ }
@@ -15,9 +15,7 @@ export function useMyOrgRole(org: Organization | null): OrgMember["role"] | null
15
15
  return useMemo(() => {
16
16
  if (!org || !session?.user?.id) return null;
17
17
 
18
- const me = org.members?.find(
19
- (m: OrgMember) => m.userId === session.user.id || m.email === session.user.email,
20
- );
18
+ const me = org.members?.find((m: OrgMember) => m.userId === session.user.id || m.email === session.user.email);
21
19
  return me?.role ?? null;
22
20
  }, [org, session?.user?.id, session?.user?.email]);
23
21
  }
@@ -29,14 +29,14 @@ import {
29
29
 
30
30
  // Re-export types so consumers only need one import source
31
31
  export type {
32
- PluginOption,
33
- PluginCategory,
34
- Superpower,
35
- Personality,
36
- Preset,
37
- ModelOption,
38
32
  ByokProvider,
33
+ ModelOption,
39
34
  OnboardingConfigField,
35
+ Personality,
36
+ PluginCategory,
37
+ PluginOption,
38
+ Preset,
39
+ Superpower,
40
40
  };
41
41
 
42
42
  /**
@@ -102,11 +102,7 @@ export interface PluginRegistry {
102
102
  selectedPlugins: string[],
103
103
  ) => OnboardingConfigField[];
104
104
  /** Resolve plugin dependencies */
105
- resolveDependencies: (
106
- selectedChannels: string[],
107
- selectedProviders: string[],
108
- selectedPlugins: string[],
109
- ) => string[];
105
+ resolveDependencies: (selectedChannels: string[], selectedProviders: string[], selectedPlugins: string[]) => string[];
110
106
  /** Validate a single config field value */
111
107
  validateField: (field: OnboardingConfigField, value: string) => string | null;
112
108
  }
@@ -224,9 +220,7 @@ export function usePluginRegistry(): PluginRegistry {
224
220
  pluginOptions,
225
221
  getAllPlugins: () => [...channels, ...providers, ...categories.flatMap((c) => c.plugins)],
226
222
  getPluginById: (id: string) =>
227
- [...channels, ...providers, ...categories.flatMap((c) => c.plugins)].find(
228
- (p) => p.id === id,
229
- ),
223
+ [...channels, ...providers, ...categories.flatMap((c) => c.plugins)].find((p) => p.id === id),
230
224
  collectConfigFields,
231
225
  resolveDependencies,
232
226
  validateField,
@@ -37,9 +37,7 @@ type SseEvent =
37
37
  | { type: "tool_call"; tool: string; args: Record<string, unknown> }
38
38
  | { type: "typing" };
39
39
 
40
- export function usePluginSetupChat(
41
- onComplete?: (pluginId: string) => void,
42
- ): UsePluginSetupChatReturn {
40
+ export function usePluginSetupChat(onComplete?: (pluginId: string) => void): UsePluginSetupChatReturn {
43
41
  const [state, setState] = useState<PluginSetupState>(initialState);
44
42
  const abortRef = useRef<AbortController | null>(null);
45
43
  const botIdRef = useRef<string | null>(null);
@@ -138,8 +136,7 @@ export function usePluginSetupChat(
138
136
  setState((s) => ({ ...s, isComplete: true }));
139
137
  onComplete?.(pluginId);
140
138
  } else if (event.type === "tool_call" && event.tool === "setup.rollback") {
141
- const reason =
142
- typeof event.args?.reason === "string" ? event.args.reason : "Setup failed";
139
+ const reason = typeof event.args?.reason === "string" ? event.args.reason : "Setup failed";
143
140
  setState((s) => ({
144
141
  ...s,
145
142
  messages: [