@burdenoff/microfe-vibecontrols 2026.623.1 → 2026.623.2

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 (293) hide show
  1. package/dist/components/actions/ActionCard.js +2 -2
  2. package/dist/components/actions/ActionCard.js.map +1 -1
  3. package/dist/components/actions/ExecutionHistory.js +1 -1
  4. package/dist/components/actions/ExecutionHistory.js.map +1 -1
  5. package/dist/components/agent-manager/AgentManagerAdminBar.js +1 -1
  6. package/dist/components/agent-manager/AgentManagerAdminBar.js.map +1 -1
  7. package/dist/components/agent-manager/AgentManagerChatPane.js +2 -2
  8. package/dist/components/agent-manager/AgentManagerChatPane.js.map +1 -1
  9. package/dist/components/agent-manager/AgentManagerInputArea.js +4 -4
  10. package/dist/components/agent-manager/AgentManagerInputArea.js.map +1 -1
  11. package/dist/components/agent-manager/AgentManagerLayout.js +1 -1
  12. package/dist/components/agent-manager/AgentManagerLayout.js.map +1 -1
  13. package/dist/components/agent-manager/AgentManagerMessageItem.js +1 -1
  14. package/dist/components/agent-manager/AgentManagerMessageItem.js.map +1 -1
  15. package/dist/components/agent-manager/AgentManagerSidebar.js +5 -5
  16. package/dist/components/agent-manager/AgentManagerSidebar.js.map +1 -1
  17. package/dist/components/agent-manager/AgentManagerTab.js +1 -1
  18. package/dist/components/agent-manager/AgentManagerTab.js.map +1 -1
  19. package/dist/components/agent-manager/AgentManagerTabBar.js +1 -1
  20. package/dist/components/agent-manager/AgentManagerTabBar.js.map +1 -1
  21. package/dist/components/agent-manager/BridleConfigPanel.js +23 -23
  22. package/dist/components/agent-manager/BridleConfigPanel.js.map +1 -1
  23. package/dist/components/agent-manager/PluginInstallModal.js +1 -1
  24. package/dist/components/agent-manager/PluginInstallModal.js.map +1 -1
  25. package/dist/components/agent-manager/ProfilesPanel.js +5 -5
  26. package/dist/components/agent-manager/ProfilesPanel.js.map +1 -1
  27. package/dist/components/agent-manager/controls/AutonomySelector.js +1 -1
  28. package/dist/components/agent-manager/controls/AutonomySelector.js.map +1 -1
  29. package/dist/components/agent-manager/controls/SdkSelector.js +1 -1
  30. package/dist/components/agent-manager/controls/SdkSelector.js.map +1 -1
  31. package/dist/components/agent-manager/controls/VoiceToggle.js +1 -1
  32. package/dist/components/agent-manager/controls/VoiceToggle.js.map +1 -1
  33. package/dist/components/agent-manager/detail/SessionConfigPanel.js +6 -6
  34. package/dist/components/agent-manager/detail/SessionConfigPanel.js.map +1 -1
  35. package/dist/components/agent-manager/detail/SessionLogsPanel.js +5 -5
  36. package/dist/components/agent-manager/detail/SessionLogsPanel.js.map +1 -1
  37. package/dist/components/agent-manager/detail/SessionToolsPanel.js +1 -1
  38. package/dist/components/agent-manager/detail/SessionToolsPanel.js.map +1 -1
  39. package/dist/components/agent-manager/dialogs/NewSessionDialog.js +2 -2
  40. package/dist/components/agent-manager/dialogs/NewSessionDialog.js.map +1 -1
  41. package/dist/components/agent-manager/dialogs/RenameSessionDialog.js +1 -1
  42. package/dist/components/agent-manager/dialogs/RenameSessionDialog.js.map +1 -1
  43. package/dist/components/agents/AgentCard.js +14 -11
  44. package/dist/components/agents/AgentCard.js.map +1 -1
  45. package/dist/components/ai/AIContextsTab.js +1 -1
  46. package/dist/components/ai/AIContextsTab.js.map +1 -1
  47. package/dist/components/ai/AIOverviewTab.js +4 -4
  48. package/dist/components/ai/AIOverviewTab.js.map +1 -1
  49. package/dist/components/ai/AIPlanPanel.js +4 -4
  50. package/dist/components/ai/AIPlanPanel.js.map +1 -1
  51. package/dist/components/ai/AIPlaygroundTab.js +1 -1
  52. package/dist/components/ai/AIPlaygroundTab.js.map +1 -1
  53. package/dist/components/ai/AIPromptsTab.js +2 -2
  54. package/dist/components/ai/AIPromptsTab.js.map +1 -1
  55. package/dist/components/ai/AIProviderCredentialsCard.js +4 -4
  56. package/dist/components/ai/AIProviderCredentialsCard.js.map +1 -1
  57. package/dist/components/ai/AIQueueTab.js +4 -4
  58. package/dist/components/ai/AIQueueTab.js.map +1 -1
  59. package/dist/components/ai/AISessionsTab.js +4 -4
  60. package/dist/components/ai/AISessionsTab.js.map +1 -1
  61. package/dist/components/ai/AITasksTab.js +1 -1
  62. package/dist/components/ai/AITasksTab.js.map +1 -1
  63. package/dist/components/ai/ClaudeCredentialsCard.js +3 -3
  64. package/dist/components/ai/ClaudeCredentialsCard.js.map +1 -1
  65. package/dist/components/ai/PluginHarnessPicker.js +3 -3
  66. package/dist/components/ai/PluginHarnessPicker.js.map +1 -1
  67. package/dist/components/analytics/AnalyticsBreakdownChart.js +8 -8
  68. package/dist/components/analytics/AnalyticsBreakdownChart.js.map +1 -1
  69. package/dist/components/analytics/AnalyticsTimeSeriesChart.js +5 -5
  70. package/dist/components/analytics/AnalyticsTimeSeriesChart.js.map +1 -1
  71. package/dist/components/audit/AuditDeleteDialog.js +2 -2
  72. package/dist/components/audit/AuditDeleteDialog.js.map +1 -1
  73. package/dist/components/audit/AuditExportButton.js +5 -5
  74. package/dist/components/audit/AuditExportButton.js.map +1 -1
  75. package/dist/components/audit/AuditLogDetail.js +9 -9
  76. package/dist/components/audit/AuditLogDetail.js.map +1 -1
  77. package/dist/components/audit/AuditLogFilters.js +1 -1
  78. package/dist/components/audit/AuditLogFilters.js.map +1 -1
  79. package/dist/components/audit/AuditLogStats.js +1 -1
  80. package/dist/components/audit/AuditLogStats.js.map +1 -1
  81. package/dist/components/audit/AuditLogTable.js +28 -28
  82. package/dist/components/audit/AuditLogTable.js.map +1 -1
  83. package/dist/components/catalog/CatalogCard.js +3 -3
  84. package/dist/components/catalog/CatalogCard.js.map +1 -1
  85. package/dist/components/catalog/CatalogForm.js +1 -1
  86. package/dist/components/catalog/CatalogForm.js.map +1 -1
  87. package/dist/components/common/ActionAgentTargetNotice.js +6 -6
  88. package/dist/components/common/ActionAgentTargetNotice.js.map +1 -1
  89. package/dist/components/common/CommandEditor.js +1 -1
  90. package/dist/components/common/CommandEditor.js.map +1 -1
  91. package/dist/components/docs/DocsPageTree.js +1 -1
  92. package/dist/components/docs/DocsPageTree.js.map +1 -1
  93. package/dist/components/docs/DocsPreviewPanel.js +1 -1
  94. package/dist/components/docs/DocsPreviewPanel.js.map +1 -1
  95. package/dist/components/docs/DocsRevisionHistory.js +4 -4
  96. package/dist/components/docs/DocsRevisionHistory.js.map +1 -1
  97. package/dist/components/docs/DocsSettingsPanel.js +7 -7
  98. package/dist/components/docs/DocsSettingsPanel.js.map +1 -1
  99. package/dist/components/docs/DocsTabPanel.js +1 -1
  100. package/dist/components/docs/DocsTabPanel.js.map +1 -1
  101. package/dist/components/graph/ConnectionPermissionsDialog.js +2 -2
  102. package/dist/components/graph/ConnectionPermissionsDialog.js.map +1 -1
  103. package/dist/components/graph/MeshDeployWizard.js +3 -3
  104. package/dist/components/graph/MeshDeployWizard.js.map +1 -1
  105. package/dist/components/graph/MeshRequestPanel.js +2 -2
  106. package/dist/components/graph/MeshRequestPanel.js.map +1 -1
  107. package/dist/components/idp-components/ComponentCard.js +5 -5
  108. package/dist/components/idp-components/ComponentCard.js.map +1 -1
  109. package/dist/components/idp-components/ComponentForm.js +1 -1
  110. package/dist/components/idp-components/ComponentForm.js.map +1 -1
  111. package/dist/components/idp-components/DependencyGraphTab.js +7 -7
  112. package/dist/components/idp-components/DependencyGraphTab.js.map +1 -1
  113. package/dist/components/logs/LogViewer.js +14 -14
  114. package/dist/components/logs/LogViewer.js.map +1 -1
  115. package/dist/components/notes/NoteCard.js +1 -1
  116. package/dist/components/notes/NoteCard.js.map +1 -1
  117. package/dist/components/notes/NoteEditor.js +2 -2
  118. package/dist/components/notes/NoteEditor.js.map +1 -1
  119. package/dist/components/notes/NoteList.js +1 -1
  120. package/dist/components/notes/NoteList.js.map +1 -1
  121. package/dist/components/notes/NotesTabPanel.js +1 -1
  122. package/dist/components/notes/NotesTabPanel.js.map +1 -1
  123. package/dist/components/scorecard/ScorecardEmptyState.js +1 -1
  124. package/dist/components/scorecard/ScorecardEmptyState.js.map +1 -1
  125. package/dist/components/scorecard/ScorecardPanel.js +3 -3
  126. package/dist/components/scorecard/ScorecardPanel.js.map +1 -1
  127. package/dist/components/security/SecurityScanList.js +11 -11
  128. package/dist/components/security/SecurityScanList.js.map +1 -1
  129. package/dist/components/sessions/SessionCard.js +26 -19
  130. package/dist/components/sessions/SessionCard.js.map +1 -1
  131. package/dist/components/sessions/SessionStatusBadge.js +7 -7
  132. package/dist/components/sessions/SessionStatusBadge.js.map +1 -1
  133. package/dist/components/shared/AgentSelector.js +4 -4
  134. package/dist/components/shared/AgentSelector.js.map +1 -1
  135. package/dist/components/shared/AgentSetupBanner.js +3 -3
  136. package/dist/components/shared/AgentSetupBanner.js.map +1 -1
  137. package/dist/components/shared/Card.js +4 -4
  138. package/dist/components/shared/Card.js.map +1 -1
  139. package/dist/components/shared/ErrorState.js +15 -9
  140. package/dist/components/shared/ErrorState.js.map +1 -1
  141. package/dist/components/shared/FilterDropdown.js +2 -2
  142. package/dist/components/shared/FilterDropdown.js.map +1 -1
  143. package/dist/components/shared/PageSkeleton.js +6 -6
  144. package/dist/components/shared/PageSkeleton.js.map +1 -1
  145. package/dist/components/shared/SearchInput.js +1 -1
  146. package/dist/components/shared/SearchInput.js.map +1 -1
  147. package/dist/components/shared/StatCard.js +7 -7
  148. package/dist/components/shared/StatCard.js.map +1 -1
  149. package/dist/components/templates/PublishTemplateForm.js +2 -2
  150. package/dist/components/templates/PublishTemplateForm.js.map +1 -1
  151. package/dist/components/templates/TemplateCard.js +5 -5
  152. package/dist/components/templates/TemplateCard.js.map +1 -1
  153. package/dist/components/templates/TemplateWizard.js +2 -2
  154. package/dist/components/templates/TemplateWizard.js.map +1 -1
  155. package/dist/components/tunnels/DomainVerificationBadge.js +4 -4
  156. package/dist/components/tunnels/DomainVerificationBadge.js.map +1 -1
  157. package/dist/components/tunnels/ProviderBadge.js +1 -1
  158. package/dist/components/tunnels/ProviderBadge.js.map +1 -1
  159. package/dist/components/tunnels/TunnelCard.js +16 -16
  160. package/dist/components/tunnels/TunnelCard.js.map +1 -1
  161. package/dist/components/tunnels/TunnelCreateWizard.js +1 -1
  162. package/dist/components/tunnels/TunnelCreateWizard.js.map +1 -1
  163. package/dist/components/tunnels/TunnelPublicAccessPanel.js +1 -1
  164. package/dist/components/tunnels/TunnelPublicAccessPanel.js.map +1 -1
  165. package/dist/components/tunnels/TunnelStatusBadge.js +4 -4
  166. package/dist/components/tunnels/TunnelStatusBadge.js.map +1 -1
  167. package/dist/components/vibecalendar/LogsDrawer.js +2 -2
  168. package/dist/components/vibecalendar/LogsDrawer.js.map +1 -1
  169. package/dist/components/vibecalendar/ScheduleTaskModal.js +5 -5
  170. package/dist/components/vibecalendar/ScheduleTaskModal.js.map +1 -1
  171. package/dist/components/vibecalendar/VibeCalAgendaView.js +2 -2
  172. package/dist/components/vibecalendar/VibeCalAgendaView.js.map +1 -1
  173. package/dist/components/vibecalendar/VibeCalDayView.js +3 -3
  174. package/dist/components/vibecalendar/VibeCalDayView.js.map +1 -1
  175. package/dist/components/vibecalendar/VibeCalMonthView.js +1 -1
  176. package/dist/components/vibecalendar/VibeCalMonthView.js.map +1 -1
  177. package/dist/components/vibecalendar/VibeCalWeekView.js +4 -4
  178. package/dist/components/vibecalendar/VibeCalWeekView.js.map +1 -1
  179. package/dist/components/vibecalendar/VibeCalendarHeader.js +4 -4
  180. package/dist/components/vibecalendar/VibeCalendarHeader.js.map +1 -1
  181. package/dist/components/vibecalendar/VibeTaskBlock.js +2 -2
  182. package/dist/components/vibecalendar/VibeTaskBlock.js.map +1 -1
  183. package/dist/components/vibecalendar/types.js +6 -6
  184. package/dist/components/vibecalendar/types.js.map +1 -1
  185. package/dist/components/vibedeck/VibeDeckButtonForm.js +9 -9
  186. package/dist/components/vibedeck/VibeDeckButtonForm.js.map +1 -1
  187. package/dist/components/vibedeck/VibeDeckButtonTile.js +6 -6
  188. package/dist/components/vibedeck/VibeDeckButtonTile.js.map +1 -1
  189. package/dist/components/vibedeck/VibeDeckExecutionLog.js +10 -10
  190. package/dist/components/vibedeck/VibeDeckExecutionLog.js.map +1 -1
  191. package/dist/components/vibedeck/VibeDeckForm.js +8 -8
  192. package/dist/components/vibedeck/VibeDeckForm.js.map +1 -1
  193. package/dist/components/vibedeck/VibeDeckGrid.js +2 -2
  194. package/dist/components/vibedeck/VibeDeckGrid.js.map +1 -1
  195. package/dist/components/vibedeck/VibeDeckPublicLinkPanel.js +2 -2
  196. package/dist/components/vibedeck/VibeDeckPublicLinkPanel.js.map +1 -1
  197. package/dist/components/vibedeck/VibeDeckSwitcher.js +8 -8
  198. package/dist/components/vibedeck/VibeDeckSwitcher.js.map +1 -1
  199. package/dist/components/vibes/VibeCard.js +22 -18
  200. package/dist/components/vibes/VibeCard.js.map +1 -1
  201. package/dist/components/vibes/VibeList.js +16 -12
  202. package/dist/components/vibes/VibeList.js.map +1 -1
  203. package/dist/pages/ActionsPage.js +4 -3
  204. package/dist/pages/ActionsPage.js.map +1 -1
  205. package/dist/pages/AgentDetailsPage.js +9 -9
  206. package/dist/pages/AgentDetailsPage.js.map +1 -1
  207. package/dist/pages/AgentsLandingPage.js +4 -4
  208. package/dist/pages/AgentsLandingPage.js.map +1 -1
  209. package/dist/pages/AgentsPage.js +14 -14
  210. package/dist/pages/AgentsPage.js.map +1 -1
  211. package/dist/pages/AnalyticsPage.js +8 -8
  212. package/dist/pages/AnalyticsPage.js.map +1 -1
  213. package/dist/pages/CatalogDetailPage.js +11 -11
  214. package/dist/pages/CatalogDetailPage.js.map +1 -1
  215. package/dist/pages/CatalogPage.js +12 -9
  216. package/dist/pages/CatalogPage.js.map +1 -1
  217. package/dist/pages/ComponentDetailPage.js +7 -7
  218. package/dist/pages/ComponentDetailPage.js.map +1 -1
  219. package/dist/pages/ComponentsPage.js +12 -9
  220. package/dist/pages/ComponentsPage.js.map +1 -1
  221. package/dist/pages/EnvTopologyPage.js +2 -2
  222. package/dist/pages/EnvTopologyPage.js.map +1 -1
  223. package/dist/pages/GettingStartedPage.js +12 -12
  224. package/dist/pages/GettingStartedPage.js.map +1 -1
  225. package/dist/pages/HealthPage.js +12 -12
  226. package/dist/pages/HealthPage.js.map +1 -1
  227. package/dist/pages/LogsPage.js +4 -4
  228. package/dist/pages/LogsPage.js.map +1 -1
  229. package/dist/pages/NoteDetailsPage.js +5 -5
  230. package/dist/pages/NoteDetailsPage.js.map +1 -1
  231. package/dist/pages/OverviewPage.js +74 -41
  232. package/dist/pages/OverviewPage.js.map +1 -1
  233. package/dist/pages/PluginsCatalogPage.js +6 -3
  234. package/dist/pages/PluginsCatalogPage.js.map +1 -1
  235. package/dist/pages/ScheduledJobDetailPage.js +7 -7
  236. package/dist/pages/ScheduledJobDetailPage.js.map +1 -1
  237. package/dist/pages/SessionDetailsPage.js +21 -21
  238. package/dist/pages/SessionDetailsPage.js.map +1 -1
  239. package/dist/pages/SessionsPage.js +20 -20
  240. package/dist/pages/SessionsPage.js.map +1 -1
  241. package/dist/pages/SettingsPage.js +64 -52
  242. package/dist/pages/SettingsPage.js.map +1 -1
  243. package/dist/pages/SharedSessionPage.js +21 -21
  244. package/dist/pages/SharedSessionPage.js.map +1 -1
  245. package/dist/pages/SharedVibeDeckPage.js +5 -5
  246. package/dist/pages/SharedVibeDeckPage.js.map +1 -1
  247. package/dist/pages/TargetDetailsPage.js +56 -65
  248. package/dist/pages/TargetDetailsPage.js.map +1 -1
  249. package/dist/pages/TargetsPage.js +4 -4
  250. package/dist/pages/TargetsPage.js.map +1 -1
  251. package/dist/pages/TemplateDetailPage.js +10 -10
  252. package/dist/pages/TemplateDetailPage.js.map +1 -1
  253. package/dist/pages/TemplatesPage.js +6 -5
  254. package/dist/pages/TemplatesPage.js.map +1 -1
  255. package/dist/pages/VibeAuditPage.js +3 -3
  256. package/dist/pages/VibeAuditPage.js.map +1 -1
  257. package/dist/pages/VibeCalendarPage.js +3 -3
  258. package/dist/pages/VibeCalendarPage.js.map +1 -1
  259. package/dist/pages/VibeDeckPage.js +3 -3
  260. package/dist/pages/VibeDeckPage.js.map +1 -1
  261. package/dist/pages/VibeDetailsPage.js +15 -15
  262. package/dist/pages/VibeDetailsPage.js.map +1 -1
  263. package/dist/pages/VibeTaskDetailPage.js +5 -5
  264. package/dist/pages/VibeTaskDetailPage.js.map +1 -1
  265. package/dist/pages/VibesPage.js +18 -18
  266. package/dist/pages/VibesPage.js.map +1 -1
  267. package/dist/pages/WebhooksPage.js +11 -11
  268. package/dist/pages/WebhooksPage.js.map +1 -1
  269. package/dist/pages/ai/AIBookmarksPage.js +1 -1
  270. package/dist/pages/ai/AIBookmarksPage.js.map +1 -1
  271. package/dist/pages/ai/AISessionDetailPage.js +6 -6
  272. package/dist/pages/ai/AISessionDetailPage.js.map +1 -1
  273. package/dist/pages/tunnels/AuditTab.js +1 -1
  274. package/dist/pages/tunnels/AuditTab.js.map +1 -1
  275. package/dist/pages/tunnels/DomainsTab.js +1 -1
  276. package/dist/pages/tunnels/DomainsTab.js.map +1 -1
  277. package/dist/pages/tunnels/ProvidersTab.js +1 -1
  278. package/dist/pages/tunnels/ProvidersTab.js.map +1 -1
  279. package/dist/pages/tunnels/SessionsTab.js +1 -1
  280. package/dist/pages/tunnels/SessionsTab.js.map +1 -1
  281. package/dist/pages/tunnels/TunnelDetailsPage.js +3 -3
  282. package/dist/pages/tunnels/TunnelDetailsPage.js.map +1 -1
  283. package/dist/pages/tunnels/UsageTab.js +1 -1
  284. package/dist/pages/tunnels/UsageTab.js.map +1 -1
  285. package/dist/pages/tunnels/tabs/TunnelDomainsTab.js +16 -16
  286. package/dist/pages/tunnels/tabs/TunnelDomainsTab.js.map +1 -1
  287. package/dist/pages/tunnels/tabs/TunnelOverviewTab.js +1 -1
  288. package/dist/pages/tunnels/tabs/TunnelOverviewTab.js.map +1 -1
  289. package/dist/pages/tunnels/tabs/TunnelSessionsTab.js +52 -49
  290. package/dist/pages/tunnels/tabs/TunnelSessionsTab.js.map +1 -1
  291. package/dist/pages/tunnels/tabs/TunnelSettingsTab.js +9 -9
  292. package/dist/pages/tunnels/tabs/TunnelSettingsTab.js.map +1 -1
  293. package/package.json +1 -1
@@ -50,11 +50,11 @@ function g() {
50
50
  children: g("agentsLanding.loadingPermissions", "Loading permissions...")
51
51
  })
52
52
  }) : x && (x.permissionStrings?.length ?? 0) > 0 && !k ? /* @__PURE__ */ d("div", {
53
- className: "flex items-center justify-center min-h-[400px]",
53
+ className: "flex items-center justify-center min-h-[400px] px-4",
54
54
  children: /* @__PURE__ */ f("div", {
55
- className: "text-center space-y-2",
55
+ className: "text-center space-y-2 max-w-sm",
56
56
  children: [/* @__PURE__ */ d("h2", {
57
- className: "text-lg font-semibold text-foreground",
57
+ className: "text-lg font-semibold tracking-tight text-text-primary",
58
58
  children: g("agentsLanding.accessDenied", "Access Denied")
59
59
  }), /* @__PURE__ */ d("p", {
60
60
  className: "text-sm text-text-secondary",
@@ -65,7 +65,7 @@ function g() {
65
65
  className: "flex flex-col h-full",
66
66
  children: [/* @__PURE__ */ d("div", {
67
67
  "data-tour": "agents-tabs",
68
- className: "border-b border-border-subtle px-4 pt-3",
68
+ className: "border-b border-border-subtle px-6 lg:px-8 pt-3",
69
69
  children: /* @__PURE__ */ d(m, {
70
70
  items: T,
71
71
  activeTabId: D,
@@ -1 +1 @@
1
- {"version":3,"file":"AgentsLandingPage.js","names":[],"sources":["../../src/pages/AgentsLandingPage.tsx"],"sourcesContent":["import { lazy, Suspense, useCallback, useEffect, useMemo } from 'react';\nimport { useSearchParams } from 'react-router-dom';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useFeatureFlags } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { VIBE_PERMISSIONS } from '../constants/permissions';\nimport { Tabs, type TabItem } from '@burdenoff/fe-libs/chrome';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { AgentsPage } from './AgentsPage';\nimport { TargetsPage } from './TargetsPage';\n\nconst AgentGraphPage = lazy(() =>\n import('./AgentGraphPage').then((module) => ({ default: module.AgentGraphPage }))\n);\n\n/**\n * Tabbed landing page for /vibecontrols/agents — exposed as \"Targets & Agents\"\n * in the UI. Combines the previously-separate Targets, Agents, and Agent Graph\n * screens into a single tabbed landing so users can move fluidly between\n * managing hosts (targets), installed agents, and the topology view without\n * losing context.\n *\n * Tab state is synced to `?tab=` so deep-links (e.g. `?tab=graph`) survive\n * reloads; the default tab (`agents`) omits the query param for clean URLs.\n */\nexport function AgentsLandingPage() {\n const { t } = useI18n();\n const tr = useCallback(\n (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n },\n [t]\n );\n const [params, setParams] = useSearchParams();\n const { hasPermission, isLoading: permissionsLoading, permissions } = usePermissions();\n const { isEnabled, isLoading: flagsLoading } = useFeatureFlags();\n\n // Graph tab is gated behind vibecontrols.agent-graph (default-off for end\n // users; super_admins keep the bypass unless they opt out). While flags\n // are loading we treat the tab as hidden so the tab bar doesn't flicker.\n const graphEnabled = !flagsLoading && isEnabled('vibecontrols.agent-graph');\n\n const tabs: TabItem[] = useMemo(() => {\n const list: TabItem[] = [\n { id: 'agents', label: tr('agentsLanding.tabAgents', 'Agents') },\n { id: 'targets', label: tr('agentsLanding.tabTargets', 'Targets') },\n ];\n if (graphEnabled) {\n list.push({ id: 'graph', label: tr('agentsLanding.tabGraph', 'Agent Graph') });\n }\n return list;\n }, [tr, graphEnabled]);\n\n const requestedTab = params.get('tab');\n // If the user lands on ?tab=graph while the flag is off (deep link, stale\n // bookmark, or super_admin opting out mid-session), fall back to the\n // default tab rather than rendering a hidden tab's content.\n const activeId =\n requestedTab && tabs.some((t) => t.id === requestedTab) ? requestedTab : tabs[0].id;\n\n // Strip ?tab=graph from the URL once we've established the flag is off so\n // refresh/share doesn't keep pointing at a tab the user can't see.\n useEffect(() => {\n if (requestedTab === 'graph' && !graphEnabled && !flagsLoading) {\n const next = new URLSearchParams(params);\n next.delete('tab');\n setParams(next, { replace: true });\n }\n }, [requestedTab, graphEnabled, flagsLoading, params, setParams]);\n\n const handleTabChange = (id: string) => {\n const next = new URLSearchParams(params);\n if (id === tabs[0].id) next.delete('tab');\n else next.set('tab', id);\n setParams(next, { replace: true });\n };\n\n const canAccessAnyTab =\n hasPermission(VIBE_PERMISSIONS.TARGET_LIST) ||\n hasPermission(VIBE_PERMISSIONS.AGENT_LIST) ||\n hasPermission(VIBE_PERMISSIONS.AGENT_READ);\n\n if (permissionsLoading || flagsLoading)\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <p className=\"text-text-secondary\">\n {tr('agentsLanding.loadingPermissions', 'Loading permissions...')}\n </p>\n </div>\n );\n // Only show access-denied once permissions are actually loaded; otherwise allow\n // the page to render so we don't gate on a not-yet-fetched permission set\n // (e.g. when the workspace token isn't issued and PermissionProvider stays idle).\n if (permissions && (permissions.permissionStrings?.length ?? 0) > 0 && !canAccessAnyTab)\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <div className=\"text-center space-y-2\">\n <h2 className=\"text-lg font-semibold text-foreground\">\n {tr('agentsLanding.accessDenied', 'Access Denied')}\n </h2>\n <p className=\"text-sm text-text-secondary\">\n {tr('agentsLanding.accessDeniedDesc', \"You don't have permission to access this page.\")}\n </p>\n </div>\n </div>\n );\n\n return (\n <div className=\"flex flex-col h-full\">\n <div data-tour=\"agents-tabs\" className=\"border-b border-border-subtle px-4 pt-3\">\n <Tabs\n items={tabs}\n activeTabId={activeId}\n onTabChange={handleTabChange}\n variant=\"underline\"\n />\n </div>\n <div className=\"flex-1 min-h-0 overflow-auto\">\n {activeId === 'targets' && <TargetsPage />}\n {activeId === 'agents' && <AgentsPage />}\n {activeId === 'graph' && graphEnabled && (\n <Suspense\n fallback={\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <p className=\"text-text-secondary\">\n {tr('agentsLanding.loadingGraph', 'Loading agent graph...')}\n </p>\n </div>\n }\n >\n <AgentGraphPage />\n </Suspense>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;AAUA,IAAM,IAAiB,QACrB,OAAO,uBAAoB,MAAM,OAAY,EAAE,SAAS,EAAO,gBAAgB,EAAE,CAClF;AAYD,SAAgB,IAAoB;CAClC,IAAM,EAAE,SAAM,GAAS,EACjB,IAAK,GACR,GAAa,MAA6B;EACzC,IAAM,IAAQ,EAAE,EAAI;AACpB,SAAO,MAAU,IAAM,IAAW;IAEpC,CAAC,EAAE,CACJ,EACK,CAAC,GAAQ,KAAa,GAAiB,EACvC,EAAE,kBAAe,WAAW,GAAoB,mBAAgB,GAAgB,EAChF,EAAE,cAAW,WAAW,MAAiB,GAAiB,EAK1D,IAAe,CAAC,KAAgB,EAAU,2BAA2B,EAErE,IAAkB,QAAc;EACpC,IAAM,IAAkB,CACtB;GAAE,IAAI;GAAU,OAAO,EAAG,2BAA2B,SAAS;GAAE,EAChE;GAAE,IAAI;GAAW,OAAO,EAAG,4BAA4B,UAAU;GAAE,CACpE;AAID,SAHI,KACF,EAAK,KAAK;GAAE,IAAI;GAAS,OAAO,EAAG,0BAA0B,cAAc;GAAE,CAAC,EAEzE;IACN,CAAC,GAAI,EAAa,CAAC,EAEhB,IAAe,EAAO,IAAI,MAAM,EAIhC,IACJ,KAAgB,EAAK,MAAM,MAAM,EAAE,OAAO,EAAa,GAAG,IAAe,EAAK,GAAG;AAInF,SAAgB;AACd,MAAI,MAAiB,WAAW,CAAC,KAAgB,CAAC,GAAc;GAC9D,IAAM,IAAO,IAAI,gBAAgB,EAAO;AAExC,GADA,EAAK,OAAO,MAAM,EAClB,EAAU,GAAM,EAAE,SAAS,IAAM,CAAC;;IAEnC;EAAC;EAAc;EAAc;EAAc;EAAQ;EAAU,CAAC;CAEjE,IAAM,KAAmB,MAAe;EACtC,IAAM,IAAO,IAAI,gBAAgB,EAAO;AAGxC,EAFI,MAAO,EAAK,GAAG,KAAI,EAAK,OAAO,MAAM,GACpC,EAAK,IAAI,OAAO,EAAG,EACxB,EAAU,GAAM,EAAE,SAAS,IAAM,CAAC;IAG9B,IACJ,EAAc,EAAiB,YAAY,IAC3C,EAAc,EAAiB,WAAW,IAC1C,EAAc,EAAiB,WAAW;AA2B5C,QAzBI,KAAsB,IAEtB,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,KAAD;GAAG,WAAU;aACV,EAAG,oCAAoC,yBAAyB;GAC/D,CAAA;EACA,CAAA,GAKN,MAAgB,EAAY,mBAAmB,UAAU,KAAK,KAAK,CAAC,IAEpE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,MAAD;IAAI,WAAU;cACX,EAAG,8BAA8B,gBAAgB;IAC/C,CAAA,EACL,kBAAC,KAAD;IAAG,WAAU;cACV,EAAG,kCAAkC,iDAAiD;IACrF,CAAA,CACA;;EACF,CAAA,GAIR,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GAAK,aAAU;GAAc,WAAU;aACrC,kBAAC,GAAD;IACE,OAAO;IACP,aAAa;IACb,aAAa;IACb,SAAQ;IACR,CAAA;GACE,CAAA,EACN,kBAAC,OAAD;GAAK,WAAU;aAAf;IACG,MAAa,aAAa,kBAAC,GAAD,EAAe,CAAA;IACzC,MAAa,YAAY,kBAAC,GAAD,EAAc,CAAA;IACvC,MAAa,WAAW,KACvB,kBAAC,GAAD;KACE,UACE,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,KAAD;OAAG,WAAU;iBACV,EAAG,8BAA8B,yBAAyB;OACzD,CAAA;MACA,CAAA;eAGR,kBAAC,GAAD,EAAkB,CAAA;KACT,CAAA;IAET;KACF"}
1
+ {"version":3,"file":"AgentsLandingPage.js","names":[],"sources":["../../src/pages/AgentsLandingPage.tsx"],"sourcesContent":["import { lazy, Suspense, useCallback, useEffect, useMemo } from 'react';\nimport { useSearchParams } from 'react-router-dom';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useFeatureFlags } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { VIBE_PERMISSIONS } from '../constants/permissions';\nimport { Tabs, type TabItem } from '@burdenoff/fe-libs/chrome';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { AgentsPage } from './AgentsPage';\nimport { TargetsPage } from './TargetsPage';\n\nconst AgentGraphPage = lazy(() =>\n import('./AgentGraphPage').then((module) => ({ default: module.AgentGraphPage }))\n);\n\n/**\n * Tabbed landing page for /vibecontrols/agents — exposed as \"Targets & Agents\"\n * in the UI. Combines the previously-separate Targets, Agents, and Agent Graph\n * screens into a single tabbed landing so users can move fluidly between\n * managing hosts (targets), installed agents, and the topology view without\n * losing context.\n *\n * Tab state is synced to `?tab=` so deep-links (e.g. `?tab=graph`) survive\n * reloads; the default tab (`agents`) omits the query param for clean URLs.\n */\nexport function AgentsLandingPage() {\n const { t } = useI18n();\n const tr = useCallback(\n (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n },\n [t]\n );\n const [params, setParams] = useSearchParams();\n const { hasPermission, isLoading: permissionsLoading, permissions } = usePermissions();\n const { isEnabled, isLoading: flagsLoading } = useFeatureFlags();\n\n // Graph tab is gated behind vibecontrols.agent-graph (default-off for end\n // users; super_admins keep the bypass unless they opt out). While flags\n // are loading we treat the tab as hidden so the tab bar doesn't flicker.\n const graphEnabled = !flagsLoading && isEnabled('vibecontrols.agent-graph');\n\n const tabs: TabItem[] = useMemo(() => {\n const list: TabItem[] = [\n { id: 'agents', label: tr('agentsLanding.tabAgents', 'Agents') },\n { id: 'targets', label: tr('agentsLanding.tabTargets', 'Targets') },\n ];\n if (graphEnabled) {\n list.push({ id: 'graph', label: tr('agentsLanding.tabGraph', 'Agent Graph') });\n }\n return list;\n }, [tr, graphEnabled]);\n\n const requestedTab = params.get('tab');\n // If the user lands on ?tab=graph while the flag is off (deep link, stale\n // bookmark, or super_admin opting out mid-session), fall back to the\n // default tab rather than rendering a hidden tab's content.\n const activeId =\n requestedTab && tabs.some((t) => t.id === requestedTab) ? requestedTab : tabs[0].id;\n\n // Strip ?tab=graph from the URL once we've established the flag is off so\n // refresh/share doesn't keep pointing at a tab the user can't see.\n useEffect(() => {\n if (requestedTab === 'graph' && !graphEnabled && !flagsLoading) {\n const next = new URLSearchParams(params);\n next.delete('tab');\n setParams(next, { replace: true });\n }\n }, [requestedTab, graphEnabled, flagsLoading, params, setParams]);\n\n const handleTabChange = (id: string) => {\n const next = new URLSearchParams(params);\n if (id === tabs[0].id) next.delete('tab');\n else next.set('tab', id);\n setParams(next, { replace: true });\n };\n\n const canAccessAnyTab =\n hasPermission(VIBE_PERMISSIONS.TARGET_LIST) ||\n hasPermission(VIBE_PERMISSIONS.AGENT_LIST) ||\n hasPermission(VIBE_PERMISSIONS.AGENT_READ);\n\n if (permissionsLoading || flagsLoading)\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <p className=\"text-text-secondary\">\n {tr('agentsLanding.loadingPermissions', 'Loading permissions...')}\n </p>\n </div>\n );\n // Only show access-denied once permissions are actually loaded; otherwise allow\n // the page to render so we don't gate on a not-yet-fetched permission set\n // (e.g. when the workspace token isn't issued and PermissionProvider stays idle).\n if (permissions && (permissions.permissionStrings?.length ?? 0) > 0 && !canAccessAnyTab)\n return (\n <div className=\"flex items-center justify-center min-h-[400px] px-4\">\n <div className=\"text-center space-y-2 max-w-sm\">\n <h2 className=\"text-lg font-semibold tracking-tight text-text-primary\">\n {tr('agentsLanding.accessDenied', 'Access Denied')}\n </h2>\n <p className=\"text-sm text-text-secondary\">\n {tr('agentsLanding.accessDeniedDesc', \"You don't have permission to access this page.\")}\n </p>\n </div>\n </div>\n );\n\n return (\n <div className=\"flex flex-col h-full\">\n <div data-tour=\"agents-tabs\" className=\"border-b border-border-subtle px-6 lg:px-8 pt-3\">\n <Tabs\n items={tabs}\n activeTabId={activeId}\n onTabChange={handleTabChange}\n variant=\"underline\"\n />\n </div>\n <div className=\"flex-1 min-h-0 overflow-auto\">\n {activeId === 'targets' && <TargetsPage />}\n {activeId === 'agents' && <AgentsPage />}\n {activeId === 'graph' && graphEnabled && (\n <Suspense\n fallback={\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <p className=\"text-text-secondary\">\n {tr('agentsLanding.loadingGraph', 'Loading agent graph...')}\n </p>\n </div>\n }\n >\n <AgentGraphPage />\n </Suspense>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;AAUA,IAAM,IAAiB,QACrB,OAAO,uBAAoB,MAAM,OAAY,EAAE,SAAS,EAAO,gBAAgB,EAAE,CAClF;AAYD,SAAgB,IAAoB;CAClC,IAAM,EAAE,SAAM,GAAS,EACjB,IAAK,GACR,GAAa,MAA6B;EACzC,IAAM,IAAQ,EAAE,EAAI;AACpB,SAAO,MAAU,IAAM,IAAW;IAEpC,CAAC,EAAE,CACJ,EACK,CAAC,GAAQ,KAAa,GAAiB,EACvC,EAAE,kBAAe,WAAW,GAAoB,mBAAgB,GAAgB,EAChF,EAAE,cAAW,WAAW,MAAiB,GAAiB,EAK1D,IAAe,CAAC,KAAgB,EAAU,2BAA2B,EAErE,IAAkB,QAAc;EACpC,IAAM,IAAkB,CACtB;GAAE,IAAI;GAAU,OAAO,EAAG,2BAA2B,SAAS;GAAE,EAChE;GAAE,IAAI;GAAW,OAAO,EAAG,4BAA4B,UAAU;GAAE,CACpE;AAID,SAHI,KACF,EAAK,KAAK;GAAE,IAAI;GAAS,OAAO,EAAG,0BAA0B,cAAc;GAAE,CAAC,EAEzE;IACN,CAAC,GAAI,EAAa,CAAC,EAEhB,IAAe,EAAO,IAAI,MAAM,EAIhC,IACJ,KAAgB,EAAK,MAAM,MAAM,EAAE,OAAO,EAAa,GAAG,IAAe,EAAK,GAAG;AAInF,SAAgB;AACd,MAAI,MAAiB,WAAW,CAAC,KAAgB,CAAC,GAAc;GAC9D,IAAM,IAAO,IAAI,gBAAgB,EAAO;AAExC,GADA,EAAK,OAAO,MAAM,EAClB,EAAU,GAAM,EAAE,SAAS,IAAM,CAAC;;IAEnC;EAAC;EAAc;EAAc;EAAc;EAAQ;EAAU,CAAC;CAEjE,IAAM,KAAmB,MAAe;EACtC,IAAM,IAAO,IAAI,gBAAgB,EAAO;AAGxC,EAFI,MAAO,EAAK,GAAG,KAAI,EAAK,OAAO,MAAM,GACpC,EAAK,IAAI,OAAO,EAAG,EACxB,EAAU,GAAM,EAAE,SAAS,IAAM,CAAC;IAG9B,IACJ,EAAc,EAAiB,YAAY,IAC3C,EAAc,EAAiB,WAAW,IAC1C,EAAc,EAAiB,WAAW;AA2B5C,QAzBI,KAAsB,IAEtB,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,KAAD;GAAG,WAAU;aACV,EAAG,oCAAoC,yBAAyB;GAC/D,CAAA;EACA,CAAA,GAKN,MAAgB,EAAY,mBAAmB,UAAU,KAAK,KAAK,CAAC,IAEpE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,MAAD;IAAI,WAAU;cACX,EAAG,8BAA8B,gBAAgB;IAC/C,CAAA,EACL,kBAAC,KAAD;IAAG,WAAU;cACV,EAAG,kCAAkC,iDAAiD;IACrF,CAAA,CACA;;EACF,CAAA,GAIR,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GAAK,aAAU;GAAc,WAAU;aACrC,kBAAC,GAAD;IACE,OAAO;IACP,aAAa;IACb,aAAa;IACb,SAAQ;IACR,CAAA;GACE,CAAA,EACN,kBAAC,OAAD;GAAK,WAAU;aAAf;IACG,MAAa,aAAa,kBAAC,GAAD,EAAe,CAAA;IACzC,MAAa,YAAY,kBAAC,GAAD,EAAc,CAAA;IACvC,MAAa,WAAW,KACvB,kBAAC,GAAD;KACE,UACE,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,KAAD;OAAG,WAAU;iBACV,EAAG,8BAA8B,yBAAyB;OACzD,CAAA;MACA,CAAA;eAGR,kBAAC,GAAD,EAAkB,CAAA;KACT,CAAA;IAET;KACF"}
@@ -406,23 +406,23 @@ function at() {
406
406
  offline: Y.filter((e) => e.status !== "ACTIVE").length
407
407
  };
408
408
  return st ? /* @__PURE__ */ m(g, {
409
- className: "space-y-6 p-4 sm:p-6 bg-bg-surface min-h-full",
409
+ className: "space-y-6 px-6 lg:px-8 py-6 bg-bg-canvas min-h-full",
410
410
  children: /* @__PURE__ */ m(ve, { message: b("agentsPage.loadingPermissions", "Loading permissions...") })
411
411
  }) : ct && ct.permissionStrings?.length > 0 && !dt ? /* @__PURE__ */ m(g, {
412
- className: "space-y-6 p-4 sm:p-6 bg-bg-surface min-h-full",
412
+ className: "space-y-6 px-6 lg:px-8 py-6 bg-bg-canvas min-h-full",
413
413
  children: /* @__PURE__ */ m(tt, {
414
414
  title: b("agentsPage.accessDeniedTitle", "Access Denied"),
415
415
  description: b("agentsPage.accessDeniedDescription", "You don't have permission to view agents. Contact your workspace administrator for access.")
416
416
  })
417
417
  }) : Jt && Y.length === 0 ? /* @__PURE__ */ m(g, {
418
- className: "space-y-6 p-4 sm:p-6 bg-bg-surface min-h-full",
418
+ className: "space-y-6 px-6 lg:px-8 py-6 bg-bg-canvas min-h-full",
419
419
  children: /* @__PURE__ */ m(ye, {
420
420
  title: b("agentsPage.failedLoad", "Failed to load agents"),
421
421
  message: Jt.message,
422
422
  onRetry: () => G()
423
423
  })
424
424
  }) : /* @__PURE__ */ h(g, {
425
- className: "space-y-6 p-4 sm:p-6 bg-bg-surface min-h-full",
425
+ className: "space-y-6 px-6 lg:px-8 py-6 bg-bg-canvas min-h-full",
426
426
  children: [
427
427
  /* @__PURE__ */ m(_e, {
428
428
  title: b("agentsPage.title", "Agents"),
@@ -438,7 +438,7 @@ function at() {
438
438
  G(), Zt();
439
439
  },
440
440
  disabled: W || K.loading,
441
- className: "flex items-center justify-center gap-2 px-3 py-2 max-md:min-h-11 max-md:min-w-11 text-text-secondary hover:bg-bg-sunken rounded-md transition-colors disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
441
+ className: "flex items-center justify-center gap-2 px-3 py-2 max-md:min-h-11 max-md:min-w-11 text-text-secondary hover:bg-bg-sunken hover:text-text-primary rounded-button transition-colors disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
442
442
  "aria-label": b("agentsPage.refreshTitle", "Refresh agents"),
443
443
  title: b("agentsPage.refreshTitle", "Refresh agents"),
444
444
  children: /* @__PURE__ */ m(Be, {
@@ -452,13 +452,13 @@ function at() {
452
452
  children: /* @__PURE__ */ h("button", {
453
453
  type: "button",
454
454
  onClick: Z,
455
- className: "flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bgHover transition-colors disabled:opacity-50 disabled:cursor-not-allowed",
455
+ className: "flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
456
456
  children: [/* @__PURE__ */ m(p, { className: "size-4" }), b("agentsPage.addAgent", "Add Agent")]
457
457
  })
458
458
  }) : /* @__PURE__ */ h("button", {
459
459
  type: "button",
460
460
  onClick: Z,
461
- className: "flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bgHover transition-colors",
461
+ className: "flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-all duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
462
462
  children: [/* @__PURE__ */ m(p, { className: "size-4" }), b("agentsPage.addAgent", "Add Agent")]
463
463
  })]
464
464
  })
@@ -481,13 +481,13 @@ function at() {
481
481
  placeholder: b("agentsPage.searchPlaceholder", "Search agents..."),
482
482
  className: "flex-1 sm:w-64 sm:flex-none"
483
483
  }), /* @__PURE__ */ h("div", {
484
- className: "flex items-center gap-1 bg-bg-sunken rounded-md p-1 flex-shrink-0",
484
+ className: "flex items-center gap-1 bg-bg-sunken rounded-button p-1 flex-shrink-0 border border-border-subtle",
485
485
  children: [/* @__PURE__ */ m("button", {
486
486
  type: "button",
487
487
  onClick: () => {
488
488
  gt("grid"), R(/* @__PURE__ */ new Set());
489
489
  },
490
- className: `flex items-center justify-center p-1.5 sm:p-2 max-md:min-h-11 max-md:min-w-11 rounded focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus ${T === "grid" ? "bg-bg-surface shadow-sm" : "hover:bg-bg-surface/50"}`,
490
+ className: `flex items-center justify-center p-1.5 sm:p-2 max-md:min-h-11 max-md:min-w-11 rounded focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus ${T === "grid" ? "bg-[var(--color-accent-soft)] text-action-primary-bg" : "text-text-secondary hover:bg-bg-surface/60"}`,
491
491
  "aria-label": b("agentsPage.gridViewTitle", "Grid view"),
492
492
  "aria-pressed": T === "grid",
493
493
  title: b("agentsPage.gridViewTitle", "Grid view"),
@@ -500,7 +500,7 @@ function at() {
500
500
  onClick: () => {
501
501
  gt("list"), R(/* @__PURE__ */ new Set());
502
502
  },
503
- className: `flex items-center justify-center p-1.5 sm:p-2 max-md:min-h-11 max-md:min-w-11 rounded focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus ${T === "list" ? "bg-bg-surface shadow-sm" : "hover:bg-bg-surface/50"}`,
503
+ className: `flex items-center justify-center p-1.5 sm:p-2 max-md:min-h-11 max-md:min-w-11 rounded focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus ${T === "list" ? "bg-[var(--color-accent-soft)] text-action-primary-bg" : "text-text-secondary hover:bg-bg-surface/60"}`,
504
504
  "aria-label": b("agentsPage.listViewTitle", "List view"),
505
505
  "aria-pressed": T === "list",
506
506
  title: b("agentsPage.listViewTitle", "List view"),
@@ -547,7 +547,7 @@ function at() {
547
547
  })
548
548
  }),
549
549
  dn && /* @__PURE__ */ h("div", {
550
- className: "flex flex-col sm:flex-row items-start sm:items-center gap-3 px-4 py-2 bg-action-primary-bg/10 border border-action-primary-bg/20 rounded-card",
550
+ className: "flex flex-col sm:flex-row items-start sm:items-center gap-3 px-4 py-2.5 bg-[var(--color-accent-soft)] border border-border-subtle rounded-card",
551
551
  children: [/* @__PURE__ */ m("span", {
552
552
  className: "text-sm font-medium text-text-primary",
553
553
  children: b("agentsPage.selectedCount", "{{count}} agents selected", { count: L.size })
@@ -557,7 +557,7 @@ function at() {
557
557
  type: "button",
558
558
  onClick: () => B(!0),
559
559
  disabled: z,
560
- className: "flex items-center gap-1.5 px-3 py-1.5 max-md:min-h-11 text-xs font-medium text-status-error-text bg-status-error-bg-subtle border border-status-error-border rounded-md hover:bg-status-error-bg-subtle/80 transition-colors disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
560
+ className: "flex items-center gap-1.5 px-3 py-1.5 max-md:min-h-11 text-xs font-medium text-status-error-text bg-status-error-bg-subtle border border-status-error-border rounded-button hover:bg-status-error-bg-subtle/80 transition-colors disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
561
561
  children: [
562
562
  /* @__PURE__ */ m(Ve, {
563
563
  "aria-hidden": "true",
@@ -643,13 +643,13 @@ function at() {
643
643
  /* @__PURE__ */ h("button", {
644
644
  type: "button",
645
645
  onClick: Z,
646
- className: "flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bgHover transition-colors",
646
+ className: "flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-all duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
647
647
  children: [/* @__PURE__ */ m(p, { className: "size-4" }), b("agentsPage.addAgent", "Add Agent")]
648
648
  }),
649
649
  /* @__PURE__ */ m("button", {
650
650
  type: "button",
651
651
  onClick: () => v("/getting-started"),
652
- className: "flex items-center gap-2 px-4 py-2 border border-border-default text-text-primary rounded-md hover:bg-bg-sunken transition-colors text-sm",
652
+ className: "flex items-center gap-2 px-4 py-2 border border-border-subtle bg-bg-surface text-text-primary rounded-button hover:bg-bg-sunken hover:border-border-strong transition-all duration-200 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
653
653
  children: b("agentsPage.setupGuide", "Setup guide")
654
654
  }),
655
655
  /* @__PURE__ */ m(Ee, { prompt: "What is an Agent in VibeControls and how do I add one?" })
@@ -1 +1 @@
1
- {"version":3,"file":"AgentsPage.js","names":[],"sources":["../../src/pages/AgentsPage.tsx"],"sourcesContent":["import { useState, useMemo, useRef, useCallback, useEffect } from 'react';\nimport { useSearchParams } from 'react-router-dom';\nimport { Plus, Bot, Grid, List, RefreshCw, Trash2, CheckSquare, XSquare } from 'lucide-react';\nimport { useEventBus } from '@burdenoff/fe-libs/shared/events';\nimport { MotionPage, MotionList, MotionItem } from '@burdenoff/fe-libs/shared/motion';\nimport { AnimatePresence } from 'framer-motion';\nimport {\n Button,\n Input,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n} from '@burdenoff/fe-libs/ui';\nimport { AgentCard, AgentList, AgentForm } from '@/components/agents';\nimport { EntityTagPicker } from '@/components/shared/EntityTagPicker';\nimport { PageSkeleton } from '@/components/shared/PageSkeleton';\nimport { useDraftEntityTags } from '@/hooks/useDraftEntityTags';\nimport type { Agent } from '@/components/agents';\nimport {\n PageHeader,\n SearchInput,\n FilterDropdown,\n TagFilter,\n EmptyState,\n LoadingState,\n ErrorState,\n AgentSetupBanner,\n QuotaExhaustedDialog,\n GetHelpButton,\n ConfirmDialog,\n} from '@/components/shared';\nimport { notify } from '@/lib/toast';\nimport { useField } from '@/shared/hooks/useField';\nimport { useTr } from '@/shared/hooks/useTr';\nimport { useVibeControls, useVibeNavigate } from '@/providers/VibeControlsProvider';\nimport {\n useListAgentsQuery,\n useVibecontrolsAgentHealthProbesQuery,\n useVibecontrolsAgentHealthProbesLazyQuery,\n useVibecontrolsAgentHealthProbeStreamSubscription,\n useCreateAgentMutation,\n useUpdateAgentMutation,\n useDeleteAgentMutation,\n useAgentHeartbeatMutation,\n useDeactivateAgentMutation,\n useCreateTargetMutation,\n useAddAgentFromTunnelMutation,\n useStartVibecontrolsAgentMutation,\n useStopVibecontrolsAgentMutation,\n useSetupAgentGatewayAuthMutation,\n useListTargetsQuery,\n} from '@/generated/wspace-operations';\nimport { PermissionButton, AccessDenied } from '@burdenoff/fe-libs/shared/components';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { VIBE_PERMISSIONS } from '@/constants/permissions';\nimport { deriveAgentStatus, type AgentHealthProbe } from '@/utils/agentStatus';\nimport {\n isQuotaExhaustedError,\n markQuotaHandledLocally,\n getQuotaDialogContent,\n type QuotaKind,\n} from '@/utils/quotaUtils';\nimport { useCachedInitialData } from '@/hooks/useCachedInitialData';\nimport { useVibeControlsStore } from '@/store/vibeControlsStore';\n\ntype ViewMode = 'grid' | 'list';\n\n/**\n * Agents management page with filtering, search, and CRUD operations\n */\nexport function AgentsPage() {\n const { onAgentSelect, workspaceId } = useVibeControls();\n const handleNavigate = useVibeNavigate();\n const bus = useEventBus();\n const { hasPermission, isLoading: permissionsLoading, permissions } = usePermissions();\n const tr = useTr();\n\n const statusOptions = [\n { value: 'all', label: tr('agentsPage.statusAll', 'All Statuses') },\n { value: 'ACTIVE', label: tr('agentsPage.statusActive', 'Active') },\n { value: 'STOPPED', label: tr('agentsPage.statusStopped', 'Stopped') },\n { value: 'OFFLINE', label: tr('agentsPage.statusOffline', 'Offline') },\n ];\n\n const platformOptions = [\n { value: 'all', label: tr('agentsPage.platformAll', 'All Platforms') },\n { value: 'darwin', label: tr('agentsPage.platformMac', 'macOS') },\n { value: 'linux', label: tr('agentsPage.platformLinux', 'Linux') },\n { value: 'windows', label: tr('agentsPage.platformWindows', 'Windows') },\n ];\n\n // Check permissions\n const canListAgents = hasPermission(VIBE_PERMISSIONS.AGENT_LIST);\n\n const [searchQuery, setSearchQuery] = useState('');\n const [statusFilter, setStatusFilter] = useState('all');\n const [platformFilter, setPlatformFilter] = useState('all');\n const [tagFilter, setTagFilter] = useState('all');\n const [viewMode, setViewMode] = useState<ViewMode>('grid');\n const [showCreateForm, setShowCreateForm] = useState(false);\n const [isCreating, setIsCreating] = useState(false);\n\n // Auto-open Add Agent modal when arriving via /agents/new or /agents?create=true\n // (dashboard widgets, deep links). Strip the param after opening so the back\n // button returns to the previous page instead of re-opening.\n const [searchParams, setSearchParams] = useSearchParams();\n useEffect(() => {\n if (searchParams.get('create') === 'true') {\n setShowCreateForm(true);\n const next = new URLSearchParams(searchParams);\n next.delete('create');\n setSearchParams(next, { replace: true });\n }\n }, [searchParams, setSearchParams]);\n const [editTunnelAgent, setEditTunnelAgent] = useState<Agent | null>(null);\n const [isSavingTunnel, setIsSavingTunnel] = useState(false);\n // Per-agent \"connecting\" overlay shown after Edit Tunnel save while the\n // backend pushes OAuth creds → agent finalize() → /health/ready flips to\n // ok. State lives in the global store (vibeControlsStore) so the overlay\n // survives navigation to AgentDetailsPage and back — the documented\n // recovery path navigates into the details page so pushAgentGatewayAuth\n // auto-recovery can fire, and storing the set in local React state used\n // to discard it on unmount, causing a CONNECTING → DEGRADED → ACTIVE\n // flicker on return.\n const connectingAgents = useVibeControlsStore((s) => s.connectingAgents);\n const markAgentConnecting = useVibeControlsStore((s) => s.markAgentConnecting);\n const clearAgentConnecting = useVibeControlsStore((s) => s.clearAgentConnecting);\n const connectingAgentIds = useMemo(\n () => new Set(Object.keys(connectingAgents)),\n [connectingAgents]\n );\n\n // 30s safety net — if a probe never reports healthy, drop the overlay\n // so the user eventually sees the real DEGRADED state.\n const connectingTimersRef = useRef<Map<string, ReturnType<typeof setTimeout>>>(new Map());\n useEffect(() => {\n const timers = connectingTimersRef.current;\n for (const agentId of Object.keys(connectingAgents)) {\n if (timers.has(agentId)) continue;\n const timer = setTimeout(() => {\n clearAgentConnecting(agentId);\n timers.delete(agentId);\n }, 30_000);\n timers.set(agentId, timer);\n }\n // Drop timers for agents no longer marked connecting.\n for (const [agentId, timer] of timers.entries()) {\n if (!(agentId in connectingAgents)) {\n clearTimeout(timer);\n timers.delete(agentId);\n }\n }\n return () => {\n for (const timer of timers.values()) clearTimeout(timer);\n timers.clear();\n };\n }, [connectingAgents, clearAgentConnecting]);\n const [disconnectConfirmAgent, setDisconnectConfirmAgent] = useState<Agent | null>(null);\n const [isDisconnecting, setIsDisconnecting] = useState(false);\n const tunnelUrlRef = useRef<HTMLInputElement>(null);\n const tunnelUrlField = useField({\n hint: tr(\n 'agentsPage.tunnelUrlHint',\n 'Leave empty to clear. Agents with auto-tunnel report this automatically on startup.'\n ),\n });\n\n // Bulk selection state\n const [selectedAgentIds, setSelectedAgentIds] = useState<Set<string>>(new Set());\n const [bulkLoading, setBulkLoading] = useState(false);\n const [bulkDeleteConfirmOpen, setBulkDeleteConfirmOpen] = useState(false);\n // Per-kind so the dialog copy (\"Agent Quota Reached\" vs \"Target Quota\n // Reached\") matches what the user just tried to create. The page hosts\n // both the agent-create form and the inline target-create row, so a\n // single boolean would mislabel one of them.\n const [quotaExhaustedKind, setQuotaExhaustedKind] = useState<QuotaKind | null>(null);\n\n const [createAgent] = useCreateAgentMutation({ refetchQueries: ['ListAgents'] });\n const { setDraftTagIds: setAgentDraftTagIds, flushTags: flushAgentTags } =\n useDraftEntityTags('VIBECONTROLS_AGENT');\n const [addAgentFromTunnel] = useAddAgentFromTunnelMutation({ refetchQueries: ['ListAgents'] });\n const [agentHeartbeat] = useAgentHeartbeatMutation({ refetchQueries: ['ListAgents'] });\n const [deactivateAgent] = useDeactivateAgentMutation({ refetchQueries: ['ListAgents'] });\n const [startAgentMutation] = useStartVibecontrolsAgentMutation({\n refetchQueries: ['ListAgents'],\n });\n const [stopAgentMutation] = useStopVibecontrolsAgentMutation({ refetchQueries: ['ListAgents'] });\n const [setupGatewayAuth] = useSetupAgentGatewayAuthMutation({\n refetchQueries: ['ListAgents'],\n });\n\n const { refetch: refetchInitialData } = useCachedInitialData();\n const {\n data: targetsData,\n loading: targetsLoading,\n refetch: refetchTargets,\n } = useListTargetsQuery({\n // Bound the list: this query hydrates per-target agents, so leaving it\n // unbounded was the heaviest contributor to slow Agents/Targets loads (the\n // 25s OPERATION_TIMEOUT class). AgentsPage only needs targets for the\n // \"Add Agent\" picker / hasTargets flag, so a page of 50 is plenty.\n variables: { workspaceId: workspaceId ?? '', limit: 50, offset: 0 },\n skip: !workspaceId,\n fetchPolicy: 'cache-and-network',\n });\n const targetsList = targetsData?.targets?.items ?? [];\n const hasTargets = targetsList.length > 0;\n const [updateAgent] = useUpdateAgentMutation({ refetchQueries: ['ListAgents'] });\n const [deleteAgent] = useDeleteAgentMutation({ refetchQueries: ['ListAgents'] });\n // refetchQueries + awaitRefetchQueries: the inline create flow used to fire\n // refetchInitialData() without awaiting it, so AgentForm closed and\n // re-rendered against stale `cachedTargets` — the new target only appeared\n // on full page reload. Awaiting the underlying queries here makes the\n // mutation resolve only after the initial-data + targets caches are fresh.\n const [createTargetMutation] = useCreateTargetMutation({\n refetchQueries: ['VibeControlsInitialData', 'ListTargets'],\n awaitRefetchQueries: true,\n });\n\n /**\n * Inline DIRECT target creation callback for the AgentForm. Returns\n * the newly created target so the form can auto-select it.\n */\n const handleInlineCreateTarget = useCallback(\n async (targetName: string) => {\n if (!workspaceId || !targetName.trim()) return null;\n try {\n // errorPolicy: 'all' (shell Apollo client) puts server errors on\n // `result.error` instead of rejecting — read both paths so quota\n // errors don't silently fall back to the generic inline copy.\n const result = await createTargetMutation({\n variables: {\n workspaceId,\n input: {\n type: 'DIRECT',\n name: targetName.trim(),\n },\n },\n });\n if (result.error && isQuotaExhaustedError(result.error)) {\n // Open the upgrade-your-plan modal at this level and re-throw\n // so AgentForm's catch sees a quota error rather than a\n // silent null (which it would otherwise render as a vague\n // \"Failed to create target. Please try again.\" inline string).\n markQuotaHandledLocally();\n setQuotaExhaustedKind('target');\n throw result.error;\n }\n const created = result.data?.createTarget;\n if (!created) return null;\n refetchInitialData();\n void refetchTargets();\n return {\n id: created.id,\n name: created.name,\n type: created.type ?? 'DIRECT',\n status: created.status ?? 'unknown',\n };\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n markQuotaHandledLocally();\n setQuotaExhaustedKind('target');\n throw err;\n }\n return null;\n }\n },\n [createTargetMutation, refetchInitialData, refetchTargets, workspaceId]\n );\n\n // Fetch agents from GraphQL API\n // Skip until a workspace is selected: the resolver requires\n // x-workspace-id and throws \"Workspace context required\" when fired\n // before the auth provider has resolved a workspace (URL with ?org but\n // no &workspace, post-context-switcher self-heal, etc.). Without this\n // skip, the page lands on \"Failed to load agents\" and never recovers\n // because Apollo's errorPolicy: 'all' returns the error and doesn't\n // auto-refetch when workspaceId becomes available.\n const { data, loading, error, refetch } = useListAgentsQuery({\n variables: {\n filter: {\n isActive: statusFilter === 'ACTIVE' ? true : statusFilter === 'OFFLINE' ? false : undefined,\n platform: platformFilter !== 'all' ? platformFilter : undefined,\n },\n pagination: { limit: 100 },\n },\n fetchPolicy: 'cache-first',\n skip: !workspaceId,\n });\n\n // Live deep-health probe — backend hits /health/ready on every agent and\n // returns per-agent { healthy, errorMessage }. Polled every 30s so stale\n // tunnels (cloudflared dead, agent crashed but `isActive` still true)\n // flip the badge to DEGRADED before users hit \"create session\" against\n // an unreachable agent.\n // Initial query primes the cache; the subscription below replaces the\n // prior 30s pollInterval so badges flip to/from DEGRADED in <1s.\n // Two probe sources combined:\n //\n // 1) `useVibecontrolsAgentHealthProbesQuery` — auto-fires on mount and\n // primes `probesById` with a complete probe array in one round-trip.\n // Without this every fresh mount shows REGISTERING until the first\n // subscription event arrives, which can be many seconds when the\n // graphql-ws upgrade is slow (browsers serialize the WS handshake\n // behind the in-flight Apollo HTTP queue). The query fetches once\n // on mount AND every 30s as a fallback for environments where the\n // subscription transport is unavailable.\n //\n // 2) `useVibecontrolsAgentHealthProbeStreamSubscription` — server pushes\n // the full probe array on every change (sub-second updates) plus a\n // 30s heartbeat. When the stream is live this overrides the query\n // result in `probesById` (newest wins).\n //\n // The lazy variant is kept solely so the Refresh button can force an\n // immediate fetch without changing the auto-fired hook's variables.\n // Defer the probe query + the WS subscription until after first paint so the\n // graphql-ws handshake + probe fetch leave the critical path. The agent list\n // renders immediately (deriveAgentStatus shows REGISTERING while probesLoading)\n // and statuses stream in a tick later.\n const [probesDeferred, setProbesDeferred] = useState(false);\n useEffect(() => {\n const t = window.setTimeout(() => setProbesDeferred(true), 0);\n return () => window.clearTimeout(t);\n }, []);\n const probeQuery = useVibecontrolsAgentHealthProbesQuery({\n fetchPolicy: 'cache-and-network',\n nextFetchPolicy: 'cache-first',\n notifyOnNetworkStatusChange: true,\n skip: !workspaceId || !probesDeferred,\n // WS subscription is the live source; this poll is just a fallback for\n // environments without WS, so 60s (was 30s) is plenty and halves the load.\n pollInterval: 60_000,\n });\n const [refetchProbes] = useVibecontrolsAgentHealthProbesLazyQuery({\n fetchPolicy: 'network-only',\n });\n const { data: probeStreamData } = useVibecontrolsAgentHealthProbeStreamSubscription({\n skip: !workspaceId || !probesDeferred,\n });\n // True when no probe data has been delivered for this mount cycle yet.\n // Used by deriveAgentStatus to show REGISTERING for agents we have not\n // yet probed (instead of optimistically ACTIVE).\n const probesLoading = probeQuery.data === undefined && probeStreamData === undefined;\n const probesById = useMemo(() => {\n const m = new Map<string, AgentHealthProbe>();\n // Subscription stream wins over the initial query — strictly newer.\n const probes =\n probeStreamData?.vibecontrolsAgentHealthProbeStream ??\n probeQuery.data?.vibecontrolsAgentHealthProbes ??\n [];\n for (const p of probes) {\n m.set(p.agentId, {\n agentId: p.agentId,\n healthy: p.healthy,\n errorMessage: p.errorMessage,\n });\n }\n return m;\n }, [probeQuery.data, probeStreamData]);\n\n // Map GraphQL response to Agent type\n const agents: Agent[] = useMemo(() => {\n const rawAgents = data?.vibecontrolsAgents ?? [];\n return rawAgents.map((agent: NonNullable<typeof rawAgents>[number]) => ({\n id: agent.id,\n workspaceId: agent.workspaceId,\n name: agent.name,\n hostname: agent.hostname,\n platform: agent.platform,\n architecture: agent.architecture,\n version: agent.version,\n isActive: agent.isActive,\n status: connectingAgentIds.has(agent.id)\n ? ('CONNECTING' as const)\n : deriveAgentStatus(\n {\n id: agent.id,\n isActive: agent.isActive,\n metadata: agent.metadata as Record<string, unknown> | null,\n },\n probesById,\n { probesLoading }\n ),\n // Prefer the live health probe error. When the live probe didn't run\n // (or returned ok) but the agent's most-recent gateway-auth push\n // failed, surface that instead — otherwise the only signal of a\n // failed onboarding is a yellow Degraded badge with no hover text.\n // `pending` is hidden: it just means the async finalize is still\n // running and the badge already shows the right transient state.\n healthError:\n probesById.get(agent.id)?.errorMessage ??\n (agent.lastGatewayAuthPushStatus === 'failed'\n ? (agent.lastGatewayAuthPushError ?? 'Gateway-auth push failed')\n : null),\n lastHeartbeat: agent.lastHeartbeat || null,\n tunnelUrl: agent.tunnelUrl ?? null,\n tunnelStatus: agent.tunnelStatus ?? null,\n configuration: (agent.configuration as Record<string, unknown>) || {},\n capabilities: Array.isArray(agent.capabilities)\n ? (agent.capabilities as string[])\n : typeof agent.capabilities === 'object' && agent.capabilities !== null\n ? (() => {\n const obj = agent.capabilities as Record<string, unknown>;\n const keys = Object.keys(obj);\n // When capabilities was serialized from an array, keys are numeric (\"0\",\"1\",\"2\")\n // In that case, extract values (the actual capability names) instead of keys\n const hasNumericKeys = keys.length > 0 && keys.every((k) => /^\\d+$/.test(k));\n return hasNumericKeys ? (Object.values(obj) as string[]) : (keys as string[]);\n })()\n : [],\n metadata: (agent.metadata as Record<string, unknown>) || {},\n createdAt: agent.createdAt,\n updatedAt: agent.updatedAt,\n tagAssignments: (agent.tagAssignments ?? []).map(\n (a: NonNullable<typeof agent.tagAssignments>[number]) => ({\n id: a.id,\n tagId: a.tagId,\n tag: a.tagInfo\n ? {\n id: a.tagInfo.id,\n key: a.tagInfo.key ?? null,\n label: a.tagInfo.label ?? null,\n color: a.tagInfo.color ?? null,\n }\n : null,\n })\n ),\n }));\n }, [data?.vibecontrolsAgents, probesById, probesLoading, connectingAgentIds]);\n\n // Clear CONNECTING overlay only when ALL of these hold:\n // 1. The live probe reports `healthy: true` (deep-health probe, which\n // already gates on session.ok + tunnel.ok + ttyd.ok server-side —\n // see probeAgentHealth in wspace-vibecontrols-svc).\n // 2. The grace-window timestamp stored at markAgentConnecting time has\n // elapsed. This defends against a STALE `healthy:true` probe\n // delivered from the Apollo cache during the in-flight refetch\n // that follows updateAgent — without the grace window, that stale\n // cache hit cleared CONNECTING immediately and the next (fresh,\n // transient) probe landing during cloudflared restart flipped the\n // badge to DEGRADED, producing the CONNECTING → DEGRADED → ACTIVE\n // flicker reported by users.\n //\n // Re-runs on `probesById` updates AND on a one-shot timer per pending\n // agent so the clear fires automatically once the grace window elapses\n // even if no new probe arrives.\n useEffect(() => {\n const pending = Object.keys(connectingAgents);\n if (pending.length === 0) return;\n const now = Date.now();\n let earliestPending = Infinity;\n for (const agentId of pending) {\n const minClearAt = connectingAgents[agentId];\n if (minClearAt === undefined) continue;\n const probe = probesById.get(agentId);\n if (probe?.healthy === true && now >= minClearAt) {\n clearAgentConnecting(agentId);\n } else if (minClearAt < earliestPending) {\n earliestPending = minClearAt;\n }\n }\n if (earliestPending === Infinity) return;\n const wait = Math.max(0, earliestPending - now) + 50;\n const tick = setTimeout(() => {\n // No-op state poke — the dependency on `connectingAgents` causes\n // this effect to re-run when state changes, but a healthy probe\n // arriving BEFORE the grace window elapses needs a re-evaluation\n // when the window finally closes. Re-marking with graceMs=0 is a\n // no-op when the entry is already past its window; if cleared in\n // the meantime, the early `pending.length === 0` short-circuit\n // makes this safe.\n const fresh = useVibeControlsStore.getState().connectingAgents;\n for (const agentId of Object.keys(fresh)) {\n const minClearAt = fresh[agentId];\n if (minClearAt === undefined) continue;\n const probe = probesById.get(agentId);\n if (probe?.healthy === true && Date.now() >= minClearAt) {\n clearAgentConnecting(agentId);\n }\n }\n }, wait);\n return () => clearTimeout(tick);\n }, [probesById, connectingAgents, clearAgentConnecting]);\n\n // Filter agents based on search and tag (client-side filtering).\n // Status / platform filters are applied server-side via the GraphQL filter\n // input above; these client-side filters compose on top of that result set.\n const filteredAgents = useMemo(() => {\n return agents.filter((agent) => {\n // Search filter\n if (searchQuery) {\n const query = searchQuery.toLowerCase();\n const matchesSearch =\n agent.name.toLowerCase().includes(query) ||\n agent.hostname?.toLowerCase().includes(query) ||\n agent.id.toLowerCase().includes(query);\n if (!matchesSearch) return false;\n }\n\n // Tag filter — match against structured tagAssignments (UUIDs).\n // Never falls back to the legacy free-text agent.tags array because\n // those values are not the same as tagFilterIds and would silently\n // produce empty results (the failure mode that got the previous\n // TagFilter implementation removed in commit 4e101ca).\n if (tagFilter !== 'all') {\n const hasTag = (agent.tagAssignments ?? []).some(\n (a: { tagId: string }) => a.tagId === tagFilter\n );\n if (!hasTag) return false;\n }\n\n return true;\n });\n }, [agents, searchQuery, tagFilter]);\n\n const handleAgentSelect = (agent: Agent) => {\n // Navigate to agent details page\n handleNavigate(`/agents/${agent.id}`);\n // Also call the shell callback if provided\n onAgentSelect?.({\n id: agent.id,\n name: agent.name,\n type: agent.platform || 'unknown',\n status: agent.status || (agent.isActive ? 'ACTIVE' : 'OFFLINE'),\n });\n };\n\n const handleCreateAgent = () => {\n setShowCreateForm(true);\n bus.emitCustom('vibecontrols.agent.register_clicked', {\n workspaceId: workspaceId ?? undefined,\n });\n };\n\n const handleCreateAgentSubmit = async (\n formData: Record<string, unknown>\n ): Promise<{ id: string } | void> => {\n try {\n setIsCreating(true);\n\n const tunnelUrl = (formData.tunnelUrl as string | undefined)?.trim();\n const agentApiKey = (formData.agentApiKey as string | undefined)?.trim();\n\n // Zero-config path: operator pasted the agent's tunnel URL + API key\n // shown in its startup banner. Backend probes the agent, registers it,\n // mints an OAuth app, and pushes credentials — the agent promotes\n // itself to `ready` inside that final POST /api/agent/gateway-auth.\n if (tunnelUrl && agentApiKey) {\n const result = await addAgentFromTunnel({\n variables: {\n input: {\n tunnelUrl,\n apiKey: agentApiKey,\n ...(formData.name ? { name: formData.name as string } : {}),\n ...(formData.targetId ? { targetId: formData.targetId as string } : {}),\n },\n },\n });\n if (result.error) throw result.error;\n const payload = result.data?.addVibecontrolsAgentFromTunnel;\n if (!payload?.success) {\n throw new Error(\n payload?.error || 'Failed to onboard agent — probe or OAuth setup failed.'\n );\n }\n if (payload.agent?.id) {\n await flushAgentTags(payload.agent.id);\n bus.emitCustom('vibecontrols.agent.registered', {\n workspaceId: workspaceId ?? undefined,\n agentId: payload.agent.id,\n });\n bus.emitCustom('vibecontrols.agent.gateway_auth_setup', {\n workspaceId: workspaceId ?? undefined,\n agentId: payload.agent.id,\n });\n return { id: payload.agent.id };\n }\n throw new Error('Agent onboarding returned no agent record.');\n }\n\n // Legacy manual path — operator typed hostname/platform/etc directly.\n const result = await createAgent({\n variables: {\n input: {\n name: formData.name as string,\n hostname: formData.hostname as string,\n platform: formData.platform as string,\n architecture: formData.architecture as string,\n version: formData.version as string,\n ...(formData.targetId ? { targetId: formData.targetId as string } : {}),\n ...(formData.tunnelUrl ? { tunnelUrl: formData.tunnelUrl as string } : {}),\n ...(formData.tunnelStatus ? { tunnelStatus: formData.tunnelStatus as string } : {}),\n },\n },\n });\n\n const createdAgent = result.data?.createVibecontrolsAgent;\n if (createdAgent?.id) {\n await flushAgentTags(createdAgent.id);\n bus.emitCustom('vibecontrols.agent.registered', {\n workspaceId: workspaceId ?? undefined,\n agentId: createdAgent.id,\n });\n return { id: createdAgent.id };\n }\n\n if (result.error) {\n throw result.error;\n }\n\n throw new Error('Agent creation failed — no data returned from the server.');\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n markQuotaHandledLocally();\n setQuotaExhaustedKind('agent');\n setShowCreateForm(false);\n bus.emitCustom('vibecontrols.quota.agent_limit_hit', {\n workspaceId: workspaceId ?? undefined,\n });\n return;\n }\n\n console.error('Failed to create agent:', err);\n throw err;\n } finally {\n setIsCreating(false);\n }\n };\n\n const handleStartAgent = async (agent: Agent) => {\n try {\n const result = await startAgentMutation({ variables: { agentId: agent.id } });\n const payload = result.data?.startVibecontrolsAgent;\n\n // Smart fallback: an agent in bootState=awaiting-config rejects\n // lifecycle/start with \"Agent not yet configured\" because it has\n // no OAuth credentials yet. The right recovery is\n // setupAgentGatewayAuth (the same mutation behind the\n // \"Re-push credentials\" button on the Security tab). Fire it\n // automatically so a single Reconnect click handles both the\n // common case and the awaiting-config case.\n if (!payload?.success) {\n const needsConfig = /not yet configured|awaiting[-\\s_]config/i.test(payload?.error ?? '');\n if (needsConfig) {\n const setupResult = await setupGatewayAuth({\n variables: { input: { agentId: agent.id } },\n });\n const setup = setupResult.data?.setupAgentGatewayAuth;\n if (setup?.error) {\n throw new Error(\n `Agent needs credentials. Auto re-push failed: ${setup.error}. Try \"Re-push credentials\" in Security tab.`\n );\n }\n if (!setup) {\n throw new Error(\n 'Agent needs credentials. Auto re-push returned no result. Try \"Re-push credentials\" in Security tab.'\n );\n }\n // `setupAgentGatewayAuth.error` is null when the OAuth app +\n // devportal app are created successfully, but the credentials\n // PUSH to the agent's tunnel can still fail downstream (the\n // svc records this as `lastGatewayAuthPushStatus: \"failed\"` +\n // `lastGatewayAuthPushError` on the agent row). The push fails\n // when the agent's finalize() hangs trying to authenticate\n // against gateway URLs it can't reach — most commonly when an\n // agent is registered against a workspace whose gateway URLs\n // are dev-local (e.g. *.local.burdenoff.com) and the agent\n // runs on a different machine. Surface that specific failure\n // rather than claiming success.\n const pushStatus = setup.agent?.lastGatewayAuthPushStatus;\n const pushError = setup.agent?.lastGatewayAuthPushError;\n if (pushStatus === 'failed') {\n throw new Error(\n `Credentials created but push to agent tunnel failed: ${\n pushError || 'unknown error'\n }. Check that the agent can reach the workspace's gateway URLs from its host network.`\n );\n }\n // PR #136 review (claude MEDIUM BUG): setupAgentGatewayAuth\n // pushes creds → agent finalize → bootState=ready. But that\n // doesn't call lifecycle/start. If the agent was previously\n // STOPPED (not just awaiting-config), it'd be credentialed\n // but not running, while the toast claimed \"and started\".\n // Retry the original lifecycle/start now that the agent\n // should have left awaiting-config; surface its outcome\n // explicitly so the toast doesn't lie either way.\n const retryResult = await startAgentMutation({\n variables: { agentId: agent.id },\n });\n const retryPayload = retryResult.data?.startVibecontrolsAgent;\n if (!retryPayload?.success) {\n throw new Error(\n `Credentials pushed but lifecycle/start failed: ${\n retryPayload?.error || 'unknown error'\n }. The agent has fresh credentials; try clicking Reconnect again or check vibe status on the host.`\n );\n }\n bus.emitCustom('vibecontrols.agent.gateway_auth_setup', {\n workspaceId: workspaceId ?? undefined,\n agentId: agent.id,\n });\n await agentHeartbeat({ variables: { id: agent.id } });\n notify.success(`Agent \"${agent.name}\" re-credentialed and started.`);\n return;\n }\n throw new Error(payload?.error || 'Failed to start agent');\n }\n\n await agentHeartbeat({ variables: { id: agent.id } });\n notify.success(`Agent \"${agent.name}\" started`);\n } catch (err) {\n console.error('Failed to start agent:', err);\n notify.error(err instanceof Error ? err.message : 'Failed to start agent');\n }\n };\n\n // NOTE: Stop and Disconnect both call deactivateVibecontrolsAgent and produce the same\n // server-side list state (isActive: false). The distinction is that Disconnect additionally\n // clears tunnel fields (tunnelUrl, tunnelStatus). When a dedicated stopVibecontrolsAgent\n // mutation succeeds, we still deactivate in workspace state so the list reflects the agent as offline.\n const handleStopAgent = async (agent: Agent) => {\n try {\n const result = await stopAgentMutation({ variables: { agentId: agent.id } });\n const payload = result.data?.stopVibecontrolsAgent;\n\n if (!payload?.success) {\n throw new Error(payload?.error || 'Failed to stop agent');\n }\n\n await deactivateAgent({ variables: { id: agent.id } });\n notify.success(`Agent \"${agent.name}\" stopped`);\n } catch (err) {\n console.error('Failed to stop agent:', err);\n notify.error(err instanceof Error ? err.message : 'Failed to stop agent');\n }\n };\n\n const handleDisconnectAgent = (agent: Agent) => {\n setDisconnectConfirmAgent(agent);\n };\n\n const handleConfirmDisconnect = async () => {\n if (!disconnectConfirmAgent) return;\n const agent = disconnectConfirmAgent;\n setIsDisconnecting(true);\n // Deactivate + clear tunnel info so the agent appears fully disconnected.\n // Two sequential mutations with refetchQueries suppressed — a single refetch()\n // is called after both complete so the list doesn't flash with stale tunnel fields.\n // A single atomic disconnectVibecontrolsAgent backend mutation that handles\n // both deactivation and tunnel field clearing is tracked in vibecontrols-svc\n // backlog; until then the two-step approach below is the supported flow.\n try {\n await deactivateAgent({ variables: { id: agent.id }, refetchQueries: [] });\n } catch (err) {\n console.error('Failed to deactivate agent:', err);\n notify.error(err instanceof Error ? err.message : 'Failed to disconnect agent');\n setIsDisconnecting(false);\n setDisconnectConfirmAgent(null);\n // Early-return path: refetch here so the list reflects the deactivated state,\n // then return so the bottom refetch below does not also fire.\n await refetch();\n return;\n }\n try {\n // F4 Wave B: `agentApiKey` is no longer part of\n // `UpdateVibecontrolsAgentInput`. Clearing the tunnel fields is\n // enough — the secret is owned by the agent itself, not the svc.\n await updateAgent({\n variables: {\n id: agent.id,\n input: { tunnelUrl: null, tunnelStatus: null },\n },\n refetchQueries: [],\n });\n notify.success(`Agent \"${agent.name}\" disconnected`);\n } catch (err) {\n console.error('Failed to clear tunnel fields:', err);\n notify.error(\n `Agent marked offline, but failed to clear connection fields — clear them manually in Settings.`\n );\n }\n setIsDisconnecting(false);\n setDisconnectConfirmAgent(null);\n // Both updateAgent success and updateAgent failure reach here — single refetch covers both.\n await refetch();\n };\n\n const handleConfigureAgent = (agent: Agent) => {\n // Navigate to agent details page (settings tab)\n handleNavigate(`/agents/${agent.id}`);\n };\n\n const handleEditTunnelUrl = (agent: Agent) => {\n setEditTunnelAgent(agent);\n };\n\n const handleSaveTunnelSettings = async () => {\n if (!editTunnelAgent) return;\n const agentId = editTunnelAgent.id;\n const newUrl = tunnelUrlRef.current?.value?.trim() || '';\n try {\n setIsSavingTunnel(true);\n // TODO(svc): rotate-API-key flow needs a dedicated svc mutation —\n // `UpdateVibecontrolsAgentInput.agentApiKey` was dropped by F4\n // Wave B. The Edit Tunnel dialog only updates the URL now.\n await updateAgent({\n variables: {\n id: agentId,\n input: {\n tunnelUrl: newUrl || null,\n tunnelStatus: newUrl ? 'ACTIVE' : null,\n },\n },\n // Force a fresh probe immediately so the badge resolves to the\n // real state (instead of relying on the 30s subscription cadence).\n refetchQueries: ['ListAgents', 'VibecontrolsAgentHealthProbes'],\n });\n // Begin the \"Connecting…\" overlay BEFORE clearing the dialog so the\n // operator sees an unbroken progress signal: dialog spinner →\n // CONNECTING badge with spinner → ACTIVE / DEGRADED.\n if (newUrl) markAgentConnecting(agentId);\n setEditTunnelAgent(null);\n } catch (err) {\n console.error('Failed to update tunnel settings:', err);\n } finally {\n setIsSavingTunnel(false);\n }\n };\n\n // ── Bulk Selection ─────────────────────────────────────────────────\n const toggleAgentSelection = useCallback((agentId: string) => {\n setSelectedAgentIds((prev) => {\n const next = new Set(prev);\n if (next.has(agentId)) {\n next.delete(agentId);\n } else {\n next.add(agentId);\n }\n return next;\n });\n }, []);\n\n const selectAllAgents = useCallback(() => {\n setSelectedAgentIds(new Set(filteredAgents.map((a) => a.id)));\n }, [filteredAgents]);\n\n const deselectAllAgents = useCallback(() => {\n setSelectedAgentIds(new Set());\n }, []);\n\n const isAllSelected =\n filteredAgents.length > 0 && selectedAgentIds.size === filteredAgents.length;\n const hasSelection = selectedAgentIds.size > 0;\n\n // ── Bulk Delete ──────────────────────────────────────────────────\n // Confirmation happens through the ConfirmDialog rendered below\n // (bulkDeleteConfirmOpen) — this handler runs only after the user\n // confirms there.\n const handleBulkDelete = useCallback(async () => {\n if (selectedAgentIds.size === 0) return;\n\n setBulkLoading(true);\n try {\n const results = await Promise.allSettled(\n Array.from(selectedAgentIds).map((id) => deleteAgent({ variables: { id } }))\n );\n const succeeded = results.filter((r) => r.status === 'fulfilled').length;\n const failed = results.filter((r) => r.status === 'rejected').length;\n if (failed > 0) {\n notify.error(\n `Removed ${succeeded}, failed to remove ${failed} agent${failed !== 1 ? 's' : ''}`\n );\n } else {\n notify.success(`Removed ${succeeded} agent${succeeded !== 1 ? 's' : ''}`);\n }\n setSelectedAgentIds(new Set());\n } catch (err) {\n notify.error(err instanceof Error ? err.message : 'Failed to remove agents');\n } finally {\n setBulkLoading(false);\n setBulkDeleteConfirmOpen(false);\n }\n }, [selectedAgentIds, deleteAgent]);\n\n // Stats\n //\n // \"online\" must mean the same thing the per-agent badge shows. The badge is\n // driven by `deriveAgentStatus` (live /health/ready probe + lifecycleState),\n // not by the persisted `isActive` flag — which is sticky: the backend sets it\n // true on every heartbeat but never flips it false when heartbeats stop, so a\n // stopped/crashed agent stays `isActive: true` indefinitely. Counting\n // `isActive` here is what produced \"2 of 2 agents online\" while both cards\n // showed DEGRADED. Count the derived status instead so the header and the\n // cards can never disagree.\n const stats = {\n total: agents.length,\n active: agents.filter((a) => a.status === 'ACTIVE').length,\n offline: agents.filter((a) => a.status !== 'ACTIVE').length,\n };\n\n // Guard states rendered inside MotionPage to prevent unmount/remount animation replay\n if (permissionsLoading) {\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-full\">\n <LoadingState message={tr('agentsPage.loadingPermissions', 'Loading permissions...')} />\n </MotionPage>\n );\n }\n\n if (permissions && permissions.permissionStrings?.length > 0 && !canListAgents) {\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-full\">\n <AccessDenied\n title={tr('agentsPage.accessDeniedTitle', 'Access Denied')}\n description={tr(\n 'agentsPage.accessDeniedDescription',\n \"You don't have permission to view agents. Contact your workspace administrator for access.\"\n )}\n />\n </MotionPage>\n );\n }\n\n if (error && agents.length === 0) {\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-full\">\n <ErrorState\n title={tr('agentsPage.failedLoad', 'Failed to load agents')}\n message={error.message}\n onRetry={() => refetch()}\n />\n </MotionPage>\n );\n }\n\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-full\">\n <PageHeader\n title={tr('agentsPage.title', 'Agents')}\n description={tr('agentsPage.descriptionOnline', '{{active}} of {{total}} agents online', {\n active: stats.active,\n total: stats.total,\n })}\n actions={\n <div className=\"flex w-full flex-col items-end gap-2 sm:w-auto sm:flex-row sm:items-center\">\n <button\n type=\"button\"\n onClick={() => {\n // Refresh both the agent list AND the live deep-health\n // probe so the badge reflects current state, not a stale\n // initial probe captured while the agent was still\n // booting. Without re-probing here the user clicks the\n // refresh icon and the row's name/tunnel reload but the\n // DEGRADED badge stays stuck until the next 30s tick.\n refetch();\n void refetchProbes();\n }}\n disabled={loading || probeQuery.loading}\n className=\"flex items-center justify-center gap-2 px-3 py-2 max-md:min-h-11 max-md:min-w-11 text-text-secondary hover:bg-bg-sunken rounded-md transition-colors disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus\"\n aria-label={tr('agentsPage.refreshTitle', 'Refresh agents')}\n title={tr('agentsPage.refreshTitle', 'Refresh agents')}\n >\n <RefreshCw\n aria-hidden=\"true\"\n className={`size-4 ${loading || probeQuery.loading ? 'animate-spin' : ''}`}\n />\n </button>\n {hasTargets ? (\n <PermissionButton\n permission={VIBE_PERMISSIONS.AGENT_CREATE}\n deniedBehavior=\"disable\"\n deniedTooltip={tr(\n 'agentsPage.deniedCreateTooltip',\n \"You don't have permission to create agents\"\n )}\n >\n <button\n type=\"button\"\n onClick={handleCreateAgent}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bgHover transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n <Plus className=\"size-4\" />\n {tr('agentsPage.addAgent', 'Add Agent')}\n </button>\n </PermissionButton>\n ) : (\n /* No targets yet — still open Add Agent; the form now\n supports inline target creation. */\n <button\n type=\"button\"\n onClick={handleCreateAgent}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bgHover transition-colors\"\n >\n <Plus className=\"size-4\" />\n {tr('agentsPage.addAgent', 'Add Agent')}\n </button>\n )}\n </div>\n }\n />\n\n {/* Agent CLI Setup Banner — shown when no agents exist */}\n {agents.length === 0 && !loading && (\n <AgentSetupBanner variant=\"full\" dismissKey=\"vibecontrols-agents-setup-banner\" />\n )}\n\n {/* Compact reminder when agents exist but shown once */}\n {agents.length > 0 && (\n <AgentSetupBanner variant=\"compact\" dismissKey=\"vibecontrols-agents-compact-banner\" />\n )}\n\n {/* Filters Bar */}\n <div className=\"space-y-3\">\n {/* Row 1: Search + View Toggle */}\n <div className=\"flex items-center gap-2\">\n <SearchInput\n value={searchQuery}\n onChange={setSearchQuery}\n placeholder={tr('agentsPage.searchPlaceholder', 'Search agents...')}\n className=\"flex-1 sm:w-64 sm:flex-none\"\n />\n {/* View Toggle */}\n <div className=\"flex items-center gap-1 bg-bg-sunken rounded-md p-1 flex-shrink-0\">\n <button\n type=\"button\"\n onClick={() => {\n setViewMode('grid');\n setSelectedAgentIds(new Set());\n }}\n className={`flex items-center justify-center p-1.5 sm:p-2 max-md:min-h-11 max-md:min-w-11 rounded focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus ${viewMode === 'grid' ? 'bg-bg-surface shadow-sm' : 'hover:bg-bg-surface/50'}`}\n aria-label={tr('agentsPage.gridViewTitle', 'Grid view')}\n aria-pressed={viewMode === 'grid'}\n title={tr('agentsPage.gridViewTitle', 'Grid view')}\n >\n <Grid aria-hidden=\"true\" className=\"size-4 text-text-secondary\" />\n </button>\n <button\n type=\"button\"\n onClick={() => {\n setViewMode('list');\n setSelectedAgentIds(new Set());\n }}\n className={`flex items-center justify-center p-1.5 sm:p-2 max-md:min-h-11 max-md:min-w-11 rounded focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus ${viewMode === 'list' ? 'bg-bg-surface shadow-sm' : 'hover:bg-bg-surface/50'}`}\n aria-label={tr('agentsPage.listViewTitle', 'List view')}\n aria-pressed={viewMode === 'list'}\n title={tr('agentsPage.listViewTitle', 'List view')}\n >\n <List aria-hidden=\"true\" className=\"size-4 text-text-secondary\" />\n </button>\n </div>\n </div>\n\n {/* Row 2: Filters - wrap on all screens */}\n <div className=\"flex items-center gap-2 sm:gap-3 flex-wrap\">\n <FilterDropdown\n label={tr('agentsPage.filterStatus', 'Status')}\n value={statusFilter}\n options={statusOptions}\n onChange={setStatusFilter}\n />\n <FilterDropdown\n label={tr('agentsPage.filterPlatform', 'Platform')}\n value={platformFilter}\n options={platformOptions}\n onChange={setPlatformFilter}\n />\n <TagFilter\n label={tr('agentsPage.filterTag', 'Tag')}\n value={tagFilter}\n onChange={setTagFilter}\n />\n </div>\n </div>\n\n {/* Select All (grid view only — list view renders its own select-all\n affordance via AgentList's header checkbox / mobile button) */}\n {viewMode === 'grid' && filteredAgents.length > 0 && (\n <div className=\"flex justify-end\">\n <button\n type=\"button\"\n onClick={isAllSelected ? deselectAllAgents : selectAllAgents}\n aria-pressed={isAllSelected}\n className=\"flex items-center gap-1.5 px-3 py-1.5 max-md:min-h-11 text-xs text-text-secondary hover:text-text-primary rounded-md transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus\"\n >\n <CheckSquare aria-hidden=\"true\" className=\"size-3.5\" />\n {isAllSelected\n ? tr('agentsPage.deselectAll', 'Deselect All')\n : tr('agentsPage.selectAll', 'Select All')}\n </button>\n </div>\n )}\n\n {/* Bulk Action Bar */}\n {hasSelection && (\n <div className=\"flex flex-col sm:flex-row items-start sm:items-center gap-3 px-4 py-2 bg-action-primary-bg/10 border border-action-primary-bg/20 rounded-card\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('agentsPage.selectedCount', '{{count}} agents selected', {\n count: selectedAgentIds.size,\n })}\n </span>\n <div className=\"flex items-center gap-1.5 sm:ml-auto\">\n <button\n type=\"button\"\n onClick={() => setBulkDeleteConfirmOpen(true)}\n disabled={bulkLoading}\n className=\"flex items-center gap-1.5 px-3 py-1.5 max-md:min-h-11 text-xs font-medium text-status-error-text bg-status-error-bg-subtle border border-status-error-border rounded-md hover:bg-status-error-bg-subtle/80 transition-colors disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus\"\n >\n <Trash2 aria-hidden=\"true\" className=\"size-3\" /> {tr('agentsPage.remove', 'Remove')}\n </button>\n <button\n type=\"button\"\n onClick={deselectAllAgents}\n className=\"flex items-center gap-1.5 px-3 py-1.5 max-md:min-h-11 text-xs text-text-secondary hover:text-text-primary rounded-md transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus\"\n >\n <XSquare aria-hidden=\"true\" className=\"size-3\" />{' '}\n {tr('agentsPage.deselect', 'Deselect')}\n </button>\n </div>\n </div>\n )}\n\n {/* Results Count */}\n {searchQuery || statusFilter !== 'all' || platformFilter !== 'all' ? (\n <p className=\"text-sm text-text-secondary\">\n {tr('agentsPage.showingResults', 'Showing {{filtered}} of {{total}} agents', {\n filtered: filteredAgents.length,\n total: agents.length,\n })}\n {loading && (\n <span className=\"ml-2 text-text-muted\">\n {tr('agentsPage.refreshing', '(refreshing...)')}\n </span>\n )}\n </p>\n ) : null}\n\n {/* Agents Display */}\n {loading && agents.length === 0 ? (\n <PageSkeleton variant=\"rows\" count={6} />\n ) : filteredAgents.length > 0 ? (\n <AnimatePresence mode=\"wait\">\n {viewMode === 'grid' ? (\n <MotionList\n key=\"grid-view\"\n data-tour=\"agents-page-grid-header\"\n stagger={0.04}\n className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\"\n >\n {filteredAgents.map((agent) => (\n <MotionItem key={agent.id} className=\"h-full\">\n <AgentCard\n agent={agent}\n onSelect={handleAgentSelect}\n onStart={handleStartAgent}\n onStop={handleStopAgent}\n onDisconnect={handleDisconnectAgent}\n onConfigure={handleConfigureAgent}\n onEditTunnelUrl={handleEditTunnelUrl}\n selected={selectedAgentIds.has(agent.id)}\n onToggleSelect={() => toggleAgentSelection(agent.id)}\n healthErrorMessage={agent.healthError ?? null}\n />\n </MotionItem>\n ))}\n </MotionList>\n ) : (\n <MotionList key=\"list-view\" stagger={0.04}>\n <MotionItem variant=\"fadeRight\">\n <AgentList\n agents={filteredAgents}\n onSelect={handleAgentSelect}\n onStart={handleStartAgent}\n onStop={handleStopAgent}\n onDisconnect={handleDisconnectAgent}\n onSettings={handleConfigureAgent}\n selectedIds={selectedAgentIds}\n onToggleSelect={toggleAgentSelection}\n onSelectAll={selectAllAgents}\n onDeselectAll={deselectAllAgents}\n />\n </MotionItem>\n </MotionList>\n )}\n </AnimatePresence>\n ) : (\n <EmptyState\n icon={<Bot className=\"size-8 text-text-secondary\" />}\n title={\n searchQuery || statusFilter !== 'all' || platformFilter !== 'all'\n ? tr('agentsPage.emptyNoAgentsFound', 'No agents found')\n : tr('agentsPage.emptyNoAgentsConfigured', 'No agents configured')\n }\n description={\n searchQuery || statusFilter !== 'all' || platformFilter !== 'all'\n ? tr('agentsPage.emptyAdjustSearch', 'Try adjusting your search or filters')\n : tr(\n 'agentsPage.emptyNoAgentsDescription',\n 'Install the CLI agent on your machine, then add it here. You can create a target inline while adding the agent.'\n )\n }\n action={\n !searchQuery && statusFilter === 'all' && platformFilter === 'all' ? (\n <div className=\"flex flex-wrap items-center gap-3\">\n {/* Always open Add Agent — the form handles the \"no\n targets yet\" case via inline DIRECT target creation. */}\n <button\n type=\"button\"\n onClick={handleCreateAgent}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bgHover transition-colors\"\n >\n <Plus className=\"size-4\" />\n {tr('agentsPage.addAgent', 'Add Agent')}\n </button>\n <button\n type=\"button\"\n onClick={() => handleNavigate('/getting-started')}\n className=\"flex items-center gap-2 px-4 py-2 border border-border-default text-text-primary rounded-md hover:bg-bg-sunken transition-colors text-sm\"\n >\n {tr('agentsPage.setupGuide', 'Setup guide')}\n </button>\n <GetHelpButton prompt=\"What is an Agent in VibeControls and how do I add one?\" />\n </div>\n ) : undefined\n }\n />\n )}\n\n {/* Create Agent Modal */}\n {showCreateForm && (\n <AgentForm\n onSubmit={handleCreateAgentSubmit}\n onCancel={() => setShowCreateForm(false)}\n isLoading={isCreating}\n onNavigate={(path) => {\n setShowCreateForm(false);\n handleNavigate(path);\n }}\n targets={targetsList.map(\n (t: { id: string; name: string; type?: string; status: string }) => ({\n id: t.id,\n name: t.name,\n type: t.type || 'DIRECT',\n status: t.status,\n })\n )}\n targetsLoading={targetsLoading}\n onCreateTarget={handleInlineCreateTarget}\n extraSection={\n <div>\n <h3 className=\"text-sm font-medium text-text-secondary mb-2\">\n {tr('agents.tags', 'Tags')}\n </h3>\n <EntityTagPicker\n entityType=\"VIBECONTROLS_AGENT\"\n onDraftChange={setAgentDraftTagIds}\n />\n </div>\n }\n />\n )}\n\n {/* Disconnect Confirmation Dialog */}\n <ConfirmDialog\n isOpen={disconnectConfirmAgent !== null}\n onClose={() => {\n if (!isDisconnecting) setDisconnectConfirmAgent(null);\n }}\n onConfirm={handleConfirmDisconnect}\n title={tr('agentsPage.disconnectTitle', 'Disconnect Agent')}\n message={\n disconnectConfirmAgent\n ? deriveAgentStatus(disconnectConfirmAgent) === 'OFFLINE'\n ? tr(\n 'agentsPage.disconnectOfflineMessage',\n 'Clear the stored tunnel connection for {{name}}? This will remove the tunnel URL and API key from this offline agent.',\n { name: disconnectConfirmAgent.name }\n )\n : tr(\n 'agentsPage.disconnectActiveMessage',\n 'Mark {{name}} as offline and clear its tunnel connection? The agent process will not be stopped automatically — you may need to stop it manually on the host machine.',\n { name: disconnectConfirmAgent.name }\n )\n : ''\n }\n confirmLabel={tr('agentsPage.disconnectConfirm', 'Disconnect')}\n variant=\"danger\"\n isLoading={isDisconnecting}\n />\n\n {/* Edit Tunnel Dialog */}\n <ResponsiveDialog\n open={editTunnelAgent !== null}\n onOpenChange={(open) => {\n if (!open) setEditTunnelAgent(null);\n }}\n >\n {editTunnelAgent ? (\n <ResponsiveDialogContent className=\"sm:max-w-md\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>\n {tr('agentsPage.editTunnelTitle', 'Edit Tunnel')}\n </ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n {tr(\n 'agentsPage.editTunnelDescription',\n 'Configure the cloudflared tunnel URL and API key for {{name}}.',\n {\n name: editTunnelAgent.name,\n }\n )}\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"py-4\">\n <label\n htmlFor={tunnelUrlField.id}\n className=\"mb-1.5 block text-sm font-medium text-text-primary\"\n >\n {tr('agentsPage.tunnelUrlLabel', 'Tunnel URL')}\n </label>\n <Input\n {...tunnelUrlField.inputProps}\n ref={tunnelUrlRef}\n type=\"url\"\n defaultValue={editTunnelAgent.tunnelUrl || ''}\n placeholder={tr(\n 'agentsPage.tunnelUrlPlaceholder',\n 'https://example.trycloudflare.com'\n )}\n className=\"text-base md:text-sm\"\n />\n <p id={tunnelUrlField.hintId} className=\"mt-1.5 text-xs text-text-muted\">\n {tr(\n 'agentsPage.tunnelUrlHint',\n 'Leave empty to clear. Agents with auto-tunnel report this automatically on startup.'\n )}\n </p>\n {/* F4 Wave B: API key rotation removed from this dialog —\n `UpdateVibecontrolsAgentInput.agentApiKey` was dropped\n by the svc and key rotation now requires a dedicated\n rotate mutation (TODO). The agent's key still surfaces\n on the agent details page via the lazy reveal query. */}\n </div>\n <ResponsiveDialogFooter>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => setEditTunnelAgent(null)}\n className=\"max-md:min-h-11\"\n >\n {tr('agentsPage.cancel', 'Cancel')}\n </Button>\n <Button\n type=\"button\"\n onClick={handleSaveTunnelSettings}\n disabled={isSavingTunnel}\n aria-busy={isSavingTunnel}\n className=\"max-md:min-h-11\"\n >\n {isSavingTunnel\n ? tr('agentsPage.saving', 'Saving...')\n : tr('agentsPage.save', 'Save')}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n ) : null}\n </ResponsiveDialog>\n\n {/* Bulk Delete Confirmation Dialog (replaces window.confirm) */}\n <ConfirmDialog\n isOpen={bulkDeleteConfirmOpen}\n onClose={() => {\n if (!bulkLoading) setBulkDeleteConfirmOpen(false);\n }}\n onConfirm={handleBulkDelete}\n title={tr('agentsPage.bulkDeleteTitle', 'Remove agents')}\n message={tr(\n 'agentsPage.bulkDeleteConfirm',\n 'Are you sure you want to remove {{count}} agent(s)? This cannot be undone.',\n { count: selectedAgentIds.size }\n )}\n confirmLabel={tr('agentsPage.remove', 'Remove')}\n variant=\"danger\"\n isLoading={bulkLoading}\n />\n\n {/* Quota Exhausted Dialog — copy switches based on the entity the\n user just tried to create (agent vs inline target). */}\n <QuotaExhaustedDialog\n isOpen={quotaExhaustedKind !== null}\n onClose={() => setQuotaExhaustedKind(null)}\n onConfirm={() => {\n setQuotaExhaustedKind(null);\n handleNavigate('/billing/plans');\n }}\n title={getQuotaDialogContent(quotaExhaustedKind ?? 'generic').title}\n message={getQuotaDialogContent(quotaExhaustedKind ?? 'generic').message}\n />\n </MotionPage>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"AgentsPage.js","names":[],"sources":["../../src/pages/AgentsPage.tsx"],"sourcesContent":["import { useState, useMemo, useRef, useCallback, useEffect } from 'react';\nimport { useSearchParams } from 'react-router-dom';\nimport { Plus, Bot, Grid, List, RefreshCw, Trash2, CheckSquare, XSquare } from 'lucide-react';\nimport { useEventBus } from '@burdenoff/fe-libs/shared/events';\nimport { MotionPage, MotionList, MotionItem } from '@burdenoff/fe-libs/shared/motion';\nimport { AnimatePresence } from 'framer-motion';\nimport {\n Button,\n Input,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n} from '@burdenoff/fe-libs/ui';\nimport { AgentCard, AgentList, AgentForm } from '@/components/agents';\nimport { EntityTagPicker } from '@/components/shared/EntityTagPicker';\nimport { PageSkeleton } from '@/components/shared/PageSkeleton';\nimport { useDraftEntityTags } from '@/hooks/useDraftEntityTags';\nimport type { Agent } from '@/components/agents';\nimport {\n PageHeader,\n SearchInput,\n FilterDropdown,\n TagFilter,\n EmptyState,\n LoadingState,\n ErrorState,\n AgentSetupBanner,\n QuotaExhaustedDialog,\n GetHelpButton,\n ConfirmDialog,\n} from '@/components/shared';\nimport { notify } from '@/lib/toast';\nimport { useField } from '@/shared/hooks/useField';\nimport { useTr } from '@/shared/hooks/useTr';\nimport { useVibeControls, useVibeNavigate } from '@/providers/VibeControlsProvider';\nimport {\n useListAgentsQuery,\n useVibecontrolsAgentHealthProbesQuery,\n useVibecontrolsAgentHealthProbesLazyQuery,\n useVibecontrolsAgentHealthProbeStreamSubscription,\n useCreateAgentMutation,\n useUpdateAgentMutation,\n useDeleteAgentMutation,\n useAgentHeartbeatMutation,\n useDeactivateAgentMutation,\n useCreateTargetMutation,\n useAddAgentFromTunnelMutation,\n useStartVibecontrolsAgentMutation,\n useStopVibecontrolsAgentMutation,\n useSetupAgentGatewayAuthMutation,\n useListTargetsQuery,\n} from '@/generated/wspace-operations';\nimport { PermissionButton, AccessDenied } from '@burdenoff/fe-libs/shared/components';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { VIBE_PERMISSIONS } from '@/constants/permissions';\nimport { deriveAgentStatus, type AgentHealthProbe } from '@/utils/agentStatus';\nimport {\n isQuotaExhaustedError,\n markQuotaHandledLocally,\n getQuotaDialogContent,\n type QuotaKind,\n} from '@/utils/quotaUtils';\nimport { useCachedInitialData } from '@/hooks/useCachedInitialData';\nimport { useVibeControlsStore } from '@/store/vibeControlsStore';\n\ntype ViewMode = 'grid' | 'list';\n\n/**\n * Agents management page with filtering, search, and CRUD operations\n */\nexport function AgentsPage() {\n const { onAgentSelect, workspaceId } = useVibeControls();\n const handleNavigate = useVibeNavigate();\n const bus = useEventBus();\n const { hasPermission, isLoading: permissionsLoading, permissions } = usePermissions();\n const tr = useTr();\n\n const statusOptions = [\n { value: 'all', label: tr('agentsPage.statusAll', 'All Statuses') },\n { value: 'ACTIVE', label: tr('agentsPage.statusActive', 'Active') },\n { value: 'STOPPED', label: tr('agentsPage.statusStopped', 'Stopped') },\n { value: 'OFFLINE', label: tr('agentsPage.statusOffline', 'Offline') },\n ];\n\n const platformOptions = [\n { value: 'all', label: tr('agentsPage.platformAll', 'All Platforms') },\n { value: 'darwin', label: tr('agentsPage.platformMac', 'macOS') },\n { value: 'linux', label: tr('agentsPage.platformLinux', 'Linux') },\n { value: 'windows', label: tr('agentsPage.platformWindows', 'Windows') },\n ];\n\n // Check permissions\n const canListAgents = hasPermission(VIBE_PERMISSIONS.AGENT_LIST);\n\n const [searchQuery, setSearchQuery] = useState('');\n const [statusFilter, setStatusFilter] = useState('all');\n const [platformFilter, setPlatformFilter] = useState('all');\n const [tagFilter, setTagFilter] = useState('all');\n const [viewMode, setViewMode] = useState<ViewMode>('grid');\n const [showCreateForm, setShowCreateForm] = useState(false);\n const [isCreating, setIsCreating] = useState(false);\n\n // Auto-open Add Agent modal when arriving via /agents/new or /agents?create=true\n // (dashboard widgets, deep links). Strip the param after opening so the back\n // button returns to the previous page instead of re-opening.\n const [searchParams, setSearchParams] = useSearchParams();\n useEffect(() => {\n if (searchParams.get('create') === 'true') {\n setShowCreateForm(true);\n const next = new URLSearchParams(searchParams);\n next.delete('create');\n setSearchParams(next, { replace: true });\n }\n }, [searchParams, setSearchParams]);\n const [editTunnelAgent, setEditTunnelAgent] = useState<Agent | null>(null);\n const [isSavingTunnel, setIsSavingTunnel] = useState(false);\n // Per-agent \"connecting\" overlay shown after Edit Tunnel save while the\n // backend pushes OAuth creds → agent finalize() → /health/ready flips to\n // ok. State lives in the global store (vibeControlsStore) so the overlay\n // survives navigation to AgentDetailsPage and back — the documented\n // recovery path navigates into the details page so pushAgentGatewayAuth\n // auto-recovery can fire, and storing the set in local React state used\n // to discard it on unmount, causing a CONNECTING → DEGRADED → ACTIVE\n // flicker on return.\n const connectingAgents = useVibeControlsStore((s) => s.connectingAgents);\n const markAgentConnecting = useVibeControlsStore((s) => s.markAgentConnecting);\n const clearAgentConnecting = useVibeControlsStore((s) => s.clearAgentConnecting);\n const connectingAgentIds = useMemo(\n () => new Set(Object.keys(connectingAgents)),\n [connectingAgents]\n );\n\n // 30s safety net — if a probe never reports healthy, drop the overlay\n // so the user eventually sees the real DEGRADED state.\n const connectingTimersRef = useRef<Map<string, ReturnType<typeof setTimeout>>>(new Map());\n useEffect(() => {\n const timers = connectingTimersRef.current;\n for (const agentId of Object.keys(connectingAgents)) {\n if (timers.has(agentId)) continue;\n const timer = setTimeout(() => {\n clearAgentConnecting(agentId);\n timers.delete(agentId);\n }, 30_000);\n timers.set(agentId, timer);\n }\n // Drop timers for agents no longer marked connecting.\n for (const [agentId, timer] of timers.entries()) {\n if (!(agentId in connectingAgents)) {\n clearTimeout(timer);\n timers.delete(agentId);\n }\n }\n return () => {\n for (const timer of timers.values()) clearTimeout(timer);\n timers.clear();\n };\n }, [connectingAgents, clearAgentConnecting]);\n const [disconnectConfirmAgent, setDisconnectConfirmAgent] = useState<Agent | null>(null);\n const [isDisconnecting, setIsDisconnecting] = useState(false);\n const tunnelUrlRef = useRef<HTMLInputElement>(null);\n const tunnelUrlField = useField({\n hint: tr(\n 'agentsPage.tunnelUrlHint',\n 'Leave empty to clear. Agents with auto-tunnel report this automatically on startup.'\n ),\n });\n\n // Bulk selection state\n const [selectedAgentIds, setSelectedAgentIds] = useState<Set<string>>(new Set());\n const [bulkLoading, setBulkLoading] = useState(false);\n const [bulkDeleteConfirmOpen, setBulkDeleteConfirmOpen] = useState(false);\n // Per-kind so the dialog copy (\"Agent Quota Reached\" vs \"Target Quota\n // Reached\") matches what the user just tried to create. The page hosts\n // both the agent-create form and the inline target-create row, so a\n // single boolean would mislabel one of them.\n const [quotaExhaustedKind, setQuotaExhaustedKind] = useState<QuotaKind | null>(null);\n\n const [createAgent] = useCreateAgentMutation({ refetchQueries: ['ListAgents'] });\n const { setDraftTagIds: setAgentDraftTagIds, flushTags: flushAgentTags } =\n useDraftEntityTags('VIBECONTROLS_AGENT');\n const [addAgentFromTunnel] = useAddAgentFromTunnelMutation({ refetchQueries: ['ListAgents'] });\n const [agentHeartbeat] = useAgentHeartbeatMutation({ refetchQueries: ['ListAgents'] });\n const [deactivateAgent] = useDeactivateAgentMutation({ refetchQueries: ['ListAgents'] });\n const [startAgentMutation] = useStartVibecontrolsAgentMutation({\n refetchQueries: ['ListAgents'],\n });\n const [stopAgentMutation] = useStopVibecontrolsAgentMutation({ refetchQueries: ['ListAgents'] });\n const [setupGatewayAuth] = useSetupAgentGatewayAuthMutation({\n refetchQueries: ['ListAgents'],\n });\n\n const { refetch: refetchInitialData } = useCachedInitialData();\n const {\n data: targetsData,\n loading: targetsLoading,\n refetch: refetchTargets,\n } = useListTargetsQuery({\n // Bound the list: this query hydrates per-target agents, so leaving it\n // unbounded was the heaviest contributor to slow Agents/Targets loads (the\n // 25s OPERATION_TIMEOUT class). AgentsPage only needs targets for the\n // \"Add Agent\" picker / hasTargets flag, so a page of 50 is plenty.\n variables: { workspaceId: workspaceId ?? '', limit: 50, offset: 0 },\n skip: !workspaceId,\n fetchPolicy: 'cache-and-network',\n });\n const targetsList = targetsData?.targets?.items ?? [];\n const hasTargets = targetsList.length > 0;\n const [updateAgent] = useUpdateAgentMutation({ refetchQueries: ['ListAgents'] });\n const [deleteAgent] = useDeleteAgentMutation({ refetchQueries: ['ListAgents'] });\n // refetchQueries + awaitRefetchQueries: the inline create flow used to fire\n // refetchInitialData() without awaiting it, so AgentForm closed and\n // re-rendered against stale `cachedTargets` — the new target only appeared\n // on full page reload. Awaiting the underlying queries here makes the\n // mutation resolve only after the initial-data + targets caches are fresh.\n const [createTargetMutation] = useCreateTargetMutation({\n refetchQueries: ['VibeControlsInitialData', 'ListTargets'],\n awaitRefetchQueries: true,\n });\n\n /**\n * Inline DIRECT target creation callback for the AgentForm. Returns\n * the newly created target so the form can auto-select it.\n */\n const handleInlineCreateTarget = useCallback(\n async (targetName: string) => {\n if (!workspaceId || !targetName.trim()) return null;\n try {\n // errorPolicy: 'all' (shell Apollo client) puts server errors on\n // `result.error` instead of rejecting — read both paths so quota\n // errors don't silently fall back to the generic inline copy.\n const result = await createTargetMutation({\n variables: {\n workspaceId,\n input: {\n type: 'DIRECT',\n name: targetName.trim(),\n },\n },\n });\n if (result.error && isQuotaExhaustedError(result.error)) {\n // Open the upgrade-your-plan modal at this level and re-throw\n // so AgentForm's catch sees a quota error rather than a\n // silent null (which it would otherwise render as a vague\n // \"Failed to create target. Please try again.\" inline string).\n markQuotaHandledLocally();\n setQuotaExhaustedKind('target');\n throw result.error;\n }\n const created = result.data?.createTarget;\n if (!created) return null;\n refetchInitialData();\n void refetchTargets();\n return {\n id: created.id,\n name: created.name,\n type: created.type ?? 'DIRECT',\n status: created.status ?? 'unknown',\n };\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n markQuotaHandledLocally();\n setQuotaExhaustedKind('target');\n throw err;\n }\n return null;\n }\n },\n [createTargetMutation, refetchInitialData, refetchTargets, workspaceId]\n );\n\n // Fetch agents from GraphQL API\n // Skip until a workspace is selected: the resolver requires\n // x-workspace-id and throws \"Workspace context required\" when fired\n // before the auth provider has resolved a workspace (URL with ?org but\n // no &workspace, post-context-switcher self-heal, etc.). Without this\n // skip, the page lands on \"Failed to load agents\" and never recovers\n // because Apollo's errorPolicy: 'all' returns the error and doesn't\n // auto-refetch when workspaceId becomes available.\n const { data, loading, error, refetch } = useListAgentsQuery({\n variables: {\n filter: {\n isActive: statusFilter === 'ACTIVE' ? true : statusFilter === 'OFFLINE' ? false : undefined,\n platform: platformFilter !== 'all' ? platformFilter : undefined,\n },\n pagination: { limit: 100 },\n },\n fetchPolicy: 'cache-first',\n skip: !workspaceId,\n });\n\n // Live deep-health probe — backend hits /health/ready on every agent and\n // returns per-agent { healthy, errorMessage }. Polled every 30s so stale\n // tunnels (cloudflared dead, agent crashed but `isActive` still true)\n // flip the badge to DEGRADED before users hit \"create session\" against\n // an unreachable agent.\n // Initial query primes the cache; the subscription below replaces the\n // prior 30s pollInterval so badges flip to/from DEGRADED in <1s.\n // Two probe sources combined:\n //\n // 1) `useVibecontrolsAgentHealthProbesQuery` — auto-fires on mount and\n // primes `probesById` with a complete probe array in one round-trip.\n // Without this every fresh mount shows REGISTERING until the first\n // subscription event arrives, which can be many seconds when the\n // graphql-ws upgrade is slow (browsers serialize the WS handshake\n // behind the in-flight Apollo HTTP queue). The query fetches once\n // on mount AND every 30s as a fallback for environments where the\n // subscription transport is unavailable.\n //\n // 2) `useVibecontrolsAgentHealthProbeStreamSubscription` — server pushes\n // the full probe array on every change (sub-second updates) plus a\n // 30s heartbeat. When the stream is live this overrides the query\n // result in `probesById` (newest wins).\n //\n // The lazy variant is kept solely so the Refresh button can force an\n // immediate fetch without changing the auto-fired hook's variables.\n // Defer the probe query + the WS subscription until after first paint so the\n // graphql-ws handshake + probe fetch leave the critical path. The agent list\n // renders immediately (deriveAgentStatus shows REGISTERING while probesLoading)\n // and statuses stream in a tick later.\n const [probesDeferred, setProbesDeferred] = useState(false);\n useEffect(() => {\n const t = window.setTimeout(() => setProbesDeferred(true), 0);\n return () => window.clearTimeout(t);\n }, []);\n const probeQuery = useVibecontrolsAgentHealthProbesQuery({\n fetchPolicy: 'cache-and-network',\n nextFetchPolicy: 'cache-first',\n notifyOnNetworkStatusChange: true,\n skip: !workspaceId || !probesDeferred,\n // WS subscription is the live source; this poll is just a fallback for\n // environments without WS, so 60s (was 30s) is plenty and halves the load.\n pollInterval: 60_000,\n });\n const [refetchProbes] = useVibecontrolsAgentHealthProbesLazyQuery({\n fetchPolicy: 'network-only',\n });\n const { data: probeStreamData } = useVibecontrolsAgentHealthProbeStreamSubscription({\n skip: !workspaceId || !probesDeferred,\n });\n // True when no probe data has been delivered for this mount cycle yet.\n // Used by deriveAgentStatus to show REGISTERING for agents we have not\n // yet probed (instead of optimistically ACTIVE).\n const probesLoading = probeQuery.data === undefined && probeStreamData === undefined;\n const probesById = useMemo(() => {\n const m = new Map<string, AgentHealthProbe>();\n // Subscription stream wins over the initial query — strictly newer.\n const probes =\n probeStreamData?.vibecontrolsAgentHealthProbeStream ??\n probeQuery.data?.vibecontrolsAgentHealthProbes ??\n [];\n for (const p of probes) {\n m.set(p.agentId, {\n agentId: p.agentId,\n healthy: p.healthy,\n errorMessage: p.errorMessage,\n });\n }\n return m;\n }, [probeQuery.data, probeStreamData]);\n\n // Map GraphQL response to Agent type\n const agents: Agent[] = useMemo(() => {\n const rawAgents = data?.vibecontrolsAgents ?? [];\n return rawAgents.map((agent: NonNullable<typeof rawAgents>[number]) => ({\n id: agent.id,\n workspaceId: agent.workspaceId,\n name: agent.name,\n hostname: agent.hostname,\n platform: agent.platform,\n architecture: agent.architecture,\n version: agent.version,\n isActive: agent.isActive,\n status: connectingAgentIds.has(agent.id)\n ? ('CONNECTING' as const)\n : deriveAgentStatus(\n {\n id: agent.id,\n isActive: agent.isActive,\n metadata: agent.metadata as Record<string, unknown> | null,\n },\n probesById,\n { probesLoading }\n ),\n // Prefer the live health probe error. When the live probe didn't run\n // (or returned ok) but the agent's most-recent gateway-auth push\n // failed, surface that instead — otherwise the only signal of a\n // failed onboarding is a yellow Degraded badge with no hover text.\n // `pending` is hidden: it just means the async finalize is still\n // running and the badge already shows the right transient state.\n healthError:\n probesById.get(agent.id)?.errorMessage ??\n (agent.lastGatewayAuthPushStatus === 'failed'\n ? (agent.lastGatewayAuthPushError ?? 'Gateway-auth push failed')\n : null),\n lastHeartbeat: agent.lastHeartbeat || null,\n tunnelUrl: agent.tunnelUrl ?? null,\n tunnelStatus: agent.tunnelStatus ?? null,\n configuration: (agent.configuration as Record<string, unknown>) || {},\n capabilities: Array.isArray(agent.capabilities)\n ? (agent.capabilities as string[])\n : typeof agent.capabilities === 'object' && agent.capabilities !== null\n ? (() => {\n const obj = agent.capabilities as Record<string, unknown>;\n const keys = Object.keys(obj);\n // When capabilities was serialized from an array, keys are numeric (\"0\",\"1\",\"2\")\n // In that case, extract values (the actual capability names) instead of keys\n const hasNumericKeys = keys.length > 0 && keys.every((k) => /^\\d+$/.test(k));\n return hasNumericKeys ? (Object.values(obj) as string[]) : (keys as string[]);\n })()\n : [],\n metadata: (agent.metadata as Record<string, unknown>) || {},\n createdAt: agent.createdAt,\n updatedAt: agent.updatedAt,\n tagAssignments: (agent.tagAssignments ?? []).map(\n (a: NonNullable<typeof agent.tagAssignments>[number]) => ({\n id: a.id,\n tagId: a.tagId,\n tag: a.tagInfo\n ? {\n id: a.tagInfo.id,\n key: a.tagInfo.key ?? null,\n label: a.tagInfo.label ?? null,\n color: a.tagInfo.color ?? null,\n }\n : null,\n })\n ),\n }));\n }, [data?.vibecontrolsAgents, probesById, probesLoading, connectingAgentIds]);\n\n // Clear CONNECTING overlay only when ALL of these hold:\n // 1. The live probe reports `healthy: true` (deep-health probe, which\n // already gates on session.ok + tunnel.ok + ttyd.ok server-side —\n // see probeAgentHealth in wspace-vibecontrols-svc).\n // 2. The grace-window timestamp stored at markAgentConnecting time has\n // elapsed. This defends against a STALE `healthy:true` probe\n // delivered from the Apollo cache during the in-flight refetch\n // that follows updateAgent — without the grace window, that stale\n // cache hit cleared CONNECTING immediately and the next (fresh,\n // transient) probe landing during cloudflared restart flipped the\n // badge to DEGRADED, producing the CONNECTING → DEGRADED → ACTIVE\n // flicker reported by users.\n //\n // Re-runs on `probesById` updates AND on a one-shot timer per pending\n // agent so the clear fires automatically once the grace window elapses\n // even if no new probe arrives.\n useEffect(() => {\n const pending = Object.keys(connectingAgents);\n if (pending.length === 0) return;\n const now = Date.now();\n let earliestPending = Infinity;\n for (const agentId of pending) {\n const minClearAt = connectingAgents[agentId];\n if (minClearAt === undefined) continue;\n const probe = probesById.get(agentId);\n if (probe?.healthy === true && now >= minClearAt) {\n clearAgentConnecting(agentId);\n } else if (minClearAt < earliestPending) {\n earliestPending = minClearAt;\n }\n }\n if (earliestPending === Infinity) return;\n const wait = Math.max(0, earliestPending - now) + 50;\n const tick = setTimeout(() => {\n // No-op state poke — the dependency on `connectingAgents` causes\n // this effect to re-run when state changes, but a healthy probe\n // arriving BEFORE the grace window elapses needs a re-evaluation\n // when the window finally closes. Re-marking with graceMs=0 is a\n // no-op when the entry is already past its window; if cleared in\n // the meantime, the early `pending.length === 0` short-circuit\n // makes this safe.\n const fresh = useVibeControlsStore.getState().connectingAgents;\n for (const agentId of Object.keys(fresh)) {\n const minClearAt = fresh[agentId];\n if (minClearAt === undefined) continue;\n const probe = probesById.get(agentId);\n if (probe?.healthy === true && Date.now() >= minClearAt) {\n clearAgentConnecting(agentId);\n }\n }\n }, wait);\n return () => clearTimeout(tick);\n }, [probesById, connectingAgents, clearAgentConnecting]);\n\n // Filter agents based on search and tag (client-side filtering).\n // Status / platform filters are applied server-side via the GraphQL filter\n // input above; these client-side filters compose on top of that result set.\n const filteredAgents = useMemo(() => {\n return agents.filter((agent) => {\n // Search filter\n if (searchQuery) {\n const query = searchQuery.toLowerCase();\n const matchesSearch =\n agent.name.toLowerCase().includes(query) ||\n agent.hostname?.toLowerCase().includes(query) ||\n agent.id.toLowerCase().includes(query);\n if (!matchesSearch) return false;\n }\n\n // Tag filter — match against structured tagAssignments (UUIDs).\n // Never falls back to the legacy free-text agent.tags array because\n // those values are not the same as tagFilterIds and would silently\n // produce empty results (the failure mode that got the previous\n // TagFilter implementation removed in commit 4e101ca).\n if (tagFilter !== 'all') {\n const hasTag = (agent.tagAssignments ?? []).some(\n (a: { tagId: string }) => a.tagId === tagFilter\n );\n if (!hasTag) return false;\n }\n\n return true;\n });\n }, [agents, searchQuery, tagFilter]);\n\n const handleAgentSelect = (agent: Agent) => {\n // Navigate to agent details page\n handleNavigate(`/agents/${agent.id}`);\n // Also call the shell callback if provided\n onAgentSelect?.({\n id: agent.id,\n name: agent.name,\n type: agent.platform || 'unknown',\n status: agent.status || (agent.isActive ? 'ACTIVE' : 'OFFLINE'),\n });\n };\n\n const handleCreateAgent = () => {\n setShowCreateForm(true);\n bus.emitCustom('vibecontrols.agent.register_clicked', {\n workspaceId: workspaceId ?? undefined,\n });\n };\n\n const handleCreateAgentSubmit = async (\n formData: Record<string, unknown>\n ): Promise<{ id: string } | void> => {\n try {\n setIsCreating(true);\n\n const tunnelUrl = (formData.tunnelUrl as string | undefined)?.trim();\n const agentApiKey = (formData.agentApiKey as string | undefined)?.trim();\n\n // Zero-config path: operator pasted the agent's tunnel URL + API key\n // shown in its startup banner. Backend probes the agent, registers it,\n // mints an OAuth app, and pushes credentials — the agent promotes\n // itself to `ready` inside that final POST /api/agent/gateway-auth.\n if (tunnelUrl && agentApiKey) {\n const result = await addAgentFromTunnel({\n variables: {\n input: {\n tunnelUrl,\n apiKey: agentApiKey,\n ...(formData.name ? { name: formData.name as string } : {}),\n ...(formData.targetId ? { targetId: formData.targetId as string } : {}),\n },\n },\n });\n if (result.error) throw result.error;\n const payload = result.data?.addVibecontrolsAgentFromTunnel;\n if (!payload?.success) {\n throw new Error(\n payload?.error || 'Failed to onboard agent — probe or OAuth setup failed.'\n );\n }\n if (payload.agent?.id) {\n await flushAgentTags(payload.agent.id);\n bus.emitCustom('vibecontrols.agent.registered', {\n workspaceId: workspaceId ?? undefined,\n agentId: payload.agent.id,\n });\n bus.emitCustom('vibecontrols.agent.gateway_auth_setup', {\n workspaceId: workspaceId ?? undefined,\n agentId: payload.agent.id,\n });\n return { id: payload.agent.id };\n }\n throw new Error('Agent onboarding returned no agent record.');\n }\n\n // Legacy manual path — operator typed hostname/platform/etc directly.\n const result = await createAgent({\n variables: {\n input: {\n name: formData.name as string,\n hostname: formData.hostname as string,\n platform: formData.platform as string,\n architecture: formData.architecture as string,\n version: formData.version as string,\n ...(formData.targetId ? { targetId: formData.targetId as string } : {}),\n ...(formData.tunnelUrl ? { tunnelUrl: formData.tunnelUrl as string } : {}),\n ...(formData.tunnelStatus ? { tunnelStatus: formData.tunnelStatus as string } : {}),\n },\n },\n });\n\n const createdAgent = result.data?.createVibecontrolsAgent;\n if (createdAgent?.id) {\n await flushAgentTags(createdAgent.id);\n bus.emitCustom('vibecontrols.agent.registered', {\n workspaceId: workspaceId ?? undefined,\n agentId: createdAgent.id,\n });\n return { id: createdAgent.id };\n }\n\n if (result.error) {\n throw result.error;\n }\n\n throw new Error('Agent creation failed — no data returned from the server.');\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n markQuotaHandledLocally();\n setQuotaExhaustedKind('agent');\n setShowCreateForm(false);\n bus.emitCustom('vibecontrols.quota.agent_limit_hit', {\n workspaceId: workspaceId ?? undefined,\n });\n return;\n }\n\n console.error('Failed to create agent:', err);\n throw err;\n } finally {\n setIsCreating(false);\n }\n };\n\n const handleStartAgent = async (agent: Agent) => {\n try {\n const result = await startAgentMutation({ variables: { agentId: agent.id } });\n const payload = result.data?.startVibecontrolsAgent;\n\n // Smart fallback: an agent in bootState=awaiting-config rejects\n // lifecycle/start with \"Agent not yet configured\" because it has\n // no OAuth credentials yet. The right recovery is\n // setupAgentGatewayAuth (the same mutation behind the\n // \"Re-push credentials\" button on the Security tab). Fire it\n // automatically so a single Reconnect click handles both the\n // common case and the awaiting-config case.\n if (!payload?.success) {\n const needsConfig = /not yet configured|awaiting[-\\s_]config/i.test(payload?.error ?? '');\n if (needsConfig) {\n const setupResult = await setupGatewayAuth({\n variables: { input: { agentId: agent.id } },\n });\n const setup = setupResult.data?.setupAgentGatewayAuth;\n if (setup?.error) {\n throw new Error(\n `Agent needs credentials. Auto re-push failed: ${setup.error}. Try \"Re-push credentials\" in Security tab.`\n );\n }\n if (!setup) {\n throw new Error(\n 'Agent needs credentials. Auto re-push returned no result. Try \"Re-push credentials\" in Security tab.'\n );\n }\n // `setupAgentGatewayAuth.error` is null when the OAuth app +\n // devportal app are created successfully, but the credentials\n // PUSH to the agent's tunnel can still fail downstream (the\n // svc records this as `lastGatewayAuthPushStatus: \"failed\"` +\n // `lastGatewayAuthPushError` on the agent row). The push fails\n // when the agent's finalize() hangs trying to authenticate\n // against gateway URLs it can't reach — most commonly when an\n // agent is registered against a workspace whose gateway URLs\n // are dev-local (e.g. *.local.burdenoff.com) and the agent\n // runs on a different machine. Surface that specific failure\n // rather than claiming success.\n const pushStatus = setup.agent?.lastGatewayAuthPushStatus;\n const pushError = setup.agent?.lastGatewayAuthPushError;\n if (pushStatus === 'failed') {\n throw new Error(\n `Credentials created but push to agent tunnel failed: ${\n pushError || 'unknown error'\n }. Check that the agent can reach the workspace's gateway URLs from its host network.`\n );\n }\n // PR #136 review (claude MEDIUM BUG): setupAgentGatewayAuth\n // pushes creds → agent finalize → bootState=ready. But that\n // doesn't call lifecycle/start. If the agent was previously\n // STOPPED (not just awaiting-config), it'd be credentialed\n // but not running, while the toast claimed \"and started\".\n // Retry the original lifecycle/start now that the agent\n // should have left awaiting-config; surface its outcome\n // explicitly so the toast doesn't lie either way.\n const retryResult = await startAgentMutation({\n variables: { agentId: agent.id },\n });\n const retryPayload = retryResult.data?.startVibecontrolsAgent;\n if (!retryPayload?.success) {\n throw new Error(\n `Credentials pushed but lifecycle/start failed: ${\n retryPayload?.error || 'unknown error'\n }. The agent has fresh credentials; try clicking Reconnect again or check vibe status on the host.`\n );\n }\n bus.emitCustom('vibecontrols.agent.gateway_auth_setup', {\n workspaceId: workspaceId ?? undefined,\n agentId: agent.id,\n });\n await agentHeartbeat({ variables: { id: agent.id } });\n notify.success(`Agent \"${agent.name}\" re-credentialed and started.`);\n return;\n }\n throw new Error(payload?.error || 'Failed to start agent');\n }\n\n await agentHeartbeat({ variables: { id: agent.id } });\n notify.success(`Agent \"${agent.name}\" started`);\n } catch (err) {\n console.error('Failed to start agent:', err);\n notify.error(err instanceof Error ? err.message : 'Failed to start agent');\n }\n };\n\n // NOTE: Stop and Disconnect both call deactivateVibecontrolsAgent and produce the same\n // server-side list state (isActive: false). The distinction is that Disconnect additionally\n // clears tunnel fields (tunnelUrl, tunnelStatus). When a dedicated stopVibecontrolsAgent\n // mutation succeeds, we still deactivate in workspace state so the list reflects the agent as offline.\n const handleStopAgent = async (agent: Agent) => {\n try {\n const result = await stopAgentMutation({ variables: { agentId: agent.id } });\n const payload = result.data?.stopVibecontrolsAgent;\n\n if (!payload?.success) {\n throw new Error(payload?.error || 'Failed to stop agent');\n }\n\n await deactivateAgent({ variables: { id: agent.id } });\n notify.success(`Agent \"${agent.name}\" stopped`);\n } catch (err) {\n console.error('Failed to stop agent:', err);\n notify.error(err instanceof Error ? err.message : 'Failed to stop agent');\n }\n };\n\n const handleDisconnectAgent = (agent: Agent) => {\n setDisconnectConfirmAgent(agent);\n };\n\n const handleConfirmDisconnect = async () => {\n if (!disconnectConfirmAgent) return;\n const agent = disconnectConfirmAgent;\n setIsDisconnecting(true);\n // Deactivate + clear tunnel info so the agent appears fully disconnected.\n // Two sequential mutations with refetchQueries suppressed — a single refetch()\n // is called after both complete so the list doesn't flash with stale tunnel fields.\n // A single atomic disconnectVibecontrolsAgent backend mutation that handles\n // both deactivation and tunnel field clearing is tracked in vibecontrols-svc\n // backlog; until then the two-step approach below is the supported flow.\n try {\n await deactivateAgent({ variables: { id: agent.id }, refetchQueries: [] });\n } catch (err) {\n console.error('Failed to deactivate agent:', err);\n notify.error(err instanceof Error ? err.message : 'Failed to disconnect agent');\n setIsDisconnecting(false);\n setDisconnectConfirmAgent(null);\n // Early-return path: refetch here so the list reflects the deactivated state,\n // then return so the bottom refetch below does not also fire.\n await refetch();\n return;\n }\n try {\n // F4 Wave B: `agentApiKey` is no longer part of\n // `UpdateVibecontrolsAgentInput`. Clearing the tunnel fields is\n // enough — the secret is owned by the agent itself, not the svc.\n await updateAgent({\n variables: {\n id: agent.id,\n input: { tunnelUrl: null, tunnelStatus: null },\n },\n refetchQueries: [],\n });\n notify.success(`Agent \"${agent.name}\" disconnected`);\n } catch (err) {\n console.error('Failed to clear tunnel fields:', err);\n notify.error(\n `Agent marked offline, but failed to clear connection fields — clear them manually in Settings.`\n );\n }\n setIsDisconnecting(false);\n setDisconnectConfirmAgent(null);\n // Both updateAgent success and updateAgent failure reach here — single refetch covers both.\n await refetch();\n };\n\n const handleConfigureAgent = (agent: Agent) => {\n // Navigate to agent details page (settings tab)\n handleNavigate(`/agents/${agent.id}`);\n };\n\n const handleEditTunnelUrl = (agent: Agent) => {\n setEditTunnelAgent(agent);\n };\n\n const handleSaveTunnelSettings = async () => {\n if (!editTunnelAgent) return;\n const agentId = editTunnelAgent.id;\n const newUrl = tunnelUrlRef.current?.value?.trim() || '';\n try {\n setIsSavingTunnel(true);\n // TODO(svc): rotate-API-key flow needs a dedicated svc mutation —\n // `UpdateVibecontrolsAgentInput.agentApiKey` was dropped by F4\n // Wave B. The Edit Tunnel dialog only updates the URL now.\n await updateAgent({\n variables: {\n id: agentId,\n input: {\n tunnelUrl: newUrl || null,\n tunnelStatus: newUrl ? 'ACTIVE' : null,\n },\n },\n // Force a fresh probe immediately so the badge resolves to the\n // real state (instead of relying on the 30s subscription cadence).\n refetchQueries: ['ListAgents', 'VibecontrolsAgentHealthProbes'],\n });\n // Begin the \"Connecting…\" overlay BEFORE clearing the dialog so the\n // operator sees an unbroken progress signal: dialog spinner →\n // CONNECTING badge with spinner → ACTIVE / DEGRADED.\n if (newUrl) markAgentConnecting(agentId);\n setEditTunnelAgent(null);\n } catch (err) {\n console.error('Failed to update tunnel settings:', err);\n } finally {\n setIsSavingTunnel(false);\n }\n };\n\n // ── Bulk Selection ─────────────────────────────────────────────────\n const toggleAgentSelection = useCallback((agentId: string) => {\n setSelectedAgentIds((prev) => {\n const next = new Set(prev);\n if (next.has(agentId)) {\n next.delete(agentId);\n } else {\n next.add(agentId);\n }\n return next;\n });\n }, []);\n\n const selectAllAgents = useCallback(() => {\n setSelectedAgentIds(new Set(filteredAgents.map((a) => a.id)));\n }, [filteredAgents]);\n\n const deselectAllAgents = useCallback(() => {\n setSelectedAgentIds(new Set());\n }, []);\n\n const isAllSelected =\n filteredAgents.length > 0 && selectedAgentIds.size === filteredAgents.length;\n const hasSelection = selectedAgentIds.size > 0;\n\n // ── Bulk Delete ──────────────────────────────────────────────────\n // Confirmation happens through the ConfirmDialog rendered below\n // (bulkDeleteConfirmOpen) — this handler runs only after the user\n // confirms there.\n const handleBulkDelete = useCallback(async () => {\n if (selectedAgentIds.size === 0) return;\n\n setBulkLoading(true);\n try {\n const results = await Promise.allSettled(\n Array.from(selectedAgentIds).map((id) => deleteAgent({ variables: { id } }))\n );\n const succeeded = results.filter((r) => r.status === 'fulfilled').length;\n const failed = results.filter((r) => r.status === 'rejected').length;\n if (failed > 0) {\n notify.error(\n `Removed ${succeeded}, failed to remove ${failed} agent${failed !== 1 ? 's' : ''}`\n );\n } else {\n notify.success(`Removed ${succeeded} agent${succeeded !== 1 ? 's' : ''}`);\n }\n setSelectedAgentIds(new Set());\n } catch (err) {\n notify.error(err instanceof Error ? err.message : 'Failed to remove agents');\n } finally {\n setBulkLoading(false);\n setBulkDeleteConfirmOpen(false);\n }\n }, [selectedAgentIds, deleteAgent]);\n\n // Stats\n //\n // \"online\" must mean the same thing the per-agent badge shows. The badge is\n // driven by `deriveAgentStatus` (live /health/ready probe + lifecycleState),\n // not by the persisted `isActive` flag — which is sticky: the backend sets it\n // true on every heartbeat but never flips it false when heartbeats stop, so a\n // stopped/crashed agent stays `isActive: true` indefinitely. Counting\n // `isActive` here is what produced \"2 of 2 agents online\" while both cards\n // showed DEGRADED. Count the derived status instead so the header and the\n // cards can never disagree.\n const stats = {\n total: agents.length,\n active: agents.filter((a) => a.status === 'ACTIVE').length,\n offline: agents.filter((a) => a.status !== 'ACTIVE').length,\n };\n\n // Guard states rendered inside MotionPage to prevent unmount/remount animation replay\n if (permissionsLoading) {\n return (\n <MotionPage className=\"space-y-6 px-6 lg:px-8 py-6 bg-bg-canvas min-h-full\">\n <LoadingState message={tr('agentsPage.loadingPermissions', 'Loading permissions...')} />\n </MotionPage>\n );\n }\n\n if (permissions && permissions.permissionStrings?.length > 0 && !canListAgents) {\n return (\n <MotionPage className=\"space-y-6 px-6 lg:px-8 py-6 bg-bg-canvas min-h-full\">\n <AccessDenied\n title={tr('agentsPage.accessDeniedTitle', 'Access Denied')}\n description={tr(\n 'agentsPage.accessDeniedDescription',\n \"You don't have permission to view agents. Contact your workspace administrator for access.\"\n )}\n />\n </MotionPage>\n );\n }\n\n if (error && agents.length === 0) {\n return (\n <MotionPage className=\"space-y-6 px-6 lg:px-8 py-6 bg-bg-canvas min-h-full\">\n <ErrorState\n title={tr('agentsPage.failedLoad', 'Failed to load agents')}\n message={error.message}\n onRetry={() => refetch()}\n />\n </MotionPage>\n );\n }\n\n return (\n <MotionPage className=\"space-y-6 px-6 lg:px-8 py-6 bg-bg-canvas min-h-full\">\n <PageHeader\n title={tr('agentsPage.title', 'Agents')}\n description={tr('agentsPage.descriptionOnline', '{{active}} of {{total}} agents online', {\n active: stats.active,\n total: stats.total,\n })}\n actions={\n <div className=\"flex w-full flex-col items-end gap-2 sm:w-auto sm:flex-row sm:items-center\">\n <button\n type=\"button\"\n onClick={() => {\n // Refresh both the agent list AND the live deep-health\n // probe so the badge reflects current state, not a stale\n // initial probe captured while the agent was still\n // booting. Without re-probing here the user clicks the\n // refresh icon and the row's name/tunnel reload but the\n // DEGRADED badge stays stuck until the next 30s tick.\n refetch();\n void refetchProbes();\n }}\n disabled={loading || probeQuery.loading}\n className=\"flex items-center justify-center gap-2 px-3 py-2 max-md:min-h-11 max-md:min-w-11 text-text-secondary hover:bg-bg-sunken hover:text-text-primary rounded-button transition-colors disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus\"\n aria-label={tr('agentsPage.refreshTitle', 'Refresh agents')}\n title={tr('agentsPage.refreshTitle', 'Refresh agents')}\n >\n <RefreshCw\n aria-hidden=\"true\"\n className={`size-4 ${loading || probeQuery.loading ? 'animate-spin' : ''}`}\n />\n </button>\n {hasTargets ? (\n <PermissionButton\n permission={VIBE_PERMISSIONS.AGENT_CREATE}\n deniedBehavior=\"disable\"\n deniedTooltip={tr(\n 'agentsPage.deniedCreateTooltip',\n \"You don't have permission to create agents\"\n )}\n >\n <button\n type=\"button\"\n onClick={handleCreateAgent}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus\"\n >\n <Plus className=\"size-4\" />\n {tr('agentsPage.addAgent', 'Add Agent')}\n </button>\n </PermissionButton>\n ) : (\n /* No targets yet — still open Add Agent; the form now\n supports inline target creation. */\n <button\n type=\"button\"\n onClick={handleCreateAgent}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-all duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus\"\n >\n <Plus className=\"size-4\" />\n {tr('agentsPage.addAgent', 'Add Agent')}\n </button>\n )}\n </div>\n }\n />\n\n {/* Agent CLI Setup Banner — shown when no agents exist */}\n {agents.length === 0 && !loading && (\n <AgentSetupBanner variant=\"full\" dismissKey=\"vibecontrols-agents-setup-banner\" />\n )}\n\n {/* Compact reminder when agents exist but shown once */}\n {agents.length > 0 && (\n <AgentSetupBanner variant=\"compact\" dismissKey=\"vibecontrols-agents-compact-banner\" />\n )}\n\n {/* Filters Bar */}\n <div className=\"space-y-3\">\n {/* Row 1: Search + View Toggle */}\n <div className=\"flex items-center gap-2\">\n <SearchInput\n value={searchQuery}\n onChange={setSearchQuery}\n placeholder={tr('agentsPage.searchPlaceholder', 'Search agents...')}\n className=\"flex-1 sm:w-64 sm:flex-none\"\n />\n {/* View Toggle */}\n <div className=\"flex items-center gap-1 bg-bg-sunken rounded-button p-1 flex-shrink-0 border border-border-subtle\">\n <button\n type=\"button\"\n onClick={() => {\n setViewMode('grid');\n setSelectedAgentIds(new Set());\n }}\n className={`flex items-center justify-center p-1.5 sm:p-2 max-md:min-h-11 max-md:min-w-11 rounded focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus ${viewMode === 'grid' ? 'bg-[var(--color-accent-soft)] text-action-primary-bg' : 'text-text-secondary hover:bg-bg-surface/60'}`}\n aria-label={tr('agentsPage.gridViewTitle', 'Grid view')}\n aria-pressed={viewMode === 'grid'}\n title={tr('agentsPage.gridViewTitle', 'Grid view')}\n >\n <Grid aria-hidden=\"true\" className=\"size-4 text-text-secondary\" />\n </button>\n <button\n type=\"button\"\n onClick={() => {\n setViewMode('list');\n setSelectedAgentIds(new Set());\n }}\n className={`flex items-center justify-center p-1.5 sm:p-2 max-md:min-h-11 max-md:min-w-11 rounded focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus ${viewMode === 'list' ? 'bg-[var(--color-accent-soft)] text-action-primary-bg' : 'text-text-secondary hover:bg-bg-surface/60'}`}\n aria-label={tr('agentsPage.listViewTitle', 'List view')}\n aria-pressed={viewMode === 'list'}\n title={tr('agentsPage.listViewTitle', 'List view')}\n >\n <List aria-hidden=\"true\" className=\"size-4 text-text-secondary\" />\n </button>\n </div>\n </div>\n\n {/* Row 2: Filters - wrap on all screens */}\n <div className=\"flex items-center gap-2 sm:gap-3 flex-wrap\">\n <FilterDropdown\n label={tr('agentsPage.filterStatus', 'Status')}\n value={statusFilter}\n options={statusOptions}\n onChange={setStatusFilter}\n />\n <FilterDropdown\n label={tr('agentsPage.filterPlatform', 'Platform')}\n value={platformFilter}\n options={platformOptions}\n onChange={setPlatformFilter}\n />\n <TagFilter\n label={tr('agentsPage.filterTag', 'Tag')}\n value={tagFilter}\n onChange={setTagFilter}\n />\n </div>\n </div>\n\n {/* Select All (grid view only — list view renders its own select-all\n affordance via AgentList's header checkbox / mobile button) */}\n {viewMode === 'grid' && filteredAgents.length > 0 && (\n <div className=\"flex justify-end\">\n <button\n type=\"button\"\n onClick={isAllSelected ? deselectAllAgents : selectAllAgents}\n aria-pressed={isAllSelected}\n className=\"flex items-center gap-1.5 px-3 py-1.5 max-md:min-h-11 text-xs text-text-secondary hover:text-text-primary rounded-md transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus\"\n >\n <CheckSquare aria-hidden=\"true\" className=\"size-3.5\" />\n {isAllSelected\n ? tr('agentsPage.deselectAll', 'Deselect All')\n : tr('agentsPage.selectAll', 'Select All')}\n </button>\n </div>\n )}\n\n {/* Bulk Action Bar */}\n {hasSelection && (\n <div className=\"flex flex-col sm:flex-row items-start sm:items-center gap-3 px-4 py-2.5 bg-[var(--color-accent-soft)] border border-border-subtle rounded-card\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('agentsPage.selectedCount', '{{count}} agents selected', {\n count: selectedAgentIds.size,\n })}\n </span>\n <div className=\"flex items-center gap-1.5 sm:ml-auto\">\n <button\n type=\"button\"\n onClick={() => setBulkDeleteConfirmOpen(true)}\n disabled={bulkLoading}\n className=\"flex items-center gap-1.5 px-3 py-1.5 max-md:min-h-11 text-xs font-medium text-status-error-text bg-status-error-bg-subtle border border-status-error-border rounded-button hover:bg-status-error-bg-subtle/80 transition-colors disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus\"\n >\n <Trash2 aria-hidden=\"true\" className=\"size-3\" /> {tr('agentsPage.remove', 'Remove')}\n </button>\n <button\n type=\"button\"\n onClick={deselectAllAgents}\n className=\"flex items-center gap-1.5 px-3 py-1.5 max-md:min-h-11 text-xs text-text-secondary hover:text-text-primary rounded-md transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus\"\n >\n <XSquare aria-hidden=\"true\" className=\"size-3\" />{' '}\n {tr('agentsPage.deselect', 'Deselect')}\n </button>\n </div>\n </div>\n )}\n\n {/* Results Count */}\n {searchQuery || statusFilter !== 'all' || platformFilter !== 'all' ? (\n <p className=\"text-sm text-text-secondary\">\n {tr('agentsPage.showingResults', 'Showing {{filtered}} of {{total}} agents', {\n filtered: filteredAgents.length,\n total: agents.length,\n })}\n {loading && (\n <span className=\"ml-2 text-text-muted\">\n {tr('agentsPage.refreshing', '(refreshing...)')}\n </span>\n )}\n </p>\n ) : null}\n\n {/* Agents Display */}\n {loading && agents.length === 0 ? (\n <PageSkeleton variant=\"rows\" count={6} />\n ) : filteredAgents.length > 0 ? (\n <AnimatePresence mode=\"wait\">\n {viewMode === 'grid' ? (\n <MotionList\n key=\"grid-view\"\n data-tour=\"agents-page-grid-header\"\n stagger={0.04}\n className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\"\n >\n {filteredAgents.map((agent) => (\n <MotionItem key={agent.id} className=\"h-full\">\n <AgentCard\n agent={agent}\n onSelect={handleAgentSelect}\n onStart={handleStartAgent}\n onStop={handleStopAgent}\n onDisconnect={handleDisconnectAgent}\n onConfigure={handleConfigureAgent}\n onEditTunnelUrl={handleEditTunnelUrl}\n selected={selectedAgentIds.has(agent.id)}\n onToggleSelect={() => toggleAgentSelection(agent.id)}\n healthErrorMessage={agent.healthError ?? null}\n />\n </MotionItem>\n ))}\n </MotionList>\n ) : (\n <MotionList key=\"list-view\" stagger={0.04}>\n <MotionItem variant=\"fadeRight\">\n <AgentList\n agents={filteredAgents}\n onSelect={handleAgentSelect}\n onStart={handleStartAgent}\n onStop={handleStopAgent}\n onDisconnect={handleDisconnectAgent}\n onSettings={handleConfigureAgent}\n selectedIds={selectedAgentIds}\n onToggleSelect={toggleAgentSelection}\n onSelectAll={selectAllAgents}\n onDeselectAll={deselectAllAgents}\n />\n </MotionItem>\n </MotionList>\n )}\n </AnimatePresence>\n ) : (\n <EmptyState\n icon={<Bot className=\"size-8 text-text-secondary\" />}\n title={\n searchQuery || statusFilter !== 'all' || platformFilter !== 'all'\n ? tr('agentsPage.emptyNoAgentsFound', 'No agents found')\n : tr('agentsPage.emptyNoAgentsConfigured', 'No agents configured')\n }\n description={\n searchQuery || statusFilter !== 'all' || platformFilter !== 'all'\n ? tr('agentsPage.emptyAdjustSearch', 'Try adjusting your search or filters')\n : tr(\n 'agentsPage.emptyNoAgentsDescription',\n 'Install the CLI agent on your machine, then add it here. You can create a target inline while adding the agent.'\n )\n }\n action={\n !searchQuery && statusFilter === 'all' && platformFilter === 'all' ? (\n <div className=\"flex flex-wrap items-center gap-3\">\n {/* Always open Add Agent — the form handles the \"no\n targets yet\" case via inline DIRECT target creation. */}\n <button\n type=\"button\"\n onClick={handleCreateAgent}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-all duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus\"\n >\n <Plus className=\"size-4\" />\n {tr('agentsPage.addAgent', 'Add Agent')}\n </button>\n <button\n type=\"button\"\n onClick={() => handleNavigate('/getting-started')}\n className=\"flex items-center gap-2 px-4 py-2 border border-border-subtle bg-bg-surface text-text-primary rounded-button hover:bg-bg-sunken hover:border-border-strong transition-all duration-200 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus\"\n >\n {tr('agentsPage.setupGuide', 'Setup guide')}\n </button>\n <GetHelpButton prompt=\"What is an Agent in VibeControls and how do I add one?\" />\n </div>\n ) : undefined\n }\n />\n )}\n\n {/* Create Agent Modal */}\n {showCreateForm && (\n <AgentForm\n onSubmit={handleCreateAgentSubmit}\n onCancel={() => setShowCreateForm(false)}\n isLoading={isCreating}\n onNavigate={(path) => {\n setShowCreateForm(false);\n handleNavigate(path);\n }}\n targets={targetsList.map(\n (t: { id: string; name: string; type?: string; status: string }) => ({\n id: t.id,\n name: t.name,\n type: t.type || 'DIRECT',\n status: t.status,\n })\n )}\n targetsLoading={targetsLoading}\n onCreateTarget={handleInlineCreateTarget}\n extraSection={\n <div>\n <h3 className=\"text-sm font-medium text-text-secondary mb-2\">\n {tr('agents.tags', 'Tags')}\n </h3>\n <EntityTagPicker\n entityType=\"VIBECONTROLS_AGENT\"\n onDraftChange={setAgentDraftTagIds}\n />\n </div>\n }\n />\n )}\n\n {/* Disconnect Confirmation Dialog */}\n <ConfirmDialog\n isOpen={disconnectConfirmAgent !== null}\n onClose={() => {\n if (!isDisconnecting) setDisconnectConfirmAgent(null);\n }}\n onConfirm={handleConfirmDisconnect}\n title={tr('agentsPage.disconnectTitle', 'Disconnect Agent')}\n message={\n disconnectConfirmAgent\n ? deriveAgentStatus(disconnectConfirmAgent) === 'OFFLINE'\n ? tr(\n 'agentsPage.disconnectOfflineMessage',\n 'Clear the stored tunnel connection for {{name}}? This will remove the tunnel URL and API key from this offline agent.',\n { name: disconnectConfirmAgent.name }\n )\n : tr(\n 'agentsPage.disconnectActiveMessage',\n 'Mark {{name}} as offline and clear its tunnel connection? The agent process will not be stopped automatically — you may need to stop it manually on the host machine.',\n { name: disconnectConfirmAgent.name }\n )\n : ''\n }\n confirmLabel={tr('agentsPage.disconnectConfirm', 'Disconnect')}\n variant=\"danger\"\n isLoading={isDisconnecting}\n />\n\n {/* Edit Tunnel Dialog */}\n <ResponsiveDialog\n open={editTunnelAgent !== null}\n onOpenChange={(open) => {\n if (!open) setEditTunnelAgent(null);\n }}\n >\n {editTunnelAgent ? (\n <ResponsiveDialogContent className=\"sm:max-w-md\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>\n {tr('agentsPage.editTunnelTitle', 'Edit Tunnel')}\n </ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n {tr(\n 'agentsPage.editTunnelDescription',\n 'Configure the cloudflared tunnel URL and API key for {{name}}.',\n {\n name: editTunnelAgent.name,\n }\n )}\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"py-4\">\n <label\n htmlFor={tunnelUrlField.id}\n className=\"mb-1.5 block text-sm font-medium text-text-primary\"\n >\n {tr('agentsPage.tunnelUrlLabel', 'Tunnel URL')}\n </label>\n <Input\n {...tunnelUrlField.inputProps}\n ref={tunnelUrlRef}\n type=\"url\"\n defaultValue={editTunnelAgent.tunnelUrl || ''}\n placeholder={tr(\n 'agentsPage.tunnelUrlPlaceholder',\n 'https://example.trycloudflare.com'\n )}\n className=\"text-base md:text-sm\"\n />\n <p id={tunnelUrlField.hintId} className=\"mt-1.5 text-xs text-text-muted\">\n {tr(\n 'agentsPage.tunnelUrlHint',\n 'Leave empty to clear. Agents with auto-tunnel report this automatically on startup.'\n )}\n </p>\n {/* F4 Wave B: API key rotation removed from this dialog —\n `UpdateVibecontrolsAgentInput.agentApiKey` was dropped\n by the svc and key rotation now requires a dedicated\n rotate mutation (TODO). The agent's key still surfaces\n on the agent details page via the lazy reveal query. */}\n </div>\n <ResponsiveDialogFooter>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => setEditTunnelAgent(null)}\n className=\"max-md:min-h-11\"\n >\n {tr('agentsPage.cancel', 'Cancel')}\n </Button>\n <Button\n type=\"button\"\n onClick={handleSaveTunnelSettings}\n disabled={isSavingTunnel}\n aria-busy={isSavingTunnel}\n className=\"max-md:min-h-11\"\n >\n {isSavingTunnel\n ? tr('agentsPage.saving', 'Saving...')\n : tr('agentsPage.save', 'Save')}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n ) : null}\n </ResponsiveDialog>\n\n {/* Bulk Delete Confirmation Dialog (replaces window.confirm) */}\n <ConfirmDialog\n isOpen={bulkDeleteConfirmOpen}\n onClose={() => {\n if (!bulkLoading) setBulkDeleteConfirmOpen(false);\n }}\n onConfirm={handleBulkDelete}\n title={tr('agentsPage.bulkDeleteTitle', 'Remove agents')}\n message={tr(\n 'agentsPage.bulkDeleteConfirm',\n 'Are you sure you want to remove {{count}} agent(s)? This cannot be undone.',\n { count: selectedAgentIds.size }\n )}\n confirmLabel={tr('agentsPage.remove', 'Remove')}\n variant=\"danger\"\n isLoading={bulkLoading}\n />\n\n {/* Quota Exhausted Dialog — copy switches based on the entity the\n user just tried to create (agent vs inline target). */}\n <QuotaExhaustedDialog\n isOpen={quotaExhaustedKind !== null}\n onClose={() => setQuotaExhaustedKind(null)}\n onConfirm={() => {\n setQuotaExhaustedKind(null);\n handleNavigate('/billing/plans');\n }}\n title={getQuotaDialogContent(quotaExhaustedKind ?? 'generic').title}\n message={getQuotaDialogContent(quotaExhaustedKind ?? 'generic').message}\n />\n </MotionPage>\n );\n}\n"],"mappings":"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"}