@burdenoff/microfe-vibecontrols 2026.624.1 → 2026.624.3

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 (33) hide show
  1. package/dist/VibeControlsRoutes.js +1 -1
  2. package/dist/VibeControlsRoutes.js.map +1 -1
  3. package/dist/components/agents/AgentCard.js +33 -36
  4. package/dist/components/agents/AgentCard.js.map +1 -1
  5. package/dist/components/agents/AgentStatusBadge.js +1 -1
  6. package/dist/components/agents/AgentStatusBadge.js.map +1 -1
  7. package/dist/components/agents/ConnectionLogPanel.js +1 -1
  8. package/dist/components/agents/ConnectionLogPanel.js.map +1 -1
  9. package/dist/components/agents/PluginUIPanel.js +1 -1
  10. package/dist/components/agents/PluginUIPanel.js.map +1 -1
  11. package/dist/components/audit/AuditLogTable.js +1 -1
  12. package/dist/components/audit/AuditLogTable.js.map +1 -1
  13. package/dist/components/docs/DocsFullScreenViewer.js +1 -1
  14. package/dist/components/docs/DocsFullScreenViewer.js.map +1 -1
  15. package/dist/components/notes/NoteEditor.js +1 -1
  16. package/dist/components/notes/NoteEditor.js.map +1 -1
  17. package/dist/components/sessions/TerminalPanel.js +3 -3
  18. package/dist/components/sessions/TerminalPanel.js.map +1 -1
  19. package/dist/components/shared/ActionMenu.js +1 -1
  20. package/dist/components/shared/ActionMenu.js.map +1 -1
  21. package/dist/components/shared/ErrorState.js +1 -1
  22. package/dist/components/shared/ErrorState.js.map +1 -1
  23. package/dist/components/vibecalendar/ScheduleTaskModal.js +3 -3
  24. package/dist/components/vibecalendar/ScheduleTaskModal.js.map +1 -1
  25. package/dist/components/vibecalendar/types.js +5 -5
  26. package/dist/components/vibecalendar/types.js.map +1 -1
  27. package/dist/pages/NoteDetailsPage.js +2 -2
  28. package/dist/pages/NoteDetailsPage.js.map +1 -1
  29. package/dist/pages/SessionDetailsPage.js +1 -1
  30. package/dist/pages/SessionDetailsPage.js.map +1 -1
  31. package/dist/pages/TargetsPage.js +3 -3
  32. package/dist/pages/TargetsPage.js.map +1 -1
  33. package/package.json +1 -1
@@ -16,7 +16,7 @@ var p = t.lazy(() => import("./pages/OverviewPage.js").then((e) => ({ default: e
16
16
  function ae() {
17
17
  return /* @__PURE__ */ l("div", {
18
18
  className: "flex items-center justify-center h-full min-h-[200px]",
19
- children: /* @__PURE__ */ l("div", { className: "animate-pulse bg-muted rounded w-full h-full" })
19
+ children: /* @__PURE__ */ l("div", { className: "animate-pulse bg-bg-sunken rounded w-full h-full" })
20
20
  });
21
21
  }
22
22
  function oe() {
@@ -1 +1 @@
1
- {"version":3,"file":"VibeControlsRoutes.js","names":[],"sources":["../src/VibeControlsRoutes.tsx"],"sourcesContent":["import React, { Suspense, useEffect } from 'react';\nimport { Navigate, Route, Routes, useLocation, useParams, useNavigate } from 'react-router-dom';\nimport { FeatureGate } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { NotFoundPage } from '@burdenoff/fe-libs/shared/pages';\n\n/** Catch-all renderer: a friendly 404 inside the content area (was a broken\n * redirect to \"\" that left the page blank). \"Back to home\" returns to the\n * VibeControls overview via SPA navigation. */\nfunction VibeNotFound() {\n const navigate = useNavigate();\n return (\n <NotFoundPage homeLabel=\"Back to VibeControls\" onGoHome={() => navigate('/vibecontrols')} />\n );\n}\nimport { useTr } from './shared/hooks/useTr';\n\n// Lazy load - All pages (required for @/ alias resolution when consumed by host app)\nconst OverviewPage = React.lazy(() =>\n import('./pages/OverviewPage').then((m) => ({ default: m.OverviewPage }))\n);\n\n// Other pages\nconst AgentsLandingPage = React.lazy(() =>\n import('./pages/AgentsLandingPage').then((m) => ({ default: m.AgentsLandingPage }))\n);\nconst AgentDetailsPage = React.lazy(() =>\n import('./pages/AgentDetailsPage').then((m) => ({ default: m.AgentDetailsPage }))\n);\nconst TargetDetailsPage = React.lazy(() =>\n import('./pages/TargetDetailsPage').then((m) => ({ default: m.TargetDetailsPage }))\n);\nconst SessionsPage = React.lazy(() =>\n import('./pages/SessionsPage').then((m) => ({ default: m.SessionsPage }))\n);\nconst SessionDetailsPage = React.lazy(() =>\n import('./pages/SessionDetailsPage').then((m) => ({ default: m.SessionDetailsPage }))\n);\nconst VibesPage = React.lazy(() =>\n import('./pages/VibesPage').then((m) => ({ default: m.VibesPage }))\n);\nconst VibeDetailsPage = React.lazy(() =>\n import('./pages/VibeDetailsPage').then((m) => ({ default: m.VibeDetailsPage }))\n);\nconst NoteDetailsPage = React.lazy(() =>\n import('./pages/NoteDetailsPage').then((m) => ({ default: m.NoteDetailsPage }))\n);\nconst TunnelsLandingPage = React.lazy(() =>\n import('./pages/TunnelsLandingPage').then((m) => ({ default: m.TunnelsLandingPage }))\n);\nconst TunnelDetailsPage = React.lazy(() =>\n import('./pages/tunnels/TunnelDetailsPage').then((m) => ({ default: m.TunnelDetailsPage }))\n);\nconst TunnelCreateWizard = React.lazy(() =>\n import('./components/tunnels/TunnelCreateWizard').then((m) => ({ default: m.TunnelCreateWizard }))\n);\nconst SettingsPage = React.lazy(() =>\n import('./pages/SettingsPage').then((m) => ({ default: m.SettingsPage }))\n);\nconst HealthPage = React.lazy(() =>\n import('./pages/HealthPage').then((m) => ({ default: m.HealthPage }))\n);\nconst GettingStartedPage = React.lazy(() =>\n import('./pages/GettingStartedPage').then((m) => ({ default: m.GettingStartedPage }))\n);\nconst SharedSessionPage = React.lazy(() =>\n import('./pages/SharedSessionPage').then((m) => ({ default: m.SharedSessionPage }))\n);\nconst VibeDeckPage = React.lazy(() =>\n import('./pages/VibeDeckPage').then((m) => ({ default: m.VibeDeckPage }))\n);\nconst SharedVibeDeckPage = React.lazy(() =>\n import('./pages/SharedVibeDeckPage').then((m) => ({ default: m.SharedVibeDeckPage }))\n);\nconst VibeCalendarPage = React.lazy(() =>\n import('./pages/VibeCalendarPage').then((m) => ({ default: m.VibeCalendarPage }))\n);\nconst VibeTaskDetailPage = React.lazy(() =>\n import('./pages/VibeTaskDetailPage').then((m) => ({ default: m.VibeTaskDetailPage }))\n);\nconst ScheduledJobDetailPage = React.lazy(() =>\n import('./pages/ScheduledJobDetailPage').then((m) => ({ default: m.ScheduledJobDetailPage }))\n);\nconst ActionsPage = React.lazy(() =>\n import('./pages/ActionsPage').then((m) => ({ default: m.ActionsPage }))\n);\nconst VibeAuditPage = React.lazy(() =>\n import('./pages/VibeAuditPage').then((m) => ({ default: m.VibeAuditPage }))\n);\nconst AnalyticsPage = React.lazy(() =>\n import('./pages/AnalyticsPage').then((m) => ({ default: m.AnalyticsPage }))\n);\nconst VibeDocsPage = React.lazy(() =>\n import('./pages/VibeDocsPage').then((m) => ({ default: m.VibeDocsPage }))\n);\nconst LogsPage = React.lazy(() =>\n import('./pages/LogsPage').then((m) => ({ default: m.LogsPage }))\n);\n\n// Catalog, Components, Templates pages\nconst CatalogPage = React.lazy(() =>\n import('./pages/CatalogPage').then((m) => ({ default: m.CatalogPage }))\n);\nconst CatalogDetailPage = React.lazy(() =>\n import('./pages/CatalogDetailPage').then((m) => ({ default: m.CatalogDetailPage }))\n);\nconst CatalogDocsPage = React.lazy(() =>\n import('./pages/CatalogDocsPage').then((m) => ({ default: m.CatalogDocsPage }))\n);\nconst ComponentsPage = React.lazy(() =>\n import('./pages/ComponentsPage').then((m) => ({ default: m.ComponentsPage }))\n);\nconst EnvTopologyPage = React.lazy(() =>\n import('./pages/EnvTopologyPage').then((m) => ({ default: m.EnvTopologyPage }))\n);\nconst WebhooksPage = React.lazy(() =>\n import('./pages/WebhooksPage').then((m) => ({ default: m.WebhooksPage }))\n);\nconst ComponentDetailPage = React.lazy(() =>\n import('./pages/ComponentDetailPage').then((m) => ({ default: m.ComponentDetailPage }))\n);\nconst ComponentDocsPage = React.lazy(() =>\n import('./pages/ComponentDocsPage').then((m) => ({ default: m.ComponentDocsPage }))\n);\nconst TemplatesPage = React.lazy(() =>\n import('./pages/TemplatesPage').then((m) => ({ default: m.TemplatesPage }))\n);\nconst TemplateDetailPage = React.lazy(() =>\n import('./pages/TemplateDetailPage').then((m) => ({ default: m.TemplateDetailPage }))\n);\nconst PluginsCatalogPage = React.lazy(() =>\n import('./pages/PluginsCatalogPage').then((m) => ({ default: m.PluginsCatalogPage }))\n);\n\n// AI pages\nconst AIPage = React.lazy(() => import('./pages/ai/AIPage').then((m) => ({ default: m.AIPage })));\nconst AISessionDetailPage = React.lazy(() =>\n import('./pages/ai/AISessionDetailPage').then((m) => ({ default: m.AISessionDetailPage }))\n);\nconst AgentManagerPage = React.lazy(() =>\n import('./pages/ai/AgentManagerPage').then((m) => ({ default: m.AgentManagerPage }))\n);\nconst AIBookmarksPage = React.lazy(() =>\n import('./pages/ai/AIBookmarksPage').then((m) => ({ default: m.AIBookmarksPage }))\n);\nconst AIBuilderPage = React.lazy(() =>\n import('./pages/ai/AIBuilderPage').then((m) => ({ default: m.AIBuilderPage }))\n);\n\n// Loading fallback component\nfunction PageLoadingFallback() {\n return (\n <div className=\"flex items-center justify-center h-full min-h-[200px]\">\n <div className=\"animate-pulse bg-muted rounded w-full h-full\" />\n </div>\n );\n}\n\n/**\n * Internal router for the VibeControls microfrontend.\n * This component handles routing within the microfrontend using React Router v7.\n *\n * Routes:\n * - / → Overview page (dashboard)\n * - /agents → Agents management page\n * - /agents/:id → Agent details page\n * - /sessions → Sessions management page\n * - /sessions/:id → Session details page\n * - /vibes → Vibes (projects) management page\n * - /vibes/:id → Vibe details page (includes Notes tab)\n * - /settings → Settings page\n * - /webhooks → Legacy redirect to Developer Portal webhooks\n * - /health → Health monitoring page\n * - /* → Catch-all redirects to overview\n */\nfunction ScrollToTop() {\n const { pathname } = useLocation();\n useEffect(() => {\n // The scrollable container is the <main> element with overflow-auto\n // inside AppShellLayout, not the window itself.\n const main = document.querySelector('main');\n if (main) main.scrollTop = 0;\n }, [pathname]);\n return null;\n}\n\n/**\n * Legacy redirect: /ai-tools/<tab> → /ai-tools/ai/<tab>.\n *\n * Before the Plan / AI mode toggle the page was reachable directly at\n * /ai-tools/<tab>. New shape is /ai-tools/<mode>/<tab>. We keep the\n * legacy links working by mapping the captured `:tab` param: if it\n * resolves to a known AI sub-tab, redirect there; if it's literally\n * \"plan\", route to the Plan page; otherwise fall back to AI overview.\n */\nconst VALID_AI_TABS = new Set<string>([\n 'overview',\n 'templates',\n 'contexts',\n 'prompts',\n 'sessions',\n 'tasks',\n 'stats',\n 'playground',\n 'config',\n]);\n\nfunction LegacyAIToolsRedirect() {\n const { tab } = useParams<{ tab?: string }>();\n if (tab === 'plan') {\n return <Navigate to=\"/vibecontrols/ai-tools/plan\" replace />;\n }\n if (tab && VALID_AI_TABS.has(tab)) {\n return <Navigate to={`/vibecontrols/ai-tools/ai/${tab}`} replace />;\n }\n return <Navigate to=\"/vibecontrols/ai-tools/ai/overview\" replace />;\n}\n\n/**\n * Fallback shown when a feature flag is OFF.\n */\nfunction FeatureDisabledFallback() {\n const tr = useTr();\n return (\n <div className=\"flex items-center justify-center h-full min-h-[200px] text-text-secondary text-sm\">\n {tr(\n 'vibecontrols.pages.featureDisabled',\n 'This feature is currently disabled. You can enable it from Feature Previews.'\n )}\n </div>\n );\n}\n\n/**\n * Helper: wraps a page element in a FeatureGate.\n * defaultValue={true} means the feature shows by default (before flags are seeded).\n * Only hides when a flag is explicitly set to false.\n */\nfunction gated(flag: string, element: React.ReactNode, defaultValue = true): React.ReactElement {\n return (\n <FeatureGate flag={flag} defaultValue={defaultValue} fallback={<FeatureDisabledFallback />}>\n {element}\n </FeatureGate>\n );\n}\n\nexport function VibeControlsRoutes() {\n return (\n <Suspense fallback={<PageLoadingFallback />}>\n <ScrollToTop />\n <Routes>\n {/* Default route - overview page */}\n <Route index element={gated('vibecontrols.overview', <OverviewPage />)} />\n {/* Legacy /dashboard alias → overview */}\n <Route path=\"dashboard\" element={<Navigate to=\"/vibecontrols\" replace />} />\n\n {/* Core pages */}\n {/* Plugins catalog — searchable grid of every published plugin. */}\n <Route path=\"plugins\" element={gated('vibecontrols.vibes', <PluginsCatalogPage />)} />\n\n <Route path=\"agents\" element={<AgentsLandingPage />} />\n <Route path=\"agents/new\" element={<Navigate to=\"../agents?create=true\" replace />} />\n <Route path=\"agents/:id\" element={<AgentDetailsPage />} />\n <Route path=\"sessions\" element={<SessionsPage />} />\n <Route path=\"vibes\" element={gated('vibecontrols.vibes', <VibesPage />)} />\n <Route path=\"vibes/new\" element={<Navigate to=\"../vibes?create=true\" replace />} />\n <Route path=\"vibes/:id\" element={gated('vibecontrols.vibes', <VibeDetailsPage />)} />\n <Route path=\"vibes/:id/docs/*\" element={gated('vibecontrols.vibes', <VibeDocsPage />)} />\n <Route path=\"notes/:id\" element={gated('vibecontrols.vibes', <NoteDetailsPage />)} />\n <Route path=\"sessions/:id\" element={<SessionDetailsPage />} />\n {/* Agent Manager — multi-session tabbed UI (replaces old AI page) */}\n <Route path=\"ai\" element={gated('vibecontrols.ai', <AgentManagerPage />)} />\n {/* Saved messages — listed BEFORE ai/:tab so \"bookmarks\" is not consumed as a tab. */}\n <Route path=\"ai/bookmarks\" element={gated('vibecontrols.ai', <AIBookmarksPage />)} />\n {/* AI Builder — describe → scaffold → test in sandbox → publish to catalog. */}\n <Route path=\"ai-builder\" element={gated('vibecontrols.ai', <AIBuilderPage />)} />\n <Route path=\"ai/:tab\" element={gated('vibecontrols.ai', <AgentManagerPage />)} />\n <Route\n path=\"ai/sessions/:sessionId\"\n element={gated('vibecontrols.ai', <AgentManagerPage />)}\n />\n {/* AI Tools — Plan / AI modes:\n /ai-tools → /ai-tools/ai/overview (redirect)\n /ai-tools/plan → Plan mode (plannotator iframe)\n /ai-tools/ai → /ai-tools/ai/overview (redirect)\n /ai-tools/ai/:tab → AI mode with active sub-tab\n Legacy /ai-tools/<tab> URLs from before the Plan mode rollout\n redirect into the new shape via LegacyAIToolsRedirect. */}\n <Route path=\"ai-tools\" element={<Navigate to=\"ai/overview\" replace />} />\n <Route path=\"ai-tools/plan\" element={gated('vibecontrols.ai', <AIPage />)} />\n <Route path=\"ai-tools/ai\" element={<Navigate to=\"overview\" replace />} />\n <Route path=\"ai-tools/ai/:tab\" element={gated('vibecontrols.ai', <AIPage />)} />\n <Route path=\"ai-tools/:tab\" element={<LegacyAIToolsRedirect />} />\n <Route\n path=\"ai-tools/sessions/:sessionId\"\n element={gated('vibecontrols.ai', <AISessionDetailPage />)}\n />\n\n {/* Targets list lives in the Targets & Agents landing; per-target\n detail is its own page (mirrors agents/:id). */}\n <Route path=\"targets\" element={<Navigate to=\"../agents?tab=targets\" replace />} />\n <Route path=\"targets/:id\" element={<TargetDetailsPage />} />\n {/* Agent Graph is also a tab inside the Targets & Agents landing. */}\n <Route path=\"agent-graph\" element={<Navigate to=\"../agents?tab=graph\" replace />} />\n\n {/* Catalog */}\n <Route path=\"catalogs\" element={gated('vibecontrols.catalogs', <CatalogPage />)} />\n <Route\n path=\"catalogs/:id\"\n element={gated('vibecontrols.catalogs', <CatalogDetailPage />)}\n />\n <Route\n path=\"catalogs/:id/docs/*\"\n element={gated('vibecontrols.catalogs', <CatalogDocsPage />)}\n />\n <Route path=\"components\" element={gated('vibecontrols.components', <ComponentsPage />)} />\n <Route\n path=\"environments\"\n element={gated('vibecontrols.components', <EnvTopologyPage />)}\n />\n <Route\n path=\"components/:id\"\n element={gated('vibecontrols.components', <ComponentDetailPage />)}\n />\n <Route\n path=\"components/:id/docs/*\"\n element={gated('vibecontrols.components', <ComponentDocsPage />)}\n />\n <Route path=\"templates\" element={gated('vibecontrols.templates', <TemplatesPage />)} />\n <Route\n path=\"templates/:id\"\n element={gated('vibecontrols.templates', <TemplateDetailPage />)}\n />\n\n {/* Automation */}\n <Route path=\"actions\" element={gated('vibecontrols.actions', <ActionsPage />)} />\n <Route path=\"vibedeck\" element={gated('vibecontrols.vibedeck', <VibeDeckPage />)} />\n <Route path=\"calendar\" element={gated('vibecontrols.calendar', <VibeCalendarPage />)} />\n <Route\n path=\"calendar/:viewType\"\n element={gated('vibecontrols.calendar', <VibeCalendarPage />)}\n />\n <Route\n path=\"calendar/task/:taskId\"\n element={gated('vibecontrols.calendar', <VibeTaskDetailPage />)}\n />\n {/* ScheduledJob (wspace-scheduler-svc) — separate id space\n from VibeCalendarTask above, so it needs its own route. */}\n <Route\n path=\"calendar/scheduled-job/:jobId\"\n element={gated('vibecontrols.calendar', <ScheduledJobDetailPage />)}\n />\n <Route path=\"webhooks\" element={gated('vibecontrols.webhooks', <WebhooksPage />)} />\n <Route path=\"webhooks/new\" element={gated('vibecontrols.webhooks', <WebhooksPage />)} />\n <Route path=\"webhooks/:id\" element={gated('vibecontrols.webhooks', <WebhooksPage />)} />\n {/* GitOps consolidated into Vibe detail tab — old /gitops routes redirect to vibes list. */}\n <Route path=\"gitops\" element={<Navigate to=\"../vibes\" replace />} />\n <Route path=\"gitops/:tab\" element={<Navigate to=\"../vibes\" replace />} />\n\n {/* Infrastructure */}\n <Route path=\"tunnels\" element={gated('vibecontrols.tunnels', <TunnelsLandingPage />)} />\n <Route path=\"tunnels/new\" element={gated('vibecontrols.tunnels', <TunnelCreateWizard />)} />\n <Route\n path=\"tunnels/:tunnelId\"\n element={gated('vibecontrols.tunnels', <TunnelDetailsPage />)}\n />\n\n {/* Insights */}\n <Route path=\"health\" element={gated('vibecontrols.health', <HealthPage />)} />\n <Route path=\"analytics\" element={gated('vibecontrols.analytics', <AnalyticsPage />)} />\n <Route path=\"audit\" element={gated('vibecontrols.audit-log', <VibeAuditPage />, false)} />\n <Route path=\"logs\" element={<LogsPage />} />\n\n {/* Settings */}\n <Route path=\"settings\" element={gated('vibecontrols.settings', <SettingsPage />)} />\n\n {/* Setup */}\n {/* Not gated — onboarding flow must always be reachable */}\n <Route path=\"getting-started\" element={<GettingStartedPage />} />\n\n {/* Shared public routes (no gating — accessible via public links) */}\n <Route path=\"share/:token\" element={<SharedSessionPage />} />\n <Route path=\"deck/:token\" element={<SharedVibeDeckPage />} />\n\n {/* Legacy redirects */}\n <Route path=\"access\" element={<Navigate to=\"../agents?tab=targets\" replace />} />\n <Route path=\"ssh-targets\" element={<Navigate to=\"../agents?tab=targets\" replace />} />\n <Route path=\"ssh-terminals\" element={<Navigate to=\"../agents?tab=targets\" replace />} />\n\n {/* Catch-all route - redirect to overview */}\n <Route path=\"*\" element={<VibeNotFound />} />\n </Routes>\n </Suspense>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"VibeControlsRoutes.js","names":[],"sources":["../src/VibeControlsRoutes.tsx"],"sourcesContent":["import React, { Suspense, useEffect } from 'react';\nimport { Navigate, Route, Routes, useLocation, useParams, useNavigate } from 'react-router-dom';\nimport { FeatureGate } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { NotFoundPage } from '@burdenoff/fe-libs/shared/pages';\n\n/** Catch-all renderer: a friendly 404 inside the content area (was a broken\n * redirect to \"\" that left the page blank). \"Back to home\" returns to the\n * VibeControls overview via SPA navigation. */\nfunction VibeNotFound() {\n const navigate = useNavigate();\n return (\n <NotFoundPage homeLabel=\"Back to VibeControls\" onGoHome={() => navigate('/vibecontrols')} />\n );\n}\nimport { useTr } from './shared/hooks/useTr';\n\n// Lazy load - All pages (required for @/ alias resolution when consumed by host app)\nconst OverviewPage = React.lazy(() =>\n import('./pages/OverviewPage').then((m) => ({ default: m.OverviewPage }))\n);\n\n// Other pages\nconst AgentsLandingPage = React.lazy(() =>\n import('./pages/AgentsLandingPage').then((m) => ({ default: m.AgentsLandingPage }))\n);\nconst AgentDetailsPage = React.lazy(() =>\n import('./pages/AgentDetailsPage').then((m) => ({ default: m.AgentDetailsPage }))\n);\nconst TargetDetailsPage = React.lazy(() =>\n import('./pages/TargetDetailsPage').then((m) => ({ default: m.TargetDetailsPage }))\n);\nconst SessionsPage = React.lazy(() =>\n import('./pages/SessionsPage').then((m) => ({ default: m.SessionsPage }))\n);\nconst SessionDetailsPage = React.lazy(() =>\n import('./pages/SessionDetailsPage').then((m) => ({ default: m.SessionDetailsPage }))\n);\nconst VibesPage = React.lazy(() =>\n import('./pages/VibesPage').then((m) => ({ default: m.VibesPage }))\n);\nconst VibeDetailsPage = React.lazy(() =>\n import('./pages/VibeDetailsPage').then((m) => ({ default: m.VibeDetailsPage }))\n);\nconst NoteDetailsPage = React.lazy(() =>\n import('./pages/NoteDetailsPage').then((m) => ({ default: m.NoteDetailsPage }))\n);\nconst TunnelsLandingPage = React.lazy(() =>\n import('./pages/TunnelsLandingPage').then((m) => ({ default: m.TunnelsLandingPage }))\n);\nconst TunnelDetailsPage = React.lazy(() =>\n import('./pages/tunnels/TunnelDetailsPage').then((m) => ({ default: m.TunnelDetailsPage }))\n);\nconst TunnelCreateWizard = React.lazy(() =>\n import('./components/tunnels/TunnelCreateWizard').then((m) => ({ default: m.TunnelCreateWizard }))\n);\nconst SettingsPage = React.lazy(() =>\n import('./pages/SettingsPage').then((m) => ({ default: m.SettingsPage }))\n);\nconst HealthPage = React.lazy(() =>\n import('./pages/HealthPage').then((m) => ({ default: m.HealthPage }))\n);\nconst GettingStartedPage = React.lazy(() =>\n import('./pages/GettingStartedPage').then((m) => ({ default: m.GettingStartedPage }))\n);\nconst SharedSessionPage = React.lazy(() =>\n import('./pages/SharedSessionPage').then((m) => ({ default: m.SharedSessionPage }))\n);\nconst VibeDeckPage = React.lazy(() =>\n import('./pages/VibeDeckPage').then((m) => ({ default: m.VibeDeckPage }))\n);\nconst SharedVibeDeckPage = React.lazy(() =>\n import('./pages/SharedVibeDeckPage').then((m) => ({ default: m.SharedVibeDeckPage }))\n);\nconst VibeCalendarPage = React.lazy(() =>\n import('./pages/VibeCalendarPage').then((m) => ({ default: m.VibeCalendarPage }))\n);\nconst VibeTaskDetailPage = React.lazy(() =>\n import('./pages/VibeTaskDetailPage').then((m) => ({ default: m.VibeTaskDetailPage }))\n);\nconst ScheduledJobDetailPage = React.lazy(() =>\n import('./pages/ScheduledJobDetailPage').then((m) => ({ default: m.ScheduledJobDetailPage }))\n);\nconst ActionsPage = React.lazy(() =>\n import('./pages/ActionsPage').then((m) => ({ default: m.ActionsPage }))\n);\nconst VibeAuditPage = React.lazy(() =>\n import('./pages/VibeAuditPage').then((m) => ({ default: m.VibeAuditPage }))\n);\nconst AnalyticsPage = React.lazy(() =>\n import('./pages/AnalyticsPage').then((m) => ({ default: m.AnalyticsPage }))\n);\nconst VibeDocsPage = React.lazy(() =>\n import('./pages/VibeDocsPage').then((m) => ({ default: m.VibeDocsPage }))\n);\nconst LogsPage = React.lazy(() =>\n import('./pages/LogsPage').then((m) => ({ default: m.LogsPage }))\n);\n\n// Catalog, Components, Templates pages\nconst CatalogPage = React.lazy(() =>\n import('./pages/CatalogPage').then((m) => ({ default: m.CatalogPage }))\n);\nconst CatalogDetailPage = React.lazy(() =>\n import('./pages/CatalogDetailPage').then((m) => ({ default: m.CatalogDetailPage }))\n);\nconst CatalogDocsPage = React.lazy(() =>\n import('./pages/CatalogDocsPage').then((m) => ({ default: m.CatalogDocsPage }))\n);\nconst ComponentsPage = React.lazy(() =>\n import('./pages/ComponentsPage').then((m) => ({ default: m.ComponentsPage }))\n);\nconst EnvTopologyPage = React.lazy(() =>\n import('./pages/EnvTopologyPage').then((m) => ({ default: m.EnvTopologyPage }))\n);\nconst WebhooksPage = React.lazy(() =>\n import('./pages/WebhooksPage').then((m) => ({ default: m.WebhooksPage }))\n);\nconst ComponentDetailPage = React.lazy(() =>\n import('./pages/ComponentDetailPage').then((m) => ({ default: m.ComponentDetailPage }))\n);\nconst ComponentDocsPage = React.lazy(() =>\n import('./pages/ComponentDocsPage').then((m) => ({ default: m.ComponentDocsPage }))\n);\nconst TemplatesPage = React.lazy(() =>\n import('./pages/TemplatesPage').then((m) => ({ default: m.TemplatesPage }))\n);\nconst TemplateDetailPage = React.lazy(() =>\n import('./pages/TemplateDetailPage').then((m) => ({ default: m.TemplateDetailPage }))\n);\nconst PluginsCatalogPage = React.lazy(() =>\n import('./pages/PluginsCatalogPage').then((m) => ({ default: m.PluginsCatalogPage }))\n);\n\n// AI pages\nconst AIPage = React.lazy(() => import('./pages/ai/AIPage').then((m) => ({ default: m.AIPage })));\nconst AISessionDetailPage = React.lazy(() =>\n import('./pages/ai/AISessionDetailPage').then((m) => ({ default: m.AISessionDetailPage }))\n);\nconst AgentManagerPage = React.lazy(() =>\n import('./pages/ai/AgentManagerPage').then((m) => ({ default: m.AgentManagerPage }))\n);\nconst AIBookmarksPage = React.lazy(() =>\n import('./pages/ai/AIBookmarksPage').then((m) => ({ default: m.AIBookmarksPage }))\n);\nconst AIBuilderPage = React.lazy(() =>\n import('./pages/ai/AIBuilderPage').then((m) => ({ default: m.AIBuilderPage }))\n);\n\n// Loading fallback component\nfunction PageLoadingFallback() {\n return (\n <div className=\"flex items-center justify-center h-full min-h-[200px]\">\n <div className=\"animate-pulse bg-bg-sunken rounded w-full h-full\" />\n </div>\n );\n}\n\n/**\n * Internal router for the VibeControls microfrontend.\n * This component handles routing within the microfrontend using React Router v7.\n *\n * Routes:\n * - / → Overview page (dashboard)\n * - /agents → Agents management page\n * - /agents/:id → Agent details page\n * - /sessions → Sessions management page\n * - /sessions/:id → Session details page\n * - /vibes → Vibes (projects) management page\n * - /vibes/:id → Vibe details page (includes Notes tab)\n * - /settings → Settings page\n * - /webhooks → Legacy redirect to Developer Portal webhooks\n * - /health → Health monitoring page\n * - /* → Catch-all redirects to overview\n */\nfunction ScrollToTop() {\n const { pathname } = useLocation();\n useEffect(() => {\n // The scrollable container is the <main> element with overflow-auto\n // inside AppShellLayout, not the window itself.\n const main = document.querySelector('main');\n if (main) main.scrollTop = 0;\n }, [pathname]);\n return null;\n}\n\n/**\n * Legacy redirect: /ai-tools/<tab> → /ai-tools/ai/<tab>.\n *\n * Before the Plan / AI mode toggle the page was reachable directly at\n * /ai-tools/<tab>. New shape is /ai-tools/<mode>/<tab>. We keep the\n * legacy links working by mapping the captured `:tab` param: if it\n * resolves to a known AI sub-tab, redirect there; if it's literally\n * \"plan\", route to the Plan page; otherwise fall back to AI overview.\n */\nconst VALID_AI_TABS = new Set<string>([\n 'overview',\n 'templates',\n 'contexts',\n 'prompts',\n 'sessions',\n 'tasks',\n 'stats',\n 'playground',\n 'config',\n]);\n\nfunction LegacyAIToolsRedirect() {\n const { tab } = useParams<{ tab?: string }>();\n if (tab === 'plan') {\n return <Navigate to=\"/vibecontrols/ai-tools/plan\" replace />;\n }\n if (tab && VALID_AI_TABS.has(tab)) {\n return <Navigate to={`/vibecontrols/ai-tools/ai/${tab}`} replace />;\n }\n return <Navigate to=\"/vibecontrols/ai-tools/ai/overview\" replace />;\n}\n\n/**\n * Fallback shown when a feature flag is OFF.\n */\nfunction FeatureDisabledFallback() {\n const tr = useTr();\n return (\n <div className=\"flex items-center justify-center h-full min-h-[200px] text-text-secondary text-sm\">\n {tr(\n 'vibecontrols.pages.featureDisabled',\n 'This feature is currently disabled. You can enable it from Feature Previews.'\n )}\n </div>\n );\n}\n\n/**\n * Helper: wraps a page element in a FeatureGate.\n * defaultValue={true} means the feature shows by default (before flags are seeded).\n * Only hides when a flag is explicitly set to false.\n */\nfunction gated(flag: string, element: React.ReactNode, defaultValue = true): React.ReactElement {\n return (\n <FeatureGate flag={flag} defaultValue={defaultValue} fallback={<FeatureDisabledFallback />}>\n {element}\n </FeatureGate>\n );\n}\n\nexport function VibeControlsRoutes() {\n return (\n <Suspense fallback={<PageLoadingFallback />}>\n <ScrollToTop />\n <Routes>\n {/* Default route - overview page */}\n <Route index element={gated('vibecontrols.overview', <OverviewPage />)} />\n {/* Legacy /dashboard alias → overview */}\n <Route path=\"dashboard\" element={<Navigate to=\"/vibecontrols\" replace />} />\n\n {/* Core pages */}\n {/* Plugins catalog — searchable grid of every published plugin. */}\n <Route path=\"plugins\" element={gated('vibecontrols.vibes', <PluginsCatalogPage />)} />\n\n <Route path=\"agents\" element={<AgentsLandingPage />} />\n <Route path=\"agents/new\" element={<Navigate to=\"../agents?create=true\" replace />} />\n <Route path=\"agents/:id\" element={<AgentDetailsPage />} />\n <Route path=\"sessions\" element={<SessionsPage />} />\n <Route path=\"vibes\" element={gated('vibecontrols.vibes', <VibesPage />)} />\n <Route path=\"vibes/new\" element={<Navigate to=\"../vibes?create=true\" replace />} />\n <Route path=\"vibes/:id\" element={gated('vibecontrols.vibes', <VibeDetailsPage />)} />\n <Route path=\"vibes/:id/docs/*\" element={gated('vibecontrols.vibes', <VibeDocsPage />)} />\n <Route path=\"notes/:id\" element={gated('vibecontrols.vibes', <NoteDetailsPage />)} />\n <Route path=\"sessions/:id\" element={<SessionDetailsPage />} />\n {/* Agent Manager — multi-session tabbed UI (replaces old AI page) */}\n <Route path=\"ai\" element={gated('vibecontrols.ai', <AgentManagerPage />)} />\n {/* Saved messages — listed BEFORE ai/:tab so \"bookmarks\" is not consumed as a tab. */}\n <Route path=\"ai/bookmarks\" element={gated('vibecontrols.ai', <AIBookmarksPage />)} />\n {/* AI Builder — describe → scaffold → test in sandbox → publish to catalog. */}\n <Route path=\"ai-builder\" element={gated('vibecontrols.ai', <AIBuilderPage />)} />\n <Route path=\"ai/:tab\" element={gated('vibecontrols.ai', <AgentManagerPage />)} />\n <Route\n path=\"ai/sessions/:sessionId\"\n element={gated('vibecontrols.ai', <AgentManagerPage />)}\n />\n {/* AI Tools — Plan / AI modes:\n /ai-tools → /ai-tools/ai/overview (redirect)\n /ai-tools/plan → Plan mode (plannotator iframe)\n /ai-tools/ai → /ai-tools/ai/overview (redirect)\n /ai-tools/ai/:tab → AI mode with active sub-tab\n Legacy /ai-tools/<tab> URLs from before the Plan mode rollout\n redirect into the new shape via LegacyAIToolsRedirect. */}\n <Route path=\"ai-tools\" element={<Navigate to=\"ai/overview\" replace />} />\n <Route path=\"ai-tools/plan\" element={gated('vibecontrols.ai', <AIPage />)} />\n <Route path=\"ai-tools/ai\" element={<Navigate to=\"overview\" replace />} />\n <Route path=\"ai-tools/ai/:tab\" element={gated('vibecontrols.ai', <AIPage />)} />\n <Route path=\"ai-tools/:tab\" element={<LegacyAIToolsRedirect />} />\n <Route\n path=\"ai-tools/sessions/:sessionId\"\n element={gated('vibecontrols.ai', <AISessionDetailPage />)}\n />\n\n {/* Targets list lives in the Targets & Agents landing; per-target\n detail is its own page (mirrors agents/:id). */}\n <Route path=\"targets\" element={<Navigate to=\"../agents?tab=targets\" replace />} />\n <Route path=\"targets/:id\" element={<TargetDetailsPage />} />\n {/* Agent Graph is also a tab inside the Targets & Agents landing. */}\n <Route path=\"agent-graph\" element={<Navigate to=\"../agents?tab=graph\" replace />} />\n\n {/* Catalog */}\n <Route path=\"catalogs\" element={gated('vibecontrols.catalogs', <CatalogPage />)} />\n <Route\n path=\"catalogs/:id\"\n element={gated('vibecontrols.catalogs', <CatalogDetailPage />)}\n />\n <Route\n path=\"catalogs/:id/docs/*\"\n element={gated('vibecontrols.catalogs', <CatalogDocsPage />)}\n />\n <Route path=\"components\" element={gated('vibecontrols.components', <ComponentsPage />)} />\n <Route\n path=\"environments\"\n element={gated('vibecontrols.components', <EnvTopologyPage />)}\n />\n <Route\n path=\"components/:id\"\n element={gated('vibecontrols.components', <ComponentDetailPage />)}\n />\n <Route\n path=\"components/:id/docs/*\"\n element={gated('vibecontrols.components', <ComponentDocsPage />)}\n />\n <Route path=\"templates\" element={gated('vibecontrols.templates', <TemplatesPage />)} />\n <Route\n path=\"templates/:id\"\n element={gated('vibecontrols.templates', <TemplateDetailPage />)}\n />\n\n {/* Automation */}\n <Route path=\"actions\" element={gated('vibecontrols.actions', <ActionsPage />)} />\n <Route path=\"vibedeck\" element={gated('vibecontrols.vibedeck', <VibeDeckPage />)} />\n <Route path=\"calendar\" element={gated('vibecontrols.calendar', <VibeCalendarPage />)} />\n <Route\n path=\"calendar/:viewType\"\n element={gated('vibecontrols.calendar', <VibeCalendarPage />)}\n />\n <Route\n path=\"calendar/task/:taskId\"\n element={gated('vibecontrols.calendar', <VibeTaskDetailPage />)}\n />\n {/* ScheduledJob (wspace-scheduler-svc) — separate id space\n from VibeCalendarTask above, so it needs its own route. */}\n <Route\n path=\"calendar/scheduled-job/:jobId\"\n element={gated('vibecontrols.calendar', <ScheduledJobDetailPage />)}\n />\n <Route path=\"webhooks\" element={gated('vibecontrols.webhooks', <WebhooksPage />)} />\n <Route path=\"webhooks/new\" element={gated('vibecontrols.webhooks', <WebhooksPage />)} />\n <Route path=\"webhooks/:id\" element={gated('vibecontrols.webhooks', <WebhooksPage />)} />\n {/* GitOps consolidated into Vibe detail tab — old /gitops routes redirect to vibes list. */}\n <Route path=\"gitops\" element={<Navigate to=\"../vibes\" replace />} />\n <Route path=\"gitops/:tab\" element={<Navigate to=\"../vibes\" replace />} />\n\n {/* Infrastructure */}\n <Route path=\"tunnels\" element={gated('vibecontrols.tunnels', <TunnelsLandingPage />)} />\n <Route path=\"tunnels/new\" element={gated('vibecontrols.tunnels', <TunnelCreateWizard />)} />\n <Route\n path=\"tunnels/:tunnelId\"\n element={gated('vibecontrols.tunnels', <TunnelDetailsPage />)}\n />\n\n {/* Insights */}\n <Route path=\"health\" element={gated('vibecontrols.health', <HealthPage />)} />\n <Route path=\"analytics\" element={gated('vibecontrols.analytics', <AnalyticsPage />)} />\n <Route path=\"audit\" element={gated('vibecontrols.audit-log', <VibeAuditPage />, false)} />\n <Route path=\"logs\" element={<LogsPage />} />\n\n {/* Settings */}\n <Route path=\"settings\" element={gated('vibecontrols.settings', <SettingsPage />)} />\n\n {/* Setup */}\n {/* Not gated — onboarding flow must always be reachable */}\n <Route path=\"getting-started\" element={<GettingStartedPage />} />\n\n {/* Shared public routes (no gating — accessible via public links) */}\n <Route path=\"share/:token\" element={<SharedSessionPage />} />\n <Route path=\"deck/:token\" element={<SharedVibeDeckPage />} />\n\n {/* Legacy redirects */}\n <Route path=\"access\" element={<Navigate to=\"../agents?tab=targets\" replace />} />\n <Route path=\"ssh-targets\" element={<Navigate to=\"../agents?tab=targets\" replace />} />\n <Route path=\"ssh-terminals\" element={<Navigate to=\"../agents?tab=targets\" replace />} />\n\n {/* Catch-all route - redirect to overview */}\n <Route path=\"*\" element={<VibeNotFound />} />\n </Routes>\n </Suspense>\n );\n}\n"],"mappings":";;;;;;;AAQA,SAAS,IAAe;CACtB,IAAM,IAAW,GAAa;AAC9B,QACE,kBAAC,GAAD;EAAc,WAAU;EAAuB,gBAAgB,EAAS,gBAAgB;EAAI,CAAA;;AAMhG,IAAM,IAAe,EAAM,WACzB,OAAO,2BAAwB,MAAM,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,CAC1E,EAGK,IAAoB,EAAM,WAC9B,OAAO,gCAA6B,MAAM,OAAO,EAAE,SAAS,EAAE,mBAAmB,EAAE,CACpF,EACK,IAAmB,EAAM,WAC7B,OAAO,+BAA4B,MAAM,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,CAClF,EACK,IAAoB,EAAM,WAC9B,OAAO,gCAA6B,MAAM,OAAO,EAAE,SAAS,EAAE,mBAAmB,EAAE,CACpF,EACK,IAAe,EAAM,WACzB,OAAO,2BAAwB,MAAM,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,CAC1E,EACK,IAAqB,EAAM,WAC/B,OAAO,iCAA8B,MAAM,OAAO,EAAE,SAAS,EAAE,oBAAoB,EAAE,CACtF,EACK,IAAY,EAAM,WACtB,OAAO,wBAAqB,MAAM,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,CACpE,EACK,IAAkB,EAAM,WAC5B,OAAO,8BAA2B,MAAM,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,CAChF,EACK,IAAkB,EAAM,WAC5B,OAAO,8BAA2B,MAAM,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,CAChF,EACK,IAAqB,EAAM,WAC/B,OAAO,iCAA8B,MAAM,OAAO,EAAE,SAAS,EAAE,oBAAoB,EAAE,CACtF,EACK,IAAoB,EAAM,WAC9B,OAAO,wCAAqC,MAAM,OAAO,EAAE,SAAS,EAAE,mBAAmB,EAAE,CAC5F,EACK,IAAqB,EAAM,WAC/B,OAAO,8CAA2C,MAAM,OAAO,EAAE,SAAS,EAAE,oBAAoB,EAAE,CACnG,EACK,IAAe,EAAM,WACzB,OAAO,2BAAwB,MAAM,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,CAC1E,EACK,IAAa,EAAM,WACvB,OAAO,yBAAsB,MAAM,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,CACtE,EACK,IAAqB,EAAM,WAC/B,OAAO,iCAA8B,MAAM,OAAO,EAAE,SAAS,EAAE,oBAAoB,EAAE,CACtF,EACK,IAAoB,EAAM,WAC9B,OAAO,gCAA6B,MAAM,OAAO,EAAE,SAAS,EAAE,mBAAmB,EAAE,CACpF,EACK,IAAe,EAAM,WACzB,OAAO,2BAAwB,MAAM,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,CAC1E,EACK,IAAqB,EAAM,WAC/B,OAAO,iCAA8B,MAAM,OAAO,EAAE,SAAS,EAAE,oBAAoB,EAAE,CACtF,EACK,IAAmB,EAAM,WAC7B,OAAO,+BAA4B,MAAM,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,CAClF,EACK,IAAqB,EAAM,WAC/B,OAAO,iCAA8B,MAAM,OAAO,EAAE,SAAS,EAAE,oBAAoB,EAAE,CACtF,EACK,IAAyB,EAAM,WACnC,OAAO,qCAAkC,MAAM,OAAO,EAAE,SAAS,EAAE,wBAAwB,EAAE,CAC9F,EACK,IAAc,EAAM,WACxB,OAAO,0BAAuB,MAAM,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,CACxE,EACK,KAAgB,EAAM,WAC1B,OAAO,4BAAyB,MAAM,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,CAC5E,EACK,IAAgB,EAAM,WAC1B,OAAO,4BAAyB,MAAM,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,CAC5E,EACK,IAAe,EAAM,WACzB,OAAO,2BAAwB,MAAM,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,CAC1E,EACK,IAAW,EAAM,WACrB,OAAO,uBAAoB,MAAM,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,CAClE,EAGK,IAAc,EAAM,WACxB,OAAO,0BAAuB,MAAM,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,CACxE,EACK,IAAoB,EAAM,WAC9B,OAAO,gCAA6B,MAAM,OAAO,EAAE,SAAS,EAAE,mBAAmB,EAAE,CACpF,EACK,IAAkB,EAAM,WAC5B,OAAO,8BAA2B,MAAM,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,CAChF,EACK,IAAiB,EAAM,WAC3B,OAAO,6BAA0B,MAAM,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,CAC9E,EACK,IAAkB,EAAM,WAC5B,OAAO,8BAA2B,MAAM,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,CAChF,EACK,IAAe,EAAM,WACzB,OAAO,2BAAwB,MAAM,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,CAC1E,EACK,IAAsB,EAAM,WAChC,OAAO,kCAA+B,MAAM,OAAO,EAAE,SAAS,EAAE,qBAAqB,EAAE,CACxF,EACK,IAAoB,EAAM,WAC9B,OAAO,gCAA6B,MAAM,OAAO,EAAE,SAAS,EAAE,mBAAmB,EAAE,CACpF,EACK,IAAgB,EAAM,WAC1B,OAAO,4BAAyB,MAAM,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,CAC5E,EACK,IAAqB,EAAM,WAC/B,OAAO,iCAA8B,MAAM,OAAO,EAAE,SAAS,EAAE,oBAAoB,EAAE,CACtF,EACK,IAAqB,EAAM,WAC/B,OAAO,iCAA8B,MAAM,OAAO,EAAE,SAAS,EAAE,oBAAoB,EAAE,CACtF,EAGK,IAAS,EAAM,WAAW,OAAO,wBAAqB,MAAM,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,EAC3F,IAAsB,EAAM,WAChC,OAAO,qCAAkC,MAAM,OAAO,EAAE,SAAS,EAAE,qBAAqB,EAAE,CAC3F,EACK,IAAmB,EAAM,WAC7B,OAAO,kCAA+B,MAAM,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,CACrF,EACK,KAAkB,EAAM,WAC5B,OAAO,iCAA8B,MAAM,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,CACnF,EACK,KAAgB,EAAM,WAC1B,OAAO,+BAA4B,MAAM,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,CAC/E;AAGD,SAAS,KAAsB;AAC7B,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD,EAAK,WAAU,oDAAqD,CAAA;EAChE,CAAA;;AAqBV,SAAS,KAAc;CACrB,IAAM,EAAE,gBAAa,GAAa;AAOlC,QANA,QAAgB;EAGd,IAAM,IAAO,SAAS,cAAc,OAAO;AAC3C,EAAI,MAAM,EAAK,YAAY;IAC1B,CAAC,EAAS,CAAC,EACP;;AAYT,IAAM,KAAgB,IAAI,IAAY;CACpC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;AAEF,SAAS,IAAwB;CAC/B,IAAM,EAAE,WAAQ,GAA6B;AAO7C,QANI,MAAQ,SACH,kBAAC,GAAD;EAAU,IAAG;EAA8B,SAAA;EAAU,CAAA,GAE1D,KAAO,GAAc,IAAI,EAAI,GACxB,kBAAC,GAAD;EAAU,IAAI,6BAA6B;EAAO,SAAA;EAAU,CAAA,GAE9D,kBAAC,GAAD;EAAU,IAAG;EAAqC,SAAA;EAAU,CAAA;;AAMrE,SAAS,KAA0B;AAEjC,QACE,kBAAC,OAAD;EAAK,WAAU;YAFN,GAAO,CAIZ,sCACA,+EACD;EACG,CAAA;;AASV,SAAS,EAAM,GAAc,GAA0B,IAAe,IAA0B;AAC9F,QACE,kBAAC,IAAD;EAAmB;EAAoB;EAAc,UAAU,kBAAC,IAAD,EAA2B,CAAA;YACvF;EACW,CAAA;;AAIlB,SAAgB,KAAqB;AACnC,QACE,kBAAC,GAAD;EAAU,UAAU,kBAAC,IAAD,EAAuB,CAAA;YAA3C,CACE,kBAAC,IAAD,EAAe,CAAA,EACf,kBAAC,IAAD,EAAA,UAAA;GAEE,kBAAC,GAAD;IAAO,OAAA;IAAM,SAAS,EAAM,yBAAyB,kBAAC,GAAD,EAAgB,CAAA,CAAC;IAAI,CAAA;GAE1E,kBAAC,GAAD;IAAO,MAAK;IAAY,SAAS,kBAAC,GAAD;KAAU,IAAG;KAAgB,SAAA;KAAU,CAAA;IAAI,CAAA;GAI5E,kBAAC,GAAD;IAAO,MAAK;IAAU,SAAS,EAAM,sBAAsB,kBAAC,GAAD,EAAsB,CAAA,CAAC;IAAI,CAAA;GAEtF,kBAAC,GAAD;IAAO,MAAK;IAAS,SAAS,kBAAC,GAAD,EAAqB,CAAA;IAAI,CAAA;GACvD,kBAAC,GAAD;IAAO,MAAK;IAAa,SAAS,kBAAC,GAAD;KAAU,IAAG;KAAwB,SAAA;KAAU,CAAA;IAAI,CAAA;GACrF,kBAAC,GAAD;IAAO,MAAK;IAAa,SAAS,kBAAC,GAAD,EAAoB,CAAA;IAAI,CAAA;GAC1D,kBAAC,GAAD;IAAO,MAAK;IAAW,SAAS,kBAAC,GAAD,EAAgB,CAAA;IAAI,CAAA;GACpD,kBAAC,GAAD;IAAO,MAAK;IAAQ,SAAS,EAAM,sBAAsB,kBAAC,GAAD,EAAa,CAAA,CAAC;IAAI,CAAA;GAC3E,kBAAC,GAAD;IAAO,MAAK;IAAY,SAAS,kBAAC,GAAD;KAAU,IAAG;KAAuB,SAAA;KAAU,CAAA;IAAI,CAAA;GACnF,kBAAC,GAAD;IAAO,MAAK;IAAY,SAAS,EAAM,sBAAsB,kBAAC,GAAD,EAAmB,CAAA,CAAC;IAAI,CAAA;GACrF,kBAAC,GAAD;IAAO,MAAK;IAAmB,SAAS,EAAM,sBAAsB,kBAAC,GAAD,EAAgB,CAAA,CAAC;IAAI,CAAA;GACzF,kBAAC,GAAD;IAAO,MAAK;IAAY,SAAS,EAAM,sBAAsB,kBAAC,GAAD,EAAmB,CAAA,CAAC;IAAI,CAAA;GACrF,kBAAC,GAAD;IAAO,MAAK;IAAe,SAAS,kBAAC,GAAD,EAAsB,CAAA;IAAI,CAAA;GAE9D,kBAAC,GAAD;IAAO,MAAK;IAAK,SAAS,EAAM,mBAAmB,kBAAC,GAAD,EAAoB,CAAA,CAAC;IAAI,CAAA;GAE5E,kBAAC,GAAD;IAAO,MAAK;IAAe,SAAS,EAAM,mBAAmB,kBAAC,IAAD,EAAmB,CAAA,CAAC;IAAI,CAAA;GAErF,kBAAC,GAAD;IAAO,MAAK;IAAa,SAAS,EAAM,mBAAmB,kBAAC,IAAD,EAAiB,CAAA,CAAC;IAAI,CAAA;GACjF,kBAAC,GAAD;IAAO,MAAK;IAAU,SAAS,EAAM,mBAAmB,kBAAC,GAAD,EAAoB,CAAA,CAAC;IAAI,CAAA;GACjF,kBAAC,GAAD;IACE,MAAK;IACL,SAAS,EAAM,mBAAmB,kBAAC,GAAD,EAAoB,CAAA,CAAC;IACvD,CAAA;GAQF,kBAAC,GAAD;IAAO,MAAK;IAAW,SAAS,kBAAC,GAAD;KAAU,IAAG;KAAc,SAAA;KAAU,CAAA;IAAI,CAAA;GACzE,kBAAC,GAAD;IAAO,MAAK;IAAgB,SAAS,EAAM,mBAAmB,kBAAC,GAAD,EAAU,CAAA,CAAC;IAAI,CAAA;GAC7E,kBAAC,GAAD;IAAO,MAAK;IAAc,SAAS,kBAAC,GAAD;KAAU,IAAG;KAAW,SAAA;KAAU,CAAA;IAAI,CAAA;GACzE,kBAAC,GAAD;IAAO,MAAK;IAAmB,SAAS,EAAM,mBAAmB,kBAAC,GAAD,EAAU,CAAA,CAAC;IAAI,CAAA;GAChF,kBAAC,GAAD;IAAO,MAAK;IAAgB,SAAS,kBAAC,GAAD,EAAyB,CAAA;IAAI,CAAA;GAClE,kBAAC,GAAD;IACE,MAAK;IACL,SAAS,EAAM,mBAAmB,kBAAC,GAAD,EAAuB,CAAA,CAAC;IAC1D,CAAA;GAIF,kBAAC,GAAD;IAAO,MAAK;IAAU,SAAS,kBAAC,GAAD;KAAU,IAAG;KAAwB,SAAA;KAAU,CAAA;IAAI,CAAA;GAClF,kBAAC,GAAD;IAAO,MAAK;IAAc,SAAS,kBAAC,GAAD,EAAqB,CAAA;IAAI,CAAA;GAE5D,kBAAC,GAAD;IAAO,MAAK;IAAc,SAAS,kBAAC,GAAD;KAAU,IAAG;KAAsB,SAAA;KAAU,CAAA;IAAI,CAAA;GAGpF,kBAAC,GAAD;IAAO,MAAK;IAAW,SAAS,EAAM,yBAAyB,kBAAC,GAAD,EAAe,CAAA,CAAC;IAAI,CAAA;GACnF,kBAAC,GAAD;IACE,MAAK;IACL,SAAS,EAAM,yBAAyB,kBAAC,GAAD,EAAqB,CAAA,CAAC;IAC9D,CAAA;GACF,kBAAC,GAAD;IACE,MAAK;IACL,SAAS,EAAM,yBAAyB,kBAAC,GAAD,EAAmB,CAAA,CAAC;IAC5D,CAAA;GACF,kBAAC,GAAD;IAAO,MAAK;IAAa,SAAS,EAAM,2BAA2B,kBAAC,GAAD,EAAkB,CAAA,CAAC;IAAI,CAAA;GAC1F,kBAAC,GAAD;IACE,MAAK;IACL,SAAS,EAAM,2BAA2B,kBAAC,GAAD,EAAmB,CAAA,CAAC;IAC9D,CAAA;GACF,kBAAC,GAAD;IACE,MAAK;IACL,SAAS,EAAM,2BAA2B,kBAAC,GAAD,EAAuB,CAAA,CAAC;IAClE,CAAA;GACF,kBAAC,GAAD;IACE,MAAK;IACL,SAAS,EAAM,2BAA2B,kBAAC,GAAD,EAAqB,CAAA,CAAC;IAChE,CAAA;GACF,kBAAC,GAAD;IAAO,MAAK;IAAY,SAAS,EAAM,0BAA0B,kBAAC,GAAD,EAAiB,CAAA,CAAC;IAAI,CAAA;GACvF,kBAAC,GAAD;IACE,MAAK;IACL,SAAS,EAAM,0BAA0B,kBAAC,GAAD,EAAsB,CAAA,CAAC;IAChE,CAAA;GAGF,kBAAC,GAAD;IAAO,MAAK;IAAU,SAAS,EAAM,wBAAwB,kBAAC,GAAD,EAAe,CAAA,CAAC;IAAI,CAAA;GACjF,kBAAC,GAAD;IAAO,MAAK;IAAW,SAAS,EAAM,yBAAyB,kBAAC,GAAD,EAAgB,CAAA,CAAC;IAAI,CAAA;GACpF,kBAAC,GAAD;IAAO,MAAK;IAAW,SAAS,EAAM,yBAAyB,kBAAC,GAAD,EAAoB,CAAA,CAAC;IAAI,CAAA;GACxF,kBAAC,GAAD;IACE,MAAK;IACL,SAAS,EAAM,yBAAyB,kBAAC,GAAD,EAAoB,CAAA,CAAC;IAC7D,CAAA;GACF,kBAAC,GAAD;IACE,MAAK;IACL,SAAS,EAAM,yBAAyB,kBAAC,GAAD,EAAsB,CAAA,CAAC;IAC/D,CAAA;GAGF,kBAAC,GAAD;IACE,MAAK;IACL,SAAS,EAAM,yBAAyB,kBAAC,GAAD,EAA0B,CAAA,CAAC;IACnE,CAAA;GACF,kBAAC,GAAD;IAAO,MAAK;IAAW,SAAS,EAAM,yBAAyB,kBAAC,GAAD,EAAgB,CAAA,CAAC;IAAI,CAAA;GACpF,kBAAC,GAAD;IAAO,MAAK;IAAe,SAAS,EAAM,yBAAyB,kBAAC,GAAD,EAAgB,CAAA,CAAC;IAAI,CAAA;GACxF,kBAAC,GAAD;IAAO,MAAK;IAAe,SAAS,EAAM,yBAAyB,kBAAC,GAAD,EAAgB,CAAA,CAAC;IAAI,CAAA;GAExF,kBAAC,GAAD;IAAO,MAAK;IAAS,SAAS,kBAAC,GAAD;KAAU,IAAG;KAAW,SAAA;KAAU,CAAA;IAAI,CAAA;GACpE,kBAAC,GAAD;IAAO,MAAK;IAAc,SAAS,kBAAC,GAAD;KAAU,IAAG;KAAW,SAAA;KAAU,CAAA;IAAI,CAAA;GAGzE,kBAAC,GAAD;IAAO,MAAK;IAAU,SAAS,EAAM,wBAAwB,kBAAC,GAAD,EAAsB,CAAA,CAAC;IAAI,CAAA;GACxF,kBAAC,GAAD;IAAO,MAAK;IAAc,SAAS,EAAM,wBAAwB,kBAAC,GAAD,EAAsB,CAAA,CAAC;IAAI,CAAA;GAC5F,kBAAC,GAAD;IACE,MAAK;IACL,SAAS,EAAM,wBAAwB,kBAAC,GAAD,EAAqB,CAAA,CAAC;IAC7D,CAAA;GAGF,kBAAC,GAAD;IAAO,MAAK;IAAS,SAAS,EAAM,uBAAuB,kBAAC,GAAD,EAAc,CAAA,CAAC;IAAI,CAAA;GAC9E,kBAAC,GAAD;IAAO,MAAK;IAAY,SAAS,EAAM,0BAA0B,kBAAC,GAAD,EAAiB,CAAA,CAAC;IAAI,CAAA;GACvF,kBAAC,GAAD;IAAO,MAAK;IAAQ,SAAS,EAAM,0BAA0B,kBAAC,IAAD,EAAiB,CAAA,EAAE,GAAM;IAAI,CAAA;GAC1F,kBAAC,GAAD;IAAO,MAAK;IAAO,SAAS,kBAAC,GAAD,EAAY,CAAA;IAAI,CAAA;GAG5C,kBAAC,GAAD;IAAO,MAAK;IAAW,SAAS,EAAM,yBAAyB,kBAAC,GAAD,EAAgB,CAAA,CAAC;IAAI,CAAA;GAIpF,kBAAC,GAAD;IAAO,MAAK;IAAkB,SAAS,kBAAC,GAAD,EAAsB,CAAA;IAAI,CAAA;GAGjE,kBAAC,GAAD;IAAO,MAAK;IAAe,SAAS,kBAAC,GAAD,EAAqB,CAAA;IAAI,CAAA;GAC7D,kBAAC,GAAD;IAAO,MAAK;IAAc,SAAS,kBAAC,GAAD,EAAsB,CAAA;IAAI,CAAA;GAG7D,kBAAC,GAAD;IAAO,MAAK;IAAS,SAAS,kBAAC,GAAD;KAAU,IAAG;KAAwB,SAAA;KAAU,CAAA;IAAI,CAAA;GACjF,kBAAC,GAAD;IAAO,MAAK;IAAc,SAAS,kBAAC,GAAD;KAAU,IAAG;KAAwB,SAAA;KAAU,CAAA;IAAI,CAAA;GACtF,kBAAC,GAAD;IAAO,MAAK;IAAgB,SAAS,kBAAC,GAAD;KAAU,IAAG;KAAwB,SAAA;KAAU,CAAA;IAAI,CAAA;GAGxF,kBAAC,GAAD;IAAO,MAAK;IAAI,SAAS,kBAAC,GAAD,EAAgB,CAAA;IAAI,CAAA;GACtC,EAAA,CAAA,CACA"}
@@ -80,42 +80,19 @@ var v = ({ agent: v, onSelect: y, onConfigure: b, onSettings: x, onStart: S, onS
80
80
  }),
81
81
  /* @__PURE__ */ g("div", {
82
82
  className: "min-w-0 flex-1",
83
- children: [
84
- /* @__PURE__ */ g("div", {
85
- className: "flex items-center gap-1.5 sm:gap-2 min-w-0",
86
- children: [/* @__PURE__ */ h("h3", {
87
- className: "font-semibold text-text-primary truncate min-w-0 text-sm sm:text-base tracking-tight",
88
- children: v.name
89
- }), /* @__PURE__ */ h(n, {
90
- platform: v.platform,
91
- className: "size-4 flex-shrink-0 text-text-secondary"
92
- })]
93
- }),
94
- /* @__PURE__ */ h("p", {
95
- className: "text-xs sm:text-sm text-text-secondary truncate",
96
- children: v.hostname || `ID: ${v.id}`
97
- }),
98
- (() => {
99
- let e = i(M, k ?? null);
100
- return e.phase === "none" ? null : /* @__PURE__ */ g("p", {
101
- className: `mt-1 flex items-start gap-1 text-xs ${e.phase === "attention" ? "text-status-warning-text" : "text-content-tertiary"}`,
102
- children: [e.inProgress && /* @__PURE__ */ h(d, {
103
- className: "size-3 mt-0.5 flex-shrink-0 animate-spin",
104
- "aria-hidden": "true"
105
- }), /* @__PURE__ */ g("span", {
106
- className: "min-w-0 flex-1 break-words",
107
- children: [
108
- /* @__PURE__ */ g("span", {
109
- className: "font-medium",
110
- children: [e.label, "."]
111
- }),
112
- " ",
113
- e.actionHint
114
- ]
115
- })]
116
- });
117
- })()
118
- ]
83
+ children: [/* @__PURE__ */ g("div", {
84
+ className: "flex items-center gap-1.5 sm:gap-2 min-w-0",
85
+ children: [/* @__PURE__ */ h("h3", {
86
+ className: "font-semibold text-text-primary truncate min-w-0 text-sm sm:text-base tracking-tight",
87
+ children: v.name
88
+ }), /* @__PURE__ */ h(n, {
89
+ platform: v.platform,
90
+ className: "size-4 flex-shrink-0 text-text-secondary"
91
+ })]
92
+ }), /* @__PURE__ */ h("p", {
93
+ className: "text-xs sm:text-sm text-text-secondary truncate",
94
+ children: v.hostname || `ID: ${v.id}`
95
+ })]
119
96
  })
120
97
  ]
121
98
  }), /* @__PURE__ */ g("div", {
@@ -135,6 +112,26 @@ var v = ({ agent: v, onSelect: y, onConfigure: b, onSettings: x, onStart: S, onS
135
112
  })]
136
113
  })]
137
114
  }),
115
+ (() => {
116
+ let e = i(M, k ?? null);
117
+ return e.phase === "none" ? null : /* @__PURE__ */ g("div", {
118
+ className: `mt-3 flex items-start gap-1.5 text-xs ${e.phase === "attention" ? "text-status-warning-text" : "text-content-tertiary"}`,
119
+ children: [e.inProgress && /* @__PURE__ */ h(d, {
120
+ className: "size-3.5 mt-0.5 flex-shrink-0 animate-spin",
121
+ "aria-hidden": "true"
122
+ }), /* @__PURE__ */ g("p", {
123
+ className: "min-w-0 flex-1 break-words leading-relaxed",
124
+ children: [
125
+ /* @__PURE__ */ g("span", {
126
+ className: "font-medium",
127
+ children: [e.label, "."]
128
+ }),
129
+ " ",
130
+ e.actionHint
131
+ ]
132
+ })]
133
+ });
134
+ })(),
138
135
  /* @__PURE__ */ g("div", {
139
136
  className: "mt-5 grid grid-cols-2 gap-3 text-sm",
140
137
  children: [/* @__PURE__ */ g("div", {
@@ -1 +1 @@
1
- {"version":3,"file":"AgentCard.js","names":[],"sources":["../../../src/components/agents/AgentCard.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { Server, Clock, Cpu, Play, Square, Settings, Globe, Link, RefreshCw } from 'lucide-react';\nimport { Checkbox } from '@burdenoff/fe-libs/ui';\nimport { AgentStatusBadge } from './AgentStatusBadge';\nimport type { Agent } from './types';\nimport { getAgentStatus } from './agentStatus';\nimport { classifyAgentHealth } from '../../utils/agentHealth';\nimport { ActionMenu, type ActionMenuItem } from '../shared/ActionMenu';\nimport { PlatformIcon } from '../shared/PlatformIcon';\nimport { useTr } from '../../shared/hooks/useTr';\n\nexport interface AgentCardProps {\n agent: Agent;\n onSelect?: (agent: Agent) => void;\n onConfigure?: (agent: Agent) => void;\n onSettings?: (agent: Agent) => void;\n onStart?: (agent: Agent) => void;\n onStop?: (agent: Agent) => void;\n onDisconnect?: (agent: Agent) => void;\n onEditTunnelUrl?: (agent: Agent) => void;\n selected?: boolean;\n onToggleSelect?: () => void;\n className?: string;\n /**\n * Last error from /health/ready or the live probe — shown in the badge\n * tooltip when the agent is degraded so users know WHY without curling\n * the tunnel themselves.\n */\n healthErrorMessage?: string | null;\n}\n\n/**\n * AgentCard - Display agent information in a card format\n *\n * @example\n * ```tsx\n * <AgentCard\n * agent={myAgent}\n * onSelect={handleSelect}\n * onConfigure={handleConfigure}\n * />\n * ```\n */\nexport const AgentCard: FC<AgentCardProps> = ({\n agent,\n onSelect,\n onConfigure,\n onSettings,\n onStart,\n onStop,\n onDisconnect,\n onEditTunnelUrl,\n selected = false,\n onToggleSelect,\n className = '',\n healthErrorMessage,\n}) => {\n const tr = useTr();\n // Support both onConfigure and onSettings\n const handleConfigure = onConfigure || onSettings;\n\n const status = getAgentStatus(agent);\n\n const formatLastHeartbeat = (timestamp: string | null | undefined) => {\n if (!timestamp) return tr('agentsPage.never', 'Never');\n const diff = Date.now() - new Date(timestamp).getTime();\n if (diff < 60000) return tr('agentsPage.justNow', 'Just now');\n if (diff < 3600000) return `${Math.floor(diff / 60000)}m ago`;\n if (diff < 86400000) return `${Math.floor(diff / 3600000)}h ago`;\n return `${Math.floor(diff / 86400000)}d ago`;\n };\n\n const menuItems: ActionMenuItem[] = [];\n if (status === 'ACTIVE' && onStop) {\n menuItems.push({\n key: 'stop',\n label: tr('agentsPage.stopAgent', 'Stop Agent'),\n icon: Square,\n onSelect: () => onStop(agent),\n });\n }\n if ((status === 'STOPPED' || status === 'OFFLINE') && onStart) {\n menuItems.push({\n key: 'start',\n label: tr('agentsPage.startAgent', 'Start Agent'),\n icon: Play,\n onSelect: () => onStart(agent),\n });\n }\n if (status === 'DEGRADED' && onStart) {\n // Reconnect on a degraded agent fires the same startVibecontrolsAgent\n // mutation as a fresh start — svc-side that POSTs lifecycle/start to the\n // agent's tunnel, which usually clears transient failures (tunnel hiccup,\n // finalize retry). Labelled \"Reconnect\" not \"Start\" so the user knows\n // it's a recovery action, not creating a new instance.\n menuItems.push({\n key: 'reconnect',\n label: tr('agentsPage.reconnectAgent', 'Reconnect'),\n icon: RefreshCw,\n onSelect: () => onStart(agent),\n });\n }\n if (\n (status === 'ACTIVE' ||\n status === 'STOPPED' ||\n status === 'OFFLINE' ||\n status === 'DEGRADED') &&\n onDisconnect\n ) {\n menuItems.push({\n key: 'disconnect',\n label:\n status === 'OFFLINE'\n ? tr('agentsPage.clearConnection', 'Clear Connection')\n : tr('agentsPage.disconnect', 'Disconnect'),\n icon: Square,\n onSelect: () => onDisconnect(agent),\n variant: 'danger',\n });\n }\n if (onEditTunnelUrl) {\n menuItems.push({\n key: 'edit-tunnel',\n label: tr('agentsPage.editTunnel', 'Edit Tunnel'),\n icon: Link,\n onSelect: () => onEditTunnelUrl(agent),\n });\n }\n if (handleConfigure) {\n menuItems.push({\n key: 'settings',\n label: tr('agentsPage.settings', 'Settings'),\n icon: Settings,\n onSelect: () => handleConfigure(agent),\n });\n }\n\n return (\n <div\n role=\"button\"\n tabIndex={0}\n className={`bg-bg-surface border rounded-card shadow-elevation-1 p-5 h-full hover:border-border-strong hover:shadow-elevation-2 transition-all duration-200 ease-[var(--motion-easing-out)] cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2 focus-visible:ring-offset-bg-canvas ${\n selected\n ? 'border-transparent ring-1 ring-[var(--color-focus-ring)] bg-[var(--color-accent-soft)]'\n : 'border-border-subtle'\n } ${className}`}\n onClick={() => onSelect?.(agent)}\n onKeyDown={(e) => {\n if (e.target !== e.currentTarget) return; // children (menu, checkbox, links) handle their own keys\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n onSelect?.(agent);\n }\n }}\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex items-start gap-2 sm:gap-3 min-w-0 flex-1\">\n {/* Selection Checkbox */}\n {onToggleSelect && (\n <span\n className=\"flex flex-shrink-0 items-center justify-center max-md:min-h-11 max-md:min-w-11 max-md:-m-2 md:mt-0.5\"\n onClick={(e) => {\n e.stopPropagation();\n if (e.target === e.currentTarget) onToggleSelect();\n }}\n >\n <Checkbox\n checked={selected}\n onCheckedChange={() => onToggleSelect()}\n onClick={(e) => e.stopPropagation()}\n aria-label={tr('agentsPage.selectAgent', 'Select {{name}}', { name: agent.name })}\n />\n </span>\n )}\n <div className=\"flex-shrink-0 size-9 sm:size-10 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center\">\n <Server className=\"size-4 sm:size-5 text-action-primary-bg\" aria-hidden=\"true\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center gap-1.5 sm:gap-2 min-w-0\">\n <h3 className=\"font-semibold text-text-primary truncate min-w-0 text-sm sm:text-base tracking-tight\">\n {agent.name}\n </h3>\n <PlatformIcon\n platform={agent.platform}\n className=\"size-4 flex-shrink-0 text-text-secondary\"\n />\n </div>\n <p className=\"text-xs sm:text-sm text-text-secondary truncate\">\n {agent.hostname || `ID: ${agent.id}`}\n </p>\n {(() => {\n // Always-visible (not just hover) guidance when degraded, so the\n // user knows whether it's self-healing or needs their action.\n const health = classifyAgentHealth(status, healthErrorMessage ?? null);\n if (health.phase === 'none') return null;\n return (\n <p\n className={`mt-1 flex items-start gap-1 text-xs ${\n health.phase === 'attention'\n ? 'text-status-warning-text'\n : 'text-content-tertiary'\n }`}\n >\n {health.inProgress && (\n <RefreshCw\n className=\"size-3 mt-0.5 flex-shrink-0 animate-spin\"\n aria-hidden=\"true\"\n />\n )}\n <span className=\"min-w-0 flex-1 break-words\">\n <span className=\"font-medium\">{health.label}.</span> {health.actionHint}\n </span>\n </p>\n );\n })()}\n </div>\n </div>\n {/* Status and Actions - always visible */}\n <div className=\"flex items-center gap-1 sm:gap-2 flex-shrink-0\">\n <AgentStatusBadge\n status={status}\n reason={healthErrorMessage ?? null}\n linkTo={`/vibecontrols/agents/${agent.id}?tab=connection`}\n onClick={(event) => {\n // The card itself has a `onSelect` click target that\n // routes to the agent detail. Stop propagation so a\n // badge click doesn't *also* fire that handler — the\n // anchor's href already routes to the right tab and\n // React Router picks it up via the surrounding shell.\n event.stopPropagation();\n }}\n />\n {(handleConfigure || onStart || onStop) && menuItems.length > 0 && (\n <ActionMenu\n ariaLabel={tr('agentsPage.agentActionsMenu', 'Agent actions menu')}\n title={tr('agentsPage.agentActions', 'Agent actions')}\n align=\"end\"\n items={menuItems}\n />\n )}\n </div>\n </div>\n\n <div className=\"mt-5 grid grid-cols-2 gap-3 text-sm\">\n <div className=\"flex items-center gap-2 text-text-secondary\">\n <Clock className=\"size-4\" aria-hidden=\"true\" />\n <span className=\"tabular-nums\">{formatLastHeartbeat(agent.lastHeartbeat)}</span>\n </div>\n {agent.architecture && (\n <div className=\"flex items-center gap-2 text-text-secondary\">\n <Cpu className=\"size-4\" aria-hidden=\"true\" />\n <span>{agent.architecture}</span>\n </div>\n )}\n </div>\n\n {agent.tunnelUrl && (\n // Plain \"Tunnel: <link>\" row. The colored status chip that used\n // to sit here was a redundant second status surface — the\n // canonical state lives in the <AgentStatusBadge> top-right.\n // The Globe glyph stays as a neutral icon (no status color).\n <div className=\"mt-4 flex items-center gap-2 text-sm\">\n <Globe className=\"size-4 flex-shrink-0 text-text-secondary\" aria-hidden=\"true\" />\n <span className=\"text-text-secondary flex-shrink-0\">Tunnel:</span>\n <a\n href={agent.tunnelUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n onClick={(e) => e.stopPropagation()}\n aria-label={tr('agentsPage.tunnelOpensInNewTab', '{{url}} (opens in new tab)', {\n url: agent.tunnelUrl,\n })}\n className=\"min-w-0 truncate font-mono text-xs text-text-link hover:text-text-linkHover hover:underline\"\n >\n {agent.tunnelUrl}\n </a>\n </div>\n )}\n\n {agent.capabilities && agent.capabilities.length > 0 && (\n <div className=\"mt-4 flex flex-wrap gap-1.5\">\n {agent.capabilities.slice(0, 4).map((cap) => (\n <span\n key={cap}\n className=\"px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded-full\"\n >\n {cap}\n </span>\n ))}\n {agent.capabilities.length > 4 && (\n <span className=\"px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded-full\">\n +{agent.capabilities.length - 4}\n </span>\n )}\n </div>\n )}\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"AgentCard.js","names":[],"sources":["../../../src/components/agents/AgentCard.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { Server, Clock, Cpu, Play, Square, Settings, Globe, Link, RefreshCw } from 'lucide-react';\nimport { Checkbox } from '@burdenoff/fe-libs/ui';\nimport { AgentStatusBadge } from './AgentStatusBadge';\nimport type { Agent } from './types';\nimport { getAgentStatus } from './agentStatus';\nimport { classifyAgentHealth } from '../../utils/agentHealth';\nimport { ActionMenu, type ActionMenuItem } from '../shared/ActionMenu';\nimport { PlatformIcon } from '../shared/PlatformIcon';\nimport { useTr } from '../../shared/hooks/useTr';\n\nexport interface AgentCardProps {\n agent: Agent;\n onSelect?: (agent: Agent) => void;\n onConfigure?: (agent: Agent) => void;\n onSettings?: (agent: Agent) => void;\n onStart?: (agent: Agent) => void;\n onStop?: (agent: Agent) => void;\n onDisconnect?: (agent: Agent) => void;\n onEditTunnelUrl?: (agent: Agent) => void;\n selected?: boolean;\n onToggleSelect?: () => void;\n className?: string;\n /**\n * Last error from /health/ready or the live probe — shown in the badge\n * tooltip when the agent is degraded so users know WHY without curling\n * the tunnel themselves.\n */\n healthErrorMessage?: string | null;\n}\n\n/**\n * AgentCard - Display agent information in a card format\n *\n * @example\n * ```tsx\n * <AgentCard\n * agent={myAgent}\n * onSelect={handleSelect}\n * onConfigure={handleConfigure}\n * />\n * ```\n */\nexport const AgentCard: FC<AgentCardProps> = ({\n agent,\n onSelect,\n onConfigure,\n onSettings,\n onStart,\n onStop,\n onDisconnect,\n onEditTunnelUrl,\n selected = false,\n onToggleSelect,\n className = '',\n healthErrorMessage,\n}) => {\n const tr = useTr();\n // Support both onConfigure and onSettings\n const handleConfigure = onConfigure || onSettings;\n\n const status = getAgentStatus(agent);\n\n const formatLastHeartbeat = (timestamp: string | null | undefined) => {\n if (!timestamp) return tr('agentsPage.never', 'Never');\n const diff = Date.now() - new Date(timestamp).getTime();\n if (diff < 60000) return tr('agentsPage.justNow', 'Just now');\n if (diff < 3600000) return `${Math.floor(diff / 60000)}m ago`;\n if (diff < 86400000) return `${Math.floor(diff / 3600000)}h ago`;\n return `${Math.floor(diff / 86400000)}d ago`;\n };\n\n const menuItems: ActionMenuItem[] = [];\n if (status === 'ACTIVE' && onStop) {\n menuItems.push({\n key: 'stop',\n label: tr('agentsPage.stopAgent', 'Stop Agent'),\n icon: Square,\n onSelect: () => onStop(agent),\n });\n }\n if ((status === 'STOPPED' || status === 'OFFLINE') && onStart) {\n menuItems.push({\n key: 'start',\n label: tr('agentsPage.startAgent', 'Start Agent'),\n icon: Play,\n onSelect: () => onStart(agent),\n });\n }\n if (status === 'DEGRADED' && onStart) {\n // Reconnect on a degraded agent fires the same startVibecontrolsAgent\n // mutation as a fresh start — svc-side that POSTs lifecycle/start to the\n // agent's tunnel, which usually clears transient failures (tunnel hiccup,\n // finalize retry). Labelled \"Reconnect\" not \"Start\" so the user knows\n // it's a recovery action, not creating a new instance.\n menuItems.push({\n key: 'reconnect',\n label: tr('agentsPage.reconnectAgent', 'Reconnect'),\n icon: RefreshCw,\n onSelect: () => onStart(agent),\n });\n }\n if (\n (status === 'ACTIVE' ||\n status === 'STOPPED' ||\n status === 'OFFLINE' ||\n status === 'DEGRADED') &&\n onDisconnect\n ) {\n menuItems.push({\n key: 'disconnect',\n label:\n status === 'OFFLINE'\n ? tr('agentsPage.clearConnection', 'Clear Connection')\n : tr('agentsPage.disconnect', 'Disconnect'),\n icon: Square,\n onSelect: () => onDisconnect(agent),\n variant: 'danger',\n });\n }\n if (onEditTunnelUrl) {\n menuItems.push({\n key: 'edit-tunnel',\n label: tr('agentsPage.editTunnel', 'Edit Tunnel'),\n icon: Link,\n onSelect: () => onEditTunnelUrl(agent),\n });\n }\n if (handleConfigure) {\n menuItems.push({\n key: 'settings',\n label: tr('agentsPage.settings', 'Settings'),\n icon: Settings,\n onSelect: () => handleConfigure(agent),\n });\n }\n\n return (\n <div\n role=\"button\"\n tabIndex={0}\n className={`bg-bg-surface border rounded-card shadow-elevation-1 p-5 h-full hover:border-border-strong hover:shadow-elevation-2 transition-all duration-200 ease-[var(--motion-easing-out)] cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2 focus-visible:ring-offset-bg-canvas ${\n selected\n ? 'border-transparent ring-1 ring-[var(--color-focus-ring)] bg-[var(--color-accent-soft)]'\n : 'border-border-subtle'\n } ${className}`}\n onClick={() => onSelect?.(agent)}\n onKeyDown={(e) => {\n if (e.target !== e.currentTarget) return; // children (menu, checkbox, links) handle their own keys\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n onSelect?.(agent);\n }\n }}\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex items-start gap-2 sm:gap-3 min-w-0 flex-1\">\n {/* Selection Checkbox */}\n {onToggleSelect && (\n <span\n className=\"flex flex-shrink-0 items-center justify-center max-md:min-h-11 max-md:min-w-11 max-md:-m-2 md:mt-0.5\"\n onClick={(e) => {\n e.stopPropagation();\n if (e.target === e.currentTarget) onToggleSelect();\n }}\n >\n <Checkbox\n checked={selected}\n onCheckedChange={() => onToggleSelect()}\n onClick={(e) => e.stopPropagation()}\n aria-label={tr('agentsPage.selectAgent', 'Select {{name}}', { name: agent.name })}\n />\n </span>\n )}\n <div className=\"flex-shrink-0 size-9 sm:size-10 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center\">\n <Server className=\"size-4 sm:size-5 text-action-primary-bg\" aria-hidden=\"true\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center gap-1.5 sm:gap-2 min-w-0\">\n <h3 className=\"font-semibold text-text-primary truncate min-w-0 text-sm sm:text-base tracking-tight\">\n {agent.name}\n </h3>\n <PlatformIcon\n platform={agent.platform}\n className=\"size-4 flex-shrink-0 text-text-secondary\"\n />\n </div>\n <p className=\"text-xs sm:text-sm text-text-secondary truncate\">\n {agent.hostname || `ID: ${agent.id}`}\n </p>\n </div>\n </div>\n {/* Status and Actions - always visible */}\n <div className=\"flex items-center gap-1 sm:gap-2 flex-shrink-0\">\n <AgentStatusBadge\n status={status}\n reason={healthErrorMessage ?? null}\n linkTo={`/vibecontrols/agents/${agent.id}?tab=connection`}\n onClick={(event) => {\n // The card itself has a `onSelect` click target that\n // routes to the agent detail. Stop propagation so a\n // badge click doesn't *also* fire that handler — the\n // anchor's href already routes to the right tab and\n // React Router picks it up via the surrounding shell.\n event.stopPropagation();\n }}\n />\n {(handleConfigure || onStart || onStop) && menuItems.length > 0 && (\n <ActionMenu\n ariaLabel={tr('agentsPage.agentActionsMenu', 'Agent actions menu')}\n title={tr('agentsPage.agentActions', 'Agent actions')}\n align=\"end\"\n items={menuItems}\n />\n )}\n </div>\n </div>\n\n {(() => {\n // Full-width status guidance row (below the header) so the degraded /\n // recovering copy gets the whole card width to wrap on narrow screens,\n // instead of being crushed into the narrow name column next to the\n // status badge + actions menu.\n const health = classifyAgentHealth(status, healthErrorMessage ?? null);\n if (health.phase === 'none') return null;\n return (\n <div\n className={`mt-3 flex items-start gap-1.5 text-xs ${\n health.phase === 'attention' ? 'text-status-warning-text' : 'text-content-tertiary'\n }`}\n >\n {health.inProgress && (\n <RefreshCw className=\"size-3.5 mt-0.5 flex-shrink-0 animate-spin\" aria-hidden=\"true\" />\n )}\n <p className=\"min-w-0 flex-1 break-words leading-relaxed\">\n <span className=\"font-medium\">{health.label}.</span> {health.actionHint}\n </p>\n </div>\n );\n })()}\n\n <div className=\"mt-5 grid grid-cols-2 gap-3 text-sm\">\n <div className=\"flex items-center gap-2 text-text-secondary\">\n <Clock className=\"size-4\" aria-hidden=\"true\" />\n <span className=\"tabular-nums\">{formatLastHeartbeat(agent.lastHeartbeat)}</span>\n </div>\n {agent.architecture && (\n <div className=\"flex items-center gap-2 text-text-secondary\">\n <Cpu className=\"size-4\" aria-hidden=\"true\" />\n <span>{agent.architecture}</span>\n </div>\n )}\n </div>\n\n {agent.tunnelUrl && (\n // Plain \"Tunnel: <link>\" row. The colored status chip that used\n // to sit here was a redundant second status surface — the\n // canonical state lives in the <AgentStatusBadge> top-right.\n // The Globe glyph stays as a neutral icon (no status color).\n <div className=\"mt-4 flex items-center gap-2 text-sm\">\n <Globe className=\"size-4 flex-shrink-0 text-text-secondary\" aria-hidden=\"true\" />\n <span className=\"text-text-secondary flex-shrink-0\">Tunnel:</span>\n <a\n href={agent.tunnelUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n onClick={(e) => e.stopPropagation()}\n aria-label={tr('agentsPage.tunnelOpensInNewTab', '{{url}} (opens in new tab)', {\n url: agent.tunnelUrl,\n })}\n className=\"min-w-0 truncate font-mono text-xs text-text-link hover:text-text-linkHover hover:underline\"\n >\n {agent.tunnelUrl}\n </a>\n </div>\n )}\n\n {agent.capabilities && agent.capabilities.length > 0 && (\n <div className=\"mt-4 flex flex-wrap gap-1.5\">\n {agent.capabilities.slice(0, 4).map((cap) => (\n <span\n key={cap}\n className=\"px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded-full\"\n >\n {cap}\n </span>\n ))}\n {agent.capabilities.length > 4 && (\n <span className=\"px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded-full\">\n +{agent.capabilities.length - 4}\n </span>\n )}\n </div>\n )}\n </div>\n );\n};\n"],"mappings":"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"}
@@ -44,7 +44,7 @@ var o = {
44
44
  return n === e ? t : n;
45
45
  }, h = o[c], g = !!d || !!f, _ = e(c, u), v = [];
46
46
  _.phase === "none" ? u && (c === "DEGRADED" || c === "OFFLINE") && v.push(`${h.label}: ${u}`) : (v.push(`${_.label} — ${_.actionHint}`), u && v.push(`Details: ${u}`)), g && v.push(m("agents.statusBadge.connectionLogHint", s));
47
- let y = v.length > 0 ? v.join(" · ") : void 0, b = !!h.spinner || _.inProgress, x = `inline-flex items-center gap-1 px-2.5 py-0.5 rounded-full text-xs font-medium border transition-colors duration-150 ${h.color} ${l}`, S = g ? " cursor-pointer hover:opacity-90 active:scale-95 transition-transform duration-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-strong" : "", C = /* @__PURE__ */ a(r, { children: [b && /* @__PURE__ */ i(n, {
47
+ let y = v.length > 0 ? v.join(" · ") : void 0, b = !!h.spinner || _.inProgress, x = `inline-flex items-center gap-1 px-2.5 py-0.5 rounded-full text-xs font-medium border transition-colors duration-150 ${h.color} ${l}`, S = g ? " cursor-pointer hover:opacity-90 transition-transform duration-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-strong" : "", C = /* @__PURE__ */ a(r, { children: [b && /* @__PURE__ */ i(n, {
48
48
  className: "size-3 animate-spin",
49
49
  "aria-hidden": "true"
50
50
  }), h.label] });
@@ -1 +1 @@
1
- {"version":3,"file":"AgentStatusBadge.js","names":[],"sources":["../../../src/components/agents/AgentStatusBadge.tsx"],"sourcesContent":["import { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { Loader2 } from 'lucide-react';\nimport type { FC, MouseEvent as ReactMouseEvent } from 'react';\nimport type { AgentStatus } from './types';\nimport { classifyAgentHealth } from '../../utils/agentHealth';\n\nexport interface AgentStatusBadgeProps {\n status: AgentStatus;\n className?: string;\n /**\n * Optional probe error to expose as a hover tooltip (and aria-label).\n * Surfaces the actual reason a `DEGRADED` agent is unhealthy — most\n * commonly \"boot state awaiting-config\" when the gateway-auth push\n * during onboarding silently failed. Without this, users see a yellow\n * badge with no signal about WHY, and have to curl /health/ready\n * themselves to debug.\n */\n reason?: string | null;\n /**\n * Optional href that turns the badge into an anchor pointing at the\n * agent's Connection Log tab. When this is set the badge is keyboard-\n * focusable, supports Ctrl/Cmd-click to open in a new tab, and surfaces\n * a \"click to see connection log\" tooltip suffix so users know it's\n * actionable. Provide together with `onClick` if you also need a SPA\n * navigation handler — the anchor `href` will still be the source of\n * truth for the browser's new-tab affordance.\n */\n linkTo?: string;\n /**\n * SPA navigation callback fired on unmodified left-click. When the\n * caller is already on the agent's detail page this is typically a\n * `setActiveTab('connection')` call; when the caller is on a list view\n * it's typically a `navigate(detailUrl + '?tab=connection')` call. The\n * anchor still owns the modifier-key path so Ctrl-click opens the\n * destination in a new tab regardless.\n */\n onClick?: (event: ReactMouseEvent<HTMLAnchorElement>) => void;\n}\n\n// Subtle-recipe pills: a tinted background + same-hue saturated text + a\n// matching border. This passes WCAG AA in BOTH light and dark themes (unlike\n// the old solid `bg-status-X-bg` + `text-status-X-text`, which was same-hue\n// low-contrast). Transient states (registering/connecting) read NEUTRAL with a\n// spinner instead of off-brand blue — the spinner is the \"working\" signal.\nconst statusConfig: Record<AgentStatus, { label: string; color: string; spinner?: boolean }> = {\n REGISTERING: {\n label: 'Registering',\n color: 'bg-bg-sunken text-text-secondary border-border-default',\n spinner: true,\n },\n CONNECTING: {\n label: 'Connecting',\n color: 'bg-bg-sunken text-text-secondary border-border-default',\n spinner: true,\n },\n ACTIVE: {\n label: 'Active',\n color: 'bg-status-success-bg-subtle text-status-success-text border-status-success-border',\n },\n STOPPED: {\n label: 'Stopped',\n color: 'bg-status-warning-bg-subtle text-status-warning-text border-status-warning-border',\n },\n IDLE: {\n label: 'Idle',\n color: 'bg-status-warning-bg-subtle text-status-warning-text border-status-warning-border',\n },\n DEGRADED: {\n label: 'Degraded',\n color: 'bg-status-warning-bg-subtle text-status-warning-text border-status-warning-border',\n },\n OFFLINE: {\n label: 'Offline',\n color: 'bg-bg-sunken text-text-secondary border-border-default',\n },\n DELETED: {\n label: 'Deleted',\n color: 'bg-status-error-bg-subtle text-status-error-text border-status-error-border',\n },\n};\n\nconst CONNECTION_LOG_HINT = 'See connection log for details';\n\n/**\n * AgentStatusBadge - Display agent status with appropriate styling.\n *\n * When `linkTo` or `onClick` is supplied the badge renders as an anchor\n * and becomes the canonical entry point into the agent's Connection Log\n * tab. This keeps the user one click away from the timeline of probe /\n * register / gateway-auth-push events any time they spot a non-Active\n * badge.\n */\nexport const AgentStatusBadge: FC<AgentStatusBadgeProps> = ({\n status,\n className = '',\n reason,\n linkTo,\n onClick,\n}) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string) => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n const config = statusConfig[status];\n const isInteractive = Boolean(linkTo) || Boolean(onClick);\n\n // Classify a degraded/offline agent so the badge can say whether work is\n // happening behind the scenes (spinner, \"no action needed\") or the user has\n // an action item — rather than just a yellow badge with a raw health string.\n const health = classifyAgentHealth(status, reason);\n\n const tooltipParts: string[] = [];\n if (health.phase !== 'none') {\n tooltipParts.push(`${health.label} — ${health.actionHint}`);\n if (reason) tooltipParts.push(`Details: ${reason}`);\n } else if (reason && (status === 'DEGRADED' || status === 'OFFLINE')) {\n tooltipParts.push(`${config.label}: ${reason}`);\n }\n if (isInteractive) {\n tooltipParts.push(tr('agents.statusBadge.connectionLogHint', CONNECTION_LOG_HINT));\n }\n const tooltip = tooltipParts.length > 0 ? tooltipParts.join(' · ') : undefined;\n\n // Show the in-progress spinner for CONNECTING and for a DEGRADED agent that\n // is still provisioning, so \"something is happening\" is visible at a glance.\n const showSpinner = Boolean(config.spinner) || health.inProgress;\n\n const baseClassName = `inline-flex items-center gap-1 px-2.5 py-0.5 rounded-full text-xs font-medium border transition-colors duration-150 ${config.color} ${className}`;\n const interactiveClassName = isInteractive\n ? ' cursor-pointer hover:opacity-90 active:scale-95 transition-transform duration-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-strong'\n : '';\n\n const content = (\n <>\n {showSpinner && <Loader2 className=\"size-3 animate-spin\" aria-hidden=\"true\" />}\n {config.label}\n </>\n );\n\n if (isInteractive) {\n return (\n <a\n href={linkTo ?? '#'}\n title={tooltip}\n aria-label={tooltip ?? config.label}\n className={baseClassName + interactiveClassName}\n onClick={(event) => {\n // Let modifier-clicks reach the browser's default tab-opener so\n // Ctrl/Cmd-click + middle-click open the connection log in a\n // new tab. SPA navigation only fires on plain left-click.\n if (event.metaKey || event.ctrlKey || event.shiftKey || event.button !== 0) {\n return;\n }\n if (onClick) {\n onClick(event);\n }\n }}\n >\n {content}\n </a>\n );\n }\n\n return (\n <span title={tooltip} aria-label={tooltip ?? config.label} className={baseClassName}>\n {content}\n </span>\n );\n};\n"],"mappings":";;;;;AA4CA,IAAM,IAAyF;CAC7F,aAAa;EACX,OAAO;EACP,OAAO;EACP,SAAS;EACV;CACD,YAAY;EACV,OAAO;EACP,OAAO;EACP,SAAS;EACV;CACD,QAAQ;EACN,OAAO;EACP,OAAO;EACR;CACD,SAAS;EACP,OAAO;EACP,OAAO;EACR;CACD,MAAM;EACJ,OAAO;EACP,OAAO;EACR;CACD,UAAU;EACR,OAAO;EACP,OAAO;EACR;CACD,SAAS;EACP,OAAO;EACP,OAAO;EACR;CACD,SAAS;EACP,OAAO;EACP,OAAO;EACR;CACF,EAEK,IAAsB,kCAWf,KAA+C,EAC1D,WACA,eAAY,IACZ,WACA,WACA,iBACI;CACJ,IAAM,EAAE,SAAM,GAAS,EACjB,KAAM,GAAa,MAAqB;EAC5C,IAAM,IAAI,EAAE,EAAI;AAChB,SAAO,MAAM,IAAM,IAAW;IAE1B,IAAS,EAAa,IACtB,IAAgB,EAAQ,KAAW,EAAQ,GAK3C,IAAS,EAAoB,GAAQ,EAAO,EAE5C,IAAyB,EAAE;AAOjC,CANI,EAAO,UAAU,SAGV,MAAW,MAAW,cAAc,MAAW,cACxD,EAAa,KAAK,GAAG,EAAO,MAAM,IAAI,IAAS,IAH/C,EAAa,KAAK,GAAG,EAAO,MAAM,KAAK,EAAO,aAAa,EACvD,KAAQ,EAAa,KAAK,YAAY,IAAS,GAIjD,KACF,EAAa,KAAK,EAAG,wCAAwC,EAAoB,CAAC;CAEpF,IAAM,IAAU,EAAa,SAAS,IAAI,EAAa,KAAK,MAAM,GAAG,KAAA,GAI/D,IAAc,EAAQ,EAAO,WAAY,EAAO,YAEhD,IAAgB,uHAAuH,EAAO,MAAM,GAAG,KACvJ,IAAuB,IACzB,wKACA,IAEE,IACJ,kBAAA,GAAA,EAAA,UAAA,CACG,KAAe,kBAAC,GAAD;EAAS,WAAU;EAAsB,eAAY;EAAS,CAAA,EAC7E,EAAO,MACP,EAAA,CAAA;AA2BL,QAxBI,IAEA,kBAAC,KAAD;EACE,MAAM,KAAU;EAChB,OAAO;EACP,cAAY,KAAW,EAAO;EAC9B,WAAW,IAAgB;EAC3B,UAAU,MAAU;AAId,KAAM,WAAW,EAAM,WAAW,EAAM,YAAY,EAAM,WAAW,KAGrE,KACF,EAAQ,EAAM;;YAIjB;EACC,CAAA,GAKN,kBAAC,QAAD;EAAM,OAAO;EAAS,cAAY,KAAW,EAAO;EAAO,WAAW;YACnE;EACI,CAAA"}
1
+ {"version":3,"file":"AgentStatusBadge.js","names":[],"sources":["../../../src/components/agents/AgentStatusBadge.tsx"],"sourcesContent":["import { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { Loader2 } from 'lucide-react';\nimport type { FC, MouseEvent as ReactMouseEvent } from 'react';\nimport type { AgentStatus } from './types';\nimport { classifyAgentHealth } from '../../utils/agentHealth';\n\nexport interface AgentStatusBadgeProps {\n status: AgentStatus;\n className?: string;\n /**\n * Optional probe error to expose as a hover tooltip (and aria-label).\n * Surfaces the actual reason a `DEGRADED` agent is unhealthy — most\n * commonly \"boot state awaiting-config\" when the gateway-auth push\n * during onboarding silently failed. Without this, users see a yellow\n * badge with no signal about WHY, and have to curl /health/ready\n * themselves to debug.\n */\n reason?: string | null;\n /**\n * Optional href that turns the badge into an anchor pointing at the\n * agent's Connection Log tab. When this is set the badge is keyboard-\n * focusable, supports Ctrl/Cmd-click to open in a new tab, and surfaces\n * a \"click to see connection log\" tooltip suffix so users know it's\n * actionable. Provide together with `onClick` if you also need a SPA\n * navigation handler — the anchor `href` will still be the source of\n * truth for the browser's new-tab affordance.\n */\n linkTo?: string;\n /**\n * SPA navigation callback fired on unmodified left-click. When the\n * caller is already on the agent's detail page this is typically a\n * `setActiveTab('connection')` call; when the caller is on a list view\n * it's typically a `navigate(detailUrl + '?tab=connection')` call. The\n * anchor still owns the modifier-key path so Ctrl-click opens the\n * destination in a new tab regardless.\n */\n onClick?: (event: ReactMouseEvent<HTMLAnchorElement>) => void;\n}\n\n// Subtle-recipe pills: a tinted background + same-hue saturated text + a\n// matching border. This passes WCAG AA in BOTH light and dark themes (unlike\n// the old solid `bg-status-X-bg` + `text-status-X-text`, which was same-hue\n// low-contrast). Transient states (registering/connecting) read NEUTRAL with a\n// spinner instead of off-brand blue — the spinner is the \"working\" signal.\nconst statusConfig: Record<AgentStatus, { label: string; color: string; spinner?: boolean }> = {\n REGISTERING: {\n label: 'Registering',\n color: 'bg-bg-sunken text-text-secondary border-border-default',\n spinner: true,\n },\n CONNECTING: {\n label: 'Connecting',\n color: 'bg-bg-sunken text-text-secondary border-border-default',\n spinner: true,\n },\n ACTIVE: {\n label: 'Active',\n color: 'bg-status-success-bg-subtle text-status-success-text border-status-success-border',\n },\n STOPPED: {\n label: 'Stopped',\n color: 'bg-status-warning-bg-subtle text-status-warning-text border-status-warning-border',\n },\n IDLE: {\n label: 'Idle',\n color: 'bg-status-warning-bg-subtle text-status-warning-text border-status-warning-border',\n },\n DEGRADED: {\n label: 'Degraded',\n color: 'bg-status-warning-bg-subtle text-status-warning-text border-status-warning-border',\n },\n OFFLINE: {\n label: 'Offline',\n color: 'bg-bg-sunken text-text-secondary border-border-default',\n },\n DELETED: {\n label: 'Deleted',\n color: 'bg-status-error-bg-subtle text-status-error-text border-status-error-border',\n },\n};\n\nconst CONNECTION_LOG_HINT = 'See connection log for details';\n\n/**\n * AgentStatusBadge - Display agent status with appropriate styling.\n *\n * When `linkTo` or `onClick` is supplied the badge renders as an anchor\n * and becomes the canonical entry point into the agent's Connection Log\n * tab. This keeps the user one click away from the timeline of probe /\n * register / gateway-auth-push events any time they spot a non-Active\n * badge.\n */\nexport const AgentStatusBadge: FC<AgentStatusBadgeProps> = ({\n status,\n className = '',\n reason,\n linkTo,\n onClick,\n}) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string) => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n const config = statusConfig[status];\n const isInteractive = Boolean(linkTo) || Boolean(onClick);\n\n // Classify a degraded/offline agent so the badge can say whether work is\n // happening behind the scenes (spinner, \"no action needed\") or the user has\n // an action item — rather than just a yellow badge with a raw health string.\n const health = classifyAgentHealth(status, reason);\n\n const tooltipParts: string[] = [];\n if (health.phase !== 'none') {\n tooltipParts.push(`${health.label} — ${health.actionHint}`);\n if (reason) tooltipParts.push(`Details: ${reason}`);\n } else if (reason && (status === 'DEGRADED' || status === 'OFFLINE')) {\n tooltipParts.push(`${config.label}: ${reason}`);\n }\n if (isInteractive) {\n tooltipParts.push(tr('agents.statusBadge.connectionLogHint', CONNECTION_LOG_HINT));\n }\n const tooltip = tooltipParts.length > 0 ? tooltipParts.join(' · ') : undefined;\n\n // Show the in-progress spinner for CONNECTING and for a DEGRADED agent that\n // is still provisioning, so \"something is happening\" is visible at a glance.\n const showSpinner = Boolean(config.spinner) || health.inProgress;\n\n const baseClassName = `inline-flex items-center gap-1 px-2.5 py-0.5 rounded-full text-xs font-medium border transition-colors duration-150 ${config.color} ${className}`;\n const interactiveClassName = isInteractive\n ? ' cursor-pointer hover:opacity-90 transition-transform duration-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-strong'\n : '';\n\n const content = (\n <>\n {showSpinner && <Loader2 className=\"size-3 animate-spin\" aria-hidden=\"true\" />}\n {config.label}\n </>\n );\n\n if (isInteractive) {\n return (\n <a\n href={linkTo ?? '#'}\n title={tooltip}\n aria-label={tooltip ?? config.label}\n className={baseClassName + interactiveClassName}\n onClick={(event) => {\n // Let modifier-clicks reach the browser's default tab-opener so\n // Ctrl/Cmd-click + middle-click open the connection log in a\n // new tab. SPA navigation only fires on plain left-click.\n if (event.metaKey || event.ctrlKey || event.shiftKey || event.button !== 0) {\n return;\n }\n if (onClick) {\n onClick(event);\n }\n }}\n >\n {content}\n </a>\n );\n }\n\n return (\n <span title={tooltip} aria-label={tooltip ?? config.label} className={baseClassName}>\n {content}\n </span>\n );\n};\n"],"mappings":";;;;;AA4CA,IAAM,IAAyF;CAC7F,aAAa;EACX,OAAO;EACP,OAAO;EACP,SAAS;EACV;CACD,YAAY;EACV,OAAO;EACP,OAAO;EACP,SAAS;EACV;CACD,QAAQ;EACN,OAAO;EACP,OAAO;EACR;CACD,SAAS;EACP,OAAO;EACP,OAAO;EACR;CACD,MAAM;EACJ,OAAO;EACP,OAAO;EACR;CACD,UAAU;EACR,OAAO;EACP,OAAO;EACR;CACD,SAAS;EACP,OAAO;EACP,OAAO;EACR;CACD,SAAS;EACP,OAAO;EACP,OAAO;EACR;CACF,EAEK,IAAsB,kCAWf,KAA+C,EAC1D,WACA,eAAY,IACZ,WACA,WACA,iBACI;CACJ,IAAM,EAAE,SAAM,GAAS,EACjB,KAAM,GAAa,MAAqB;EAC5C,IAAM,IAAI,EAAE,EAAI;AAChB,SAAO,MAAM,IAAM,IAAW;IAE1B,IAAS,EAAa,IACtB,IAAgB,EAAQ,KAAW,EAAQ,GAK3C,IAAS,EAAoB,GAAQ,EAAO,EAE5C,IAAyB,EAAE;AAOjC,CANI,EAAO,UAAU,SAGV,MAAW,MAAW,cAAc,MAAW,cACxD,EAAa,KAAK,GAAG,EAAO,MAAM,IAAI,IAAS,IAH/C,EAAa,KAAK,GAAG,EAAO,MAAM,KAAK,EAAO,aAAa,EACvD,KAAQ,EAAa,KAAK,YAAY,IAAS,GAIjD,KACF,EAAa,KAAK,EAAG,wCAAwC,EAAoB,CAAC;CAEpF,IAAM,IAAU,EAAa,SAAS,IAAI,EAAa,KAAK,MAAM,GAAG,KAAA,GAI/D,IAAc,EAAQ,EAAO,WAAY,EAAO,YAEhD,IAAgB,uHAAuH,EAAO,MAAM,GAAG,KACvJ,IAAuB,IACzB,wJACA,IAEE,IACJ,kBAAA,GAAA,EAAA,UAAA,CACG,KAAe,kBAAC,GAAD;EAAS,WAAU;EAAsB,eAAY;EAAS,CAAA,EAC7E,EAAO,MACP,EAAA,CAAA;AA2BL,QAxBI,IAEA,kBAAC,KAAD;EACE,MAAM,KAAU;EAChB,OAAO;EACP,cAAY,KAAW,EAAO;EAC9B,WAAW,IAAgB;EAC3B,UAAU,MAAU;AAId,KAAM,WAAW,EAAM,WAAW,EAAM,YAAY,EAAM,WAAW,KAGrE,KACF,EAAQ,EAAM;;YAIjB;EACC,CAAA,GAKN,kBAAC,QAAD;EAAM,OAAO;EAAS,cAAY,KAAW,EAAO;EAAO,WAAW;YACnE;EACI,CAAA"}
@@ -14,7 +14,7 @@ var m = {
14
14
  info: "bg-bg-sunken",
15
15
  warn: "bg-status-warning-bg-subtle",
16
16
  error: "bg-status-error-bg-subtle",
17
- debug: "bg-bg-muted"
17
+ debug: "bg-bg-sunken"
18
18
  };
19
19
  function g(e) {
20
20
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"ConnectionLogPanel.js","names":[],"sources":["../../../src/components/agents/ConnectionLogPanel.tsx"],"sourcesContent":["import { useCallback, useMemo, useState } from 'react';\nimport { Check, Copy, Download, FileJson, RefreshCw } from 'lucide-react';\nimport {\n useGetAgentConnectionEventsQuery,\n useGetSessionConnectionEventsQuery,\n} from '@/generated/wspace-operations';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { ActionMenu } from '../shared/ActionMenu';\n\ninterface Props {\n /** Pass agentId XOR sessionId. */\n agentId?: string;\n sessionId?: string;\n /** Friendly label shown in the export filename. */\n agentName?: string;\n sessionName?: string;\n}\n\ninterface EventRow {\n id: string;\n timestamp: string;\n sequence: number;\n step: string;\n level: string;\n message: string;\n detailsJson?: string | null;\n}\n\nconst LEVEL_COLOR: Record<string, string> = {\n info: 'text-text-secondary',\n warn: 'text-status-warning-text',\n error: 'text-status-error-text',\n debug: 'text-text-muted',\n};\n\nconst LEVEL_BG: Record<string, string> = {\n info: 'bg-bg-sunken',\n warn: 'bg-status-warning-bg-subtle',\n error: 'bg-status-error-bg-subtle',\n debug: 'bg-bg-muted',\n};\n\nfunction formatTimestamp(iso: string): string {\n try {\n return new Date(iso).toISOString().replace('T', ' ').replace('Z', '');\n } catch {\n return iso;\n }\n}\n\nfunction eventsAsText(rows: EventRow[]): string {\n return rows\n .map((e) => {\n const detail = e.detailsJson ? `\\n details: ${e.detailsJson}` : '';\n return `[${e.timestamp}] #${e.sequence} ${e.level.toUpperCase().padEnd(5)} ${e.step.padEnd(28)} ${e.message}${detail}`;\n })\n .join('\\n');\n}\n\nfunction eventsAsJson(rows: EventRow[], agentId: string, agentName?: string): string {\n return JSON.stringify(\n {\n exportedAt: new Date().toISOString(),\n agentId,\n agentName,\n count: rows.length,\n events: rows.map((e) => ({\n id: e.id,\n timestamp: e.timestamp,\n sequence: e.sequence,\n step: e.step,\n level: e.level,\n message: e.message,\n details: e.detailsJson ? JSON.parse(e.detailsJson) : null,\n })),\n },\n null,\n 2\n );\n}\n\nfunction downloadBlob(filename: string, mime: string, body: string): void {\n const blob = new Blob([body], { type: mime });\n const url = URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n a.download = filename;\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n URL.revokeObjectURL(url);\n}\n\nconst ICON_BTN =\n 'inline-flex size-9 max-md:size-11 items-center justify-center rounded-button border border-border-default bg-bg-sunken text-text-primary transition-colors hover:bg-bg-elevated focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus';\n\nexport function ConnectionLogPanel({ agentId, sessionId, agentName, sessionName }: Props) {\n // Poll every 3s while the panel is mounted so the operator sees new\n // events appear as the svc records them. The same panel powers both\n // the AgentDetails \"Connection Log\" tab (pass agentId) and the\n // SessionDetails \"Connection Log\" tab (pass sessionId). Apollo\n // skips the query whose id is missing.\n const tr = useTr();\n const isAgent = !!agentId;\n const subjectId = (agentId ?? sessionId) || '';\n const subjectLabel = isAgent ? agentName : sessionName;\n\n const agentQuery = useGetAgentConnectionEventsQuery({\n variables: { agentId: agentId ?? '', limit: 200 },\n pollInterval: 3000,\n fetchPolicy: 'cache-and-network',\n skip: !agentId,\n });\n const sessionQuery = useGetSessionConnectionEventsQuery({\n variables: { sessionId: sessionId ?? '', limit: 200 },\n pollInterval: 3000,\n fetchPolicy: 'cache-and-network',\n skip: !sessionId,\n });\n const { loading, error, refetch } = isAgent ? agentQuery : sessionQuery;\n const [copied, setCopied] = useState<'none' | 'text' | 'json'>('none');\n\n const events: EventRow[] = useMemo(() => {\n const rawList: Array<{\n id: string;\n timestamp: string;\n sequence: number;\n step: string;\n level: string;\n message: string;\n detailsJson?: string | null;\n }> = isAgent\n ? (agentQuery.data?.vibecontrolsAgentConnectionEvents ?? [])\n : (sessionQuery.data?.vibecontrolsSessionConnectionEvents ?? []);\n return rawList.map((e) => ({\n id: e.id,\n timestamp: e.timestamp,\n sequence: e.sequence,\n step: e.step,\n level: e.level,\n message: e.message,\n detailsJson: e.detailsJson ?? null,\n }));\n }, [isAgent, agentQuery.data, sessionQuery.data]);\n\n const handleCopy = useCallback(\n async (mode: 'text' | 'json') => {\n const body =\n mode === 'text' ? eventsAsText(events) : eventsAsJson(events, subjectId, subjectLabel);\n try {\n await navigator.clipboard.writeText(body);\n setCopied(mode);\n setTimeout(() => setCopied('none'), 1500);\n } catch {\n const filename = `${isAgent ? 'agent' : 'session'}-${subjectId}-connection-log.${\n mode === 'text' ? 'txt' : 'json'\n }`;\n downloadBlob(filename, mode === 'text' ? 'text/plain' : 'application/json', body);\n }\n },\n [events, subjectId, subjectLabel, isAgent]\n );\n\n const handleDownload = useCallback(\n (mode: 'text' | 'json') => {\n const body =\n mode === 'text' ? eventsAsText(events) : eventsAsJson(events, subjectId, subjectLabel);\n const stamp = new Date().toISOString().replace(/[:.]/g, '-');\n const prefix = isAgent ? 'agent' : 'session';\n downloadBlob(\n `${prefix}-${subjectLabel ?? subjectId}-connection-log-${stamp}.${mode === 'text' ? 'txt' : 'json'}`,\n mode === 'text' ? 'text/plain' : 'application/json',\n body\n );\n },\n [events, subjectId, subjectLabel, isAgent]\n );\n\n return (\n <div className=\"flex flex-col h-full\">\n <div className=\"flex flex-wrap items-center justify-between gap-3 p-4 border-b border-border-default\">\n <div className=\"min-w-0\">\n <h3 className=\"text-text-primary font-semibold text-base\">Connection Log</h3>\n <p role=\"status\" aria-live=\"polite\" className=\"text-text-muted text-xs mt-0.5\">\n {loading && events.length === 0\n ? 'Loading…'\n : `${events.length} event${events.length === 1 ? '' : 's'} · auto-refresh every 3s`}\n </p>\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => refetch()}\n aria-label={tr('connectionLog.refresh', 'Refresh connection log')}\n className={ICON_BTN}\n >\n <RefreshCw className=\"size-4\" aria-hidden=\"true\" />\n </button>\n <button\n type=\"button\"\n onClick={() => handleCopy('text')}\n aria-label={\n copied === 'text'\n ? tr('connectionLog.copied', 'Copied')\n : tr('connectionLog.copyText', 'Copy log as text')\n }\n className={ICON_BTN}\n >\n {copied === 'text' ? (\n <Check className=\"size-4 text-status-success-text\" aria-hidden=\"true\" />\n ) : (\n <Copy className=\"size-4\" aria-hidden=\"true\" />\n )}\n </button>\n <ActionMenu\n ariaLabel={tr('connectionLog.moreActions', 'More log actions')}\n title={tr('connectionLog.moreActions', 'More log actions')}\n align=\"end\"\n triggerClassName={ICON_BTN}\n items={[\n {\n key: 'copy-json',\n label:\n copied === 'json'\n ? tr('connectionLog.copied', 'Copied')\n : tr('connectionLog.copyJson', 'Copy JSON'),\n icon: Copy,\n onSelect: () => void handleCopy('json'),\n },\n {\n key: 'export-txt',\n label: tr('connectionLog.exportTxt', 'Export .txt'),\n icon: Download,\n onSelect: () => handleDownload('text'),\n separatorBefore: true,\n },\n {\n key: 'export-json',\n label: tr('connectionLog.exportJson', 'Export .json'),\n icon: FileJson,\n onSelect: () => handleDownload('json'),\n },\n ]}\n />\n </div>\n </div>\n\n {error && (\n <div\n role=\"alert\"\n className=\"m-4 p-3 rounded bg-status-error-bg-subtle border border-status-error-border text-status-error-text text-sm\"\n >\n Failed to load events: {error.message}\n </div>\n )}\n\n <div className=\"flex-1 min-h-0 overflow-auto p-4\">\n {events.length === 0 && !loading && (\n <div className=\"text-text-muted text-sm text-center py-12\">\n No connection events recorded for this agent yet.\n <br />\n Events appear here every time the platform probes, registers, or syncs the agent.\n </div>\n )}\n <ol className=\"space-y-2\">\n {events.map((e) => {\n const levelColor = LEVEL_COLOR[e.level] ?? LEVEL_COLOR.info;\n const levelBg = LEVEL_BG[e.level] ?? LEVEL_BG.info;\n const details = (() => {\n if (!e.detailsJson) return null;\n try {\n return JSON.stringify(JSON.parse(e.detailsJson), null, 2);\n } catch {\n return e.detailsJson;\n }\n })();\n return (\n <li key={e.id} className=\"rounded border border-border-default bg-bg-sunken\">\n <div className=\"flex items-start gap-3 p-3\">\n <div\n className={`px-2 py-0.5 rounded text-xs uppercase font-mono ${levelBg} ${levelColor} shrink-0`}\n >\n {e.level}\n </div>\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex flex-wrap items-center gap-2 text-xs text-text-muted font-mono\">\n <span>#{e.sequence}</span>\n <span aria-hidden=\"true\">·</span>\n <span>{formatTimestamp(e.timestamp)}</span>\n <span aria-hidden=\"true\">·</span>\n <span className=\"text-text-primary font-medium\">{e.step}</span>\n </div>\n <div className=\"text-sm text-text-primary mt-1 break-words\">{e.message}</div>\n {details && (\n <details className=\"mt-2\">\n <summary className=\"text-xs text-text-muted cursor-pointer hover:text-text-primary\">\n Details\n </summary>\n <pre className=\"mt-2 p-2 rounded bg-bg-surface text-xs text-text-primary overflow-x-auto whitespace-pre-wrap break-words\">\n {details}\n </pre>\n </details>\n )}\n </div>\n </div>\n </li>\n );\n })}\n </ol>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ConnectionLogPanel.js","names":[],"sources":["../../../src/components/agents/ConnectionLogPanel.tsx"],"sourcesContent":["import { useCallback, useMemo, useState } from 'react';\nimport { Check, Copy, Download, FileJson, RefreshCw } from 'lucide-react';\nimport {\n useGetAgentConnectionEventsQuery,\n useGetSessionConnectionEventsQuery,\n} from '@/generated/wspace-operations';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { ActionMenu } from '../shared/ActionMenu';\n\ninterface Props {\n /** Pass agentId XOR sessionId. */\n agentId?: string;\n sessionId?: string;\n /** Friendly label shown in the export filename. */\n agentName?: string;\n sessionName?: string;\n}\n\ninterface EventRow {\n id: string;\n timestamp: string;\n sequence: number;\n step: string;\n level: string;\n message: string;\n detailsJson?: string | null;\n}\n\nconst LEVEL_COLOR: Record<string, string> = {\n info: 'text-text-secondary',\n warn: 'text-status-warning-text',\n error: 'text-status-error-text',\n debug: 'text-text-muted',\n};\n\nconst LEVEL_BG: Record<string, string> = {\n info: 'bg-bg-sunken',\n warn: 'bg-status-warning-bg-subtle',\n error: 'bg-status-error-bg-subtle',\n debug: 'bg-bg-sunken',\n};\n\nfunction formatTimestamp(iso: string): string {\n try {\n return new Date(iso).toISOString().replace('T', ' ').replace('Z', '');\n } catch {\n return iso;\n }\n}\n\nfunction eventsAsText(rows: EventRow[]): string {\n return rows\n .map((e) => {\n const detail = e.detailsJson ? `\\n details: ${e.detailsJson}` : '';\n return `[${e.timestamp}] #${e.sequence} ${e.level.toUpperCase().padEnd(5)} ${e.step.padEnd(28)} ${e.message}${detail}`;\n })\n .join('\\n');\n}\n\nfunction eventsAsJson(rows: EventRow[], agentId: string, agentName?: string): string {\n return JSON.stringify(\n {\n exportedAt: new Date().toISOString(),\n agentId,\n agentName,\n count: rows.length,\n events: rows.map((e) => ({\n id: e.id,\n timestamp: e.timestamp,\n sequence: e.sequence,\n step: e.step,\n level: e.level,\n message: e.message,\n details: e.detailsJson ? JSON.parse(e.detailsJson) : null,\n })),\n },\n null,\n 2\n );\n}\n\nfunction downloadBlob(filename: string, mime: string, body: string): void {\n const blob = new Blob([body], { type: mime });\n const url = URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n a.download = filename;\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n URL.revokeObjectURL(url);\n}\n\nconst ICON_BTN =\n 'inline-flex size-9 max-md:size-11 items-center justify-center rounded-button border border-border-default bg-bg-sunken text-text-primary transition-colors hover:bg-bg-elevated focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus';\n\nexport function ConnectionLogPanel({ agentId, sessionId, agentName, sessionName }: Props) {\n // Poll every 3s while the panel is mounted so the operator sees new\n // events appear as the svc records them. The same panel powers both\n // the AgentDetails \"Connection Log\" tab (pass agentId) and the\n // SessionDetails \"Connection Log\" tab (pass sessionId). Apollo\n // skips the query whose id is missing.\n const tr = useTr();\n const isAgent = !!agentId;\n const subjectId = (agentId ?? sessionId) || '';\n const subjectLabel = isAgent ? agentName : sessionName;\n\n const agentQuery = useGetAgentConnectionEventsQuery({\n variables: { agentId: agentId ?? '', limit: 200 },\n pollInterval: 3000,\n fetchPolicy: 'cache-and-network',\n skip: !agentId,\n });\n const sessionQuery = useGetSessionConnectionEventsQuery({\n variables: { sessionId: sessionId ?? '', limit: 200 },\n pollInterval: 3000,\n fetchPolicy: 'cache-and-network',\n skip: !sessionId,\n });\n const { loading, error, refetch } = isAgent ? agentQuery : sessionQuery;\n const [copied, setCopied] = useState<'none' | 'text' | 'json'>('none');\n\n const events: EventRow[] = useMemo(() => {\n const rawList: Array<{\n id: string;\n timestamp: string;\n sequence: number;\n step: string;\n level: string;\n message: string;\n detailsJson?: string | null;\n }> = isAgent\n ? (agentQuery.data?.vibecontrolsAgentConnectionEvents ?? [])\n : (sessionQuery.data?.vibecontrolsSessionConnectionEvents ?? []);\n return rawList.map((e) => ({\n id: e.id,\n timestamp: e.timestamp,\n sequence: e.sequence,\n step: e.step,\n level: e.level,\n message: e.message,\n detailsJson: e.detailsJson ?? null,\n }));\n }, [isAgent, agentQuery.data, sessionQuery.data]);\n\n const handleCopy = useCallback(\n async (mode: 'text' | 'json') => {\n const body =\n mode === 'text' ? eventsAsText(events) : eventsAsJson(events, subjectId, subjectLabel);\n try {\n await navigator.clipboard.writeText(body);\n setCopied(mode);\n setTimeout(() => setCopied('none'), 1500);\n } catch {\n const filename = `${isAgent ? 'agent' : 'session'}-${subjectId}-connection-log.${\n mode === 'text' ? 'txt' : 'json'\n }`;\n downloadBlob(filename, mode === 'text' ? 'text/plain' : 'application/json', body);\n }\n },\n [events, subjectId, subjectLabel, isAgent]\n );\n\n const handleDownload = useCallback(\n (mode: 'text' | 'json') => {\n const body =\n mode === 'text' ? eventsAsText(events) : eventsAsJson(events, subjectId, subjectLabel);\n const stamp = new Date().toISOString().replace(/[:.]/g, '-');\n const prefix = isAgent ? 'agent' : 'session';\n downloadBlob(\n `${prefix}-${subjectLabel ?? subjectId}-connection-log-${stamp}.${mode === 'text' ? 'txt' : 'json'}`,\n mode === 'text' ? 'text/plain' : 'application/json',\n body\n );\n },\n [events, subjectId, subjectLabel, isAgent]\n );\n\n return (\n <div className=\"flex flex-col h-full\">\n <div className=\"flex flex-wrap items-center justify-between gap-3 p-4 border-b border-border-default\">\n <div className=\"min-w-0\">\n <h3 className=\"text-text-primary font-semibold text-base\">Connection Log</h3>\n <p role=\"status\" aria-live=\"polite\" className=\"text-text-muted text-xs mt-0.5\">\n {loading && events.length === 0\n ? 'Loading…'\n : `${events.length} event${events.length === 1 ? '' : 's'} · auto-refresh every 3s`}\n </p>\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => refetch()}\n aria-label={tr('connectionLog.refresh', 'Refresh connection log')}\n className={ICON_BTN}\n >\n <RefreshCw className=\"size-4\" aria-hidden=\"true\" />\n </button>\n <button\n type=\"button\"\n onClick={() => handleCopy('text')}\n aria-label={\n copied === 'text'\n ? tr('connectionLog.copied', 'Copied')\n : tr('connectionLog.copyText', 'Copy log as text')\n }\n className={ICON_BTN}\n >\n {copied === 'text' ? (\n <Check className=\"size-4 text-status-success-text\" aria-hidden=\"true\" />\n ) : (\n <Copy className=\"size-4\" aria-hidden=\"true\" />\n )}\n </button>\n <ActionMenu\n ariaLabel={tr('connectionLog.moreActions', 'More log actions')}\n title={tr('connectionLog.moreActions', 'More log actions')}\n align=\"end\"\n triggerClassName={ICON_BTN}\n items={[\n {\n key: 'copy-json',\n label:\n copied === 'json'\n ? tr('connectionLog.copied', 'Copied')\n : tr('connectionLog.copyJson', 'Copy JSON'),\n icon: Copy,\n onSelect: () => void handleCopy('json'),\n },\n {\n key: 'export-txt',\n label: tr('connectionLog.exportTxt', 'Export .txt'),\n icon: Download,\n onSelect: () => handleDownload('text'),\n separatorBefore: true,\n },\n {\n key: 'export-json',\n label: tr('connectionLog.exportJson', 'Export .json'),\n icon: FileJson,\n onSelect: () => handleDownload('json'),\n },\n ]}\n />\n </div>\n </div>\n\n {error && (\n <div\n role=\"alert\"\n className=\"m-4 p-3 rounded bg-status-error-bg-subtle border border-status-error-border text-status-error-text text-sm\"\n >\n Failed to load events: {error.message}\n </div>\n )}\n\n <div className=\"flex-1 min-h-0 overflow-auto p-4\">\n {events.length === 0 && !loading && (\n <div className=\"text-text-muted text-sm text-center py-12\">\n No connection events recorded for this agent yet.\n <br />\n Events appear here every time the platform probes, registers, or syncs the agent.\n </div>\n )}\n <ol className=\"space-y-2\">\n {events.map((e) => {\n const levelColor = LEVEL_COLOR[e.level] ?? LEVEL_COLOR.info;\n const levelBg = LEVEL_BG[e.level] ?? LEVEL_BG.info;\n const details = (() => {\n if (!e.detailsJson) return null;\n try {\n return JSON.stringify(JSON.parse(e.detailsJson), null, 2);\n } catch {\n return e.detailsJson;\n }\n })();\n return (\n <li key={e.id} className=\"rounded border border-border-default bg-bg-sunken\">\n <div className=\"flex items-start gap-3 p-3\">\n <div\n className={`px-2 py-0.5 rounded text-xs uppercase font-mono ${levelBg} ${levelColor} shrink-0`}\n >\n {e.level}\n </div>\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex flex-wrap items-center gap-2 text-xs text-text-muted font-mono\">\n <span>#{e.sequence}</span>\n <span aria-hidden=\"true\">·</span>\n <span>{formatTimestamp(e.timestamp)}</span>\n <span aria-hidden=\"true\">·</span>\n <span className=\"text-text-primary font-medium\">{e.step}</span>\n </div>\n <div className=\"text-sm text-text-primary mt-1 break-words\">{e.message}</div>\n {details && (\n <details className=\"mt-2\">\n <summary className=\"text-xs text-text-muted cursor-pointer hover:text-text-primary\">\n Details\n </summary>\n <pre className=\"mt-2 p-2 rounded bg-bg-surface text-xs text-text-primary overflow-x-auto whitespace-pre-wrap break-words\">\n {details}\n </pre>\n </details>\n )}\n </div>\n </div>\n </li>\n );\n })}\n </ol>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -383,7 +383,7 @@ function y({ availablePlugins: y, tunnelUrl: b, agentId: x, profile: S }) {
383
383
  /* @__PURE__ */ m("button", {
384
384
  type: "button",
385
385
  onClick: () => I(e.id),
386
- className: "mt-4 px-4 py-2 bg-bg-sunken text-text-primary text-sm rounded-md hover:bg-bg-muted transition-colors inline-flex items-center gap-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
386
+ className: "mt-4 px-4 py-2 bg-bg-sunken text-text-primary text-sm rounded-md hover:bg-bg-sunken transition-colors inline-flex items-center gap-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
387
387
  children: [/* @__PURE__ */ p(c, {
388
388
  className: "size-3.5",
389
389
  "aria-hidden": "true"