@burdenoff/microfe-vibecontrols 2026.731.1 → 2026.801.1
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.
- package/dist/VibeControlsRoutes.js +1 -1
- package/dist/VibeControlsRoutes.js.map +1 -1
- package/dist/components/actions/ActionSchedulesList.js +1 -1
- package/dist/components/actions/ActionSchedulesList.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerBookmarksDrawer.js +1 -1
- package/dist/components/agent-manager/AgentManagerBookmarksDrawer.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerChatPane.js +1 -1
- package/dist/components/agent-manager/AgentManagerChatPane.js.map +1 -1
- package/dist/components/ai/AIModeToggle.js +1 -1
- package/dist/components/ai/AIModeToggle.js.map +1 -1
- package/dist/components/common/ActionAgentTargetNotice.js +1 -1
- package/dist/components/common/ActionAgentTargetNotice.js.map +1 -1
- package/dist/components/docs/DocsFullScreenViewer.js +1 -1
- package/dist/components/docs/DocsFullScreenViewer.js.map +1 -1
- package/dist/components/notes/NotesTabPanel.js +1 -1
- package/dist/components/notes/NotesTabPanel.js.map +1 -1
- package/dist/components/tunnels/TunnelCreateWizard.js +1 -1
- package/dist/components/tunnels/TunnelCreateWizard.js.map +1 -1
- package/dist/hooks/useQueryBackedTab.js +1 -1
- package/dist/hooks/useQueryBackedTab.js.map +1 -1
- package/dist/hooks/useTenantContext.js +1 -1
- package/dist/hooks/useTenantContext.js.map +1 -1
- package/dist/pages/ActionsPage.js +1 -1
- package/dist/pages/ActionsPage.js.map +1 -1
- package/dist/pages/AgentDetailsPage.js +1 -1
- package/dist/pages/AgentDetailsPage.js.map +1 -1
- package/dist/pages/AgentsLandingPage.js +1 -1
- package/dist/pages/AgentsLandingPage.js.map +1 -1
- package/dist/pages/AgentsPage.js +1 -1
- package/dist/pages/AgentsPage.js.map +1 -1
- package/dist/pages/CatalogDetailPage.js +1 -1
- package/dist/pages/CatalogDetailPage.js.map +1 -1
- package/dist/pages/CatalogDocsPage.js +1 -1
- package/dist/pages/CatalogDocsPage.js.map +1 -1
- package/dist/pages/ComponentDetailPage.js +1 -1
- package/dist/pages/ComponentDetailPage.js.map +1 -1
- package/dist/pages/ComponentDocsPage.js +1 -1
- package/dist/pages/ComponentDocsPage.js.map +1 -1
- package/dist/pages/GettingStartedPage.js +1 -1
- package/dist/pages/GettingStartedPage.js.map +1 -1
- package/dist/pages/NoteDetailsPage.js +1 -1
- package/dist/pages/NoteDetailsPage.js.map +1 -1
- package/dist/pages/ScheduledJobDetailPage.js +1 -1
- package/dist/pages/ScheduledJobDetailPage.js.map +1 -1
- package/dist/pages/SessionDetailsPage.js +1 -1
- package/dist/pages/SessionDetailsPage.js.map +1 -1
- package/dist/pages/SessionsPage.js +1 -1
- package/dist/pages/SessionsPage.js.map +1 -1
- package/dist/pages/SharedSessionPage.js +1 -1
- package/dist/pages/SharedSessionPage.js.map +1 -1
- package/dist/pages/SharedVibeDeckPage.js +1 -1
- package/dist/pages/SharedVibeDeckPage.js.map +1 -1
- package/dist/pages/TargetDetailsPage.js +1 -1
- package/dist/pages/TargetDetailsPage.js.map +1 -1
- package/dist/pages/TargetsPage.js +1 -1
- package/dist/pages/TargetsPage.js.map +1 -1
- package/dist/pages/TemplateDetailPage.js +1 -1
- package/dist/pages/TemplateDetailPage.js.map +1 -1
- package/dist/pages/TunnelsLandingPage.js +1 -1
- package/dist/pages/TunnelsLandingPage.js.map +1 -1
- package/dist/pages/VibeCalendarPage.js +1 -1
- package/dist/pages/VibeCalendarPage.js.map +1 -1
- package/dist/pages/VibeDetailsPage.js +1 -1
- package/dist/pages/VibeDetailsPage.js.map +1 -1
- package/dist/pages/VibeDocsPage.js +1 -1
- package/dist/pages/VibeDocsPage.js.map +1 -1
- package/dist/pages/VibeTaskDetailPage.js +1 -1
- package/dist/pages/VibeTaskDetailPage.js.map +1 -1
- package/dist/pages/VibesPage.js +1 -1
- package/dist/pages/VibesPage.js.map +1 -1
- package/dist/pages/ai/AIBookmarksPage.js +1 -1
- package/dist/pages/ai/AIBookmarksPage.js.map +1 -1
- package/dist/pages/ai/AIBuilderPage.js +1 -1
- package/dist/pages/ai/AIBuilderPage.js.map +1 -1
- package/dist/pages/ai/AIPage.js +1 -1
- package/dist/pages/ai/AIPage.js.map +1 -1
- package/dist/pages/ai/AISessionDetailPage.js +1 -1
- package/dist/pages/ai/AISessionDetailPage.js.map +1 -1
- package/dist/pages/ai/AgentManagerPage.js +1 -1
- package/dist/pages/ai/AgentManagerPage.js.map +1 -1
- package/dist/pages/tunnels/TunnelDetailsPage.js +1 -1
- package/dist/pages/tunnels/TunnelDetailsPage.js.map +1 -1
- package/dist/pages/tunnels/TunnelsTab.js +1 -1
- package/dist/pages/tunnels/TunnelsTab.js.map +1 -1
- package/dist/pages/tunnels/UsageTab.js +1 -1
- package/dist/pages/tunnels/UsageTab.js.map +1 -1
- package/dist/providers/VibeControlsProvider.js +1 -1
- package/dist/providers/VibeControlsProvider.js.map +1 -1
- package/package.json +3 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useTr as e } from "./shared/hooks/useTr.js";
|
|
2
2
|
import t, { Suspense as n, useEffect as r } from "react";
|
|
3
|
-
import { Navigate as i, Route as a, Routes as ee, useLocation as o, useNavigate as s, useParams as c } from "react-router
|
|
3
|
+
import { Navigate as i, Route as a, Routes as ee, useLocation as o, useNavigate as s, useParams as c } from "react-router";
|
|
4
4
|
import { jsx as l, jsxs as u } from "react/jsx-runtime";
|
|
5
5
|
import { FeatureGate as te } from "@burdenoff/fe-libs/shared/feature-flags";
|
|
6
6
|
import { NotFoundPage as d } from "@burdenoff/fe-libs/shared/pages";
|
|
@@ -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-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":"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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';\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":"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"}
|
|
@@ -3,7 +3,7 @@ import { useTr as t } from "../../shared/hooks/useTr.js";
|
|
|
3
3
|
import { useMemo as n } from "react";
|
|
4
4
|
import { useI18n as r } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
5
5
|
import { AlarmClock as i, ArrowUpRight as a, Plus as o } from "lucide-react";
|
|
6
|
-
import { useNavigate as s } from "react-router
|
|
6
|
+
import { useNavigate as s } from "react-router";
|
|
7
7
|
import { motion as c } from "framer-motion";
|
|
8
8
|
import { jsx as l, jsxs as u } from "react/jsx-runtime";
|
|
9
9
|
//#region src/components/actions/ActionSchedulesList.tsx
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ActionSchedulesList.js","names":[],"sources":["../../../src/components/actions/ActionSchedulesList.tsx"],"sourcesContent":["import { useMemo } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport { AlarmClock, ArrowUpRight, Plus } from 'lucide-react';\nimport { motion } from 'framer-motion';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useGetScheduledJobsForCalendarQuery } from '@/generated/wspace-operations';\nimport type { GetScheduledJobsForCalendarQuery } from '@/generated/wspace-operations';\nimport type { JobStatus, ScheduleType } from '@/generated/wspace-types';\nimport { useTr } from '@/shared/hooks/useTr';\n\ntype ScheduledJobItem = NonNullable<\n GetScheduledJobsForCalendarQuery['scheduledJobs']['jobs']\n>[number];\n\ninterface ActionSchedulesListProps {\n actionId: string;\n workspaceId: string;\n actionName: string;\n onClose: () => void;\n}\n\nconst STATUS_LABEL: Record<string, string> = {\n ACTIVE: 'active',\n PENDING: 'pending',\n PAUSED: 'paused',\n COMPLETED: 'completed',\n FAILED: 'failed',\n CANCELLED: 'cancelled',\n DISABLED: 'disabled',\n ARCHIVED: 'archived',\n DRAFT: 'draft',\n};\n\nconst STATUS_TONE: Record<string, string> = {\n ACTIVE: 'text-status-success-text',\n PENDING: 'text-status-info-text',\n PAUSED: 'text-text-secondary',\n COMPLETED: 'text-status-success-text',\n FAILED: 'text-status-error-text',\n CANCELLED: 'text-text-placeholder',\n DISABLED: 'text-text-placeholder',\n ARCHIVED: 'text-text-placeholder',\n DRAFT: 'text-text-secondary',\n};\n\nconst SCHEDULE_LABEL: Record<ScheduleType | string, string> = {\n CRON: 'Cron',\n RRULE: 'Recurrence',\n ONE_TIME: 'One-time',\n FIXED_INTERVAL: 'Interval',\n WINDOWED: 'Windowed',\n};\n\nfunction makeFormatTime(locale: string | undefined) {\n return (dateStr: string | null | undefined): string => {\n if (!dateStr) return '—';\n // Pass the app locale through so the displayed format matches\n // whatever the user picked in the shell, not the browser default.\n return new Date(dateStr).toLocaleString(locale || undefined);\n };\n}\n\nfunction makeDescribeSchedule(formatTime: (d: string | null | undefined) => string) {\n return (job: {\n scheduleType: ScheduleType;\n cronExpression?: string | null;\n scheduledAt?: string | null;\n }): string => {\n if (job.scheduleType === 'CRON' && job.cronExpression) return job.cronExpression;\n if (job.scheduleType === 'ONE_TIME' && job.scheduledAt) return formatTime(job.scheduledAt);\n return SCHEDULE_LABEL[job.scheduleType] ?? job.scheduleType;\n };\n}\n\nexport function ActionSchedulesList({\n actionId,\n workspaceId,\n actionName,\n onClose,\n}: ActionSchedulesListProps) {\n const tr = useTr();\n const { locale } = useI18n();\n const navigate = useNavigate();\n const formatTime = useMemo(() => makeFormatTime(locale), [locale]);\n const describeSchedule = useMemo(() => makeDescribeSchedule(formatTime), [formatTime]);\n\n // The scheduler's `JobFilterInput` doesn't support `actionId` today, so we\n // page through up to `pageSize` jobs and filter client-side. The action page\n // is unlikely to need more than a handful of schedules per action; if a\n // workspace ever scales past this, push `actionId` into JobFilterInput.\n const pageSize = 100;\n const { data, loading, error } = useGetScheduledJobsForCalendarQuery({\n variables: { workspaceId, pagination: { first: pageSize } },\n fetchPolicy: 'cache-and-network',\n });\n\n const totalFetched = data?.scheduledJobs?.jobs?.length ?? 0;\n // The query returns at most `pageSize` jobs and we filter client-side,\n // so if the result set hits the page boundary AND the workspace's\n // overall total exceeds it, schedules for this action could be on a\n // later page we never fetched. Surface a warning rather than silently\n // showing an incomplete list. (Long-term fix: add `actionId` to\n // `JobFilterInput` so the server can return the full result set.)\n const totalAvailable = data?.scheduledJobs?.total ?? totalFetched;\n const mayBeTruncated = totalFetched >= pageSize && totalAvailable > totalFetched;\n\n const schedules = useMemo<ScheduledJobItem[]>(() => {\n const jobs: ScheduledJobItem[] = data?.scheduledJobs?.jobs ?? [];\n return jobs\n .filter((j: ScheduledJobItem) => j.actionId === actionId)\n .filter((j: ScheduledJobItem) => (j.status as JobStatus) !== 'ARCHIVED');\n }, [data, actionId]);\n\n return (\n <motion.div\n className=\"bg-bg-surface border border-border-default rounded-lg overflow-hidden\"\n initial={{ opacity: 0, height: 0 }}\n animate={{ opacity: 1, height: 'auto' }}\n exit={{ opacity: 0, height: 0 }}\n transition={{ duration: 0.2 }}\n >\n <div className=\"flex items-center justify-between px-4 py-3 border-b border-border-default bg-bg-sunken/50\">\n <h3 className=\"text-sm font-semibold text-text-primary inline-flex items-center gap-2\">\n <AlarmClock size={14} className=\"text-text-secondary\" />\n {tr('vibecontrols.actions.schedules.title', 'Schedules')} - {actionName}\n {schedules.length > 0 && (\n <span className=\"text-xs font-normal text-text-placeholder\">({schedules.length})</span>\n )}\n </h3>\n <div className=\"flex items-center gap-3\">\n <button\n type=\"button\"\n onClick={() => navigate(`/scheduler?actionId=${encodeURIComponent(actionId)}`)}\n className=\"text-xs text-text-secondary hover:text-text-primary transition-colors inline-flex items-center gap-1\"\n >\n <Plus size={12} />\n {tr('vibecontrols.actions.schedules.add', 'New schedule')}\n </button>\n <button\n type=\"button\"\n onClick={onClose}\n className=\"text-xs text-text-secondary hover:text-text-primary transition-colors\"\n >\n {tr('vibecontrols.actions.schedules.close', 'Close')}\n </button>\n </div>\n </div>\n\n {mayBeTruncated && (\n <div className=\"px-4 py-2 text-xs text-status-warning-text bg-status-warning-surface/40 border-b border-border-default\">\n {tr(\n 'vibecontrols.actions.schedules.truncated',\n `Showing the first ${pageSize} schedules across this workspace; some schedules for this action may not appear here.`\n )}\n </div>\n )}\n\n {loading && schedules.length === 0 ? (\n <div className=\"px-4 py-6 text-center text-sm text-text-secondary\">\n {tr('vibecontrols.actions.schedules.loading', 'Loading schedules…')}\n </div>\n ) : error ? (\n <div className=\"px-4 py-6 text-center text-sm text-status-error-text\">\n {tr('vibecontrols.actions.schedules.error', 'Failed to load schedules')}\n </div>\n ) : schedules.length === 0 ? (\n <div className=\"px-4 py-6 text-center text-sm text-text-secondary\">\n {tr('vibecontrols.actions.schedules.empty', 'No schedules attached to this action')}\n </div>\n ) : (\n <div className=\"divide-y divide-border-default\">\n {schedules.map((job: ScheduledJobItem) => {\n const tone = STATUS_TONE[job.status] ?? 'text-text-secondary';\n const statusKey = STATUS_LABEL[job.status] ?? job.status.toLowerCase();\n return (\n <button\n type=\"button\"\n key={job.id}\n onClick={() => navigate(`/scheduler/${encodeURIComponent(job.id)}`)}\n className=\"w-full text-left px-4 py-3 hover:bg-bg-sunken/30 transition-colors flex items-center justify-between gap-3\"\n >\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm font-medium text-text-primary truncate\">\n {job.name || job.id}\n </span>\n <span className={`text-xs capitalize ${tone}`}>{statusKey}</span>\n </div>\n <div className=\"text-xs text-text-placeholder mt-0.5 truncate\">\n {describeSchedule(job)} · next {formatTime(job.nextExecutionAt)}\n </div>\n </div>\n <ArrowUpRight size={14} className=\"text-text-placeholder flex-shrink-0\" />\n </button>\n );\n })}\n </div>\n )}\n </motion.div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ActionSchedulesList.js","names":[],"sources":["../../../src/components/actions/ActionSchedulesList.tsx"],"sourcesContent":["import { useMemo } from 'react';\nimport { useNavigate } from 'react-router';\nimport { AlarmClock, ArrowUpRight, Plus } from 'lucide-react';\nimport { motion } from 'framer-motion';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useGetScheduledJobsForCalendarQuery } from '@/generated/wspace-operations';\nimport type { GetScheduledJobsForCalendarQuery } from '@/generated/wspace-operations';\nimport type { JobStatus, ScheduleType } from '@/generated/wspace-types';\nimport { useTr } from '@/shared/hooks/useTr';\n\ntype ScheduledJobItem = NonNullable<\n GetScheduledJobsForCalendarQuery['scheduledJobs']['jobs']\n>[number];\n\ninterface ActionSchedulesListProps {\n actionId: string;\n workspaceId: string;\n actionName: string;\n onClose: () => void;\n}\n\nconst STATUS_LABEL: Record<string, string> = {\n ACTIVE: 'active',\n PENDING: 'pending',\n PAUSED: 'paused',\n COMPLETED: 'completed',\n FAILED: 'failed',\n CANCELLED: 'cancelled',\n DISABLED: 'disabled',\n ARCHIVED: 'archived',\n DRAFT: 'draft',\n};\n\nconst STATUS_TONE: Record<string, string> = {\n ACTIVE: 'text-status-success-text',\n PENDING: 'text-status-info-text',\n PAUSED: 'text-text-secondary',\n COMPLETED: 'text-status-success-text',\n FAILED: 'text-status-error-text',\n CANCELLED: 'text-text-placeholder',\n DISABLED: 'text-text-placeholder',\n ARCHIVED: 'text-text-placeholder',\n DRAFT: 'text-text-secondary',\n};\n\nconst SCHEDULE_LABEL: Record<ScheduleType | string, string> = {\n CRON: 'Cron',\n RRULE: 'Recurrence',\n ONE_TIME: 'One-time',\n FIXED_INTERVAL: 'Interval',\n WINDOWED: 'Windowed',\n};\n\nfunction makeFormatTime(locale: string | undefined) {\n return (dateStr: string | null | undefined): string => {\n if (!dateStr) return '—';\n // Pass the app locale through so the displayed format matches\n // whatever the user picked in the shell, not the browser default.\n return new Date(dateStr).toLocaleString(locale || undefined);\n };\n}\n\nfunction makeDescribeSchedule(formatTime: (d: string | null | undefined) => string) {\n return (job: {\n scheduleType: ScheduleType;\n cronExpression?: string | null;\n scheduledAt?: string | null;\n }): string => {\n if (job.scheduleType === 'CRON' && job.cronExpression) return job.cronExpression;\n if (job.scheduleType === 'ONE_TIME' && job.scheduledAt) return formatTime(job.scheduledAt);\n return SCHEDULE_LABEL[job.scheduleType] ?? job.scheduleType;\n };\n}\n\nexport function ActionSchedulesList({\n actionId,\n workspaceId,\n actionName,\n onClose,\n}: ActionSchedulesListProps) {\n const tr = useTr();\n const { locale } = useI18n();\n const navigate = useNavigate();\n const formatTime = useMemo(() => makeFormatTime(locale), [locale]);\n const describeSchedule = useMemo(() => makeDescribeSchedule(formatTime), [formatTime]);\n\n // The scheduler's `JobFilterInput` doesn't support `actionId` today, so we\n // page through up to `pageSize` jobs and filter client-side. The action page\n // is unlikely to need more than a handful of schedules per action; if a\n // workspace ever scales past this, push `actionId` into JobFilterInput.\n const pageSize = 100;\n const { data, loading, error } = useGetScheduledJobsForCalendarQuery({\n variables: { workspaceId, pagination: { first: pageSize } },\n fetchPolicy: 'cache-and-network',\n });\n\n const totalFetched = data?.scheduledJobs?.jobs?.length ?? 0;\n // The query returns at most `pageSize` jobs and we filter client-side,\n // so if the result set hits the page boundary AND the workspace's\n // overall total exceeds it, schedules for this action could be on a\n // later page we never fetched. Surface a warning rather than silently\n // showing an incomplete list. (Long-term fix: add `actionId` to\n // `JobFilterInput` so the server can return the full result set.)\n const totalAvailable = data?.scheduledJobs?.total ?? totalFetched;\n const mayBeTruncated = totalFetched >= pageSize && totalAvailable > totalFetched;\n\n const schedules = useMemo<ScheduledJobItem[]>(() => {\n const jobs: ScheduledJobItem[] = data?.scheduledJobs?.jobs ?? [];\n return jobs\n .filter((j: ScheduledJobItem) => j.actionId === actionId)\n .filter((j: ScheduledJobItem) => (j.status as JobStatus) !== 'ARCHIVED');\n }, [data, actionId]);\n\n return (\n <motion.div\n className=\"bg-bg-surface border border-border-default rounded-lg overflow-hidden\"\n initial={{ opacity: 0, height: 0 }}\n animate={{ opacity: 1, height: 'auto' }}\n exit={{ opacity: 0, height: 0 }}\n transition={{ duration: 0.2 }}\n >\n <div className=\"flex items-center justify-between px-4 py-3 border-b border-border-default bg-bg-sunken/50\">\n <h3 className=\"text-sm font-semibold text-text-primary inline-flex items-center gap-2\">\n <AlarmClock size={14} className=\"text-text-secondary\" />\n {tr('vibecontrols.actions.schedules.title', 'Schedules')} - {actionName}\n {schedules.length > 0 && (\n <span className=\"text-xs font-normal text-text-placeholder\">({schedules.length})</span>\n )}\n </h3>\n <div className=\"flex items-center gap-3\">\n <button\n type=\"button\"\n onClick={() => navigate(`/scheduler?actionId=${encodeURIComponent(actionId)}`)}\n className=\"text-xs text-text-secondary hover:text-text-primary transition-colors inline-flex items-center gap-1\"\n >\n <Plus size={12} />\n {tr('vibecontrols.actions.schedules.add', 'New schedule')}\n </button>\n <button\n type=\"button\"\n onClick={onClose}\n className=\"text-xs text-text-secondary hover:text-text-primary transition-colors\"\n >\n {tr('vibecontrols.actions.schedules.close', 'Close')}\n </button>\n </div>\n </div>\n\n {mayBeTruncated && (\n <div className=\"px-4 py-2 text-xs text-status-warning-text bg-status-warning-surface/40 border-b border-border-default\">\n {tr(\n 'vibecontrols.actions.schedules.truncated',\n `Showing the first ${pageSize} schedules across this workspace; some schedules for this action may not appear here.`\n )}\n </div>\n )}\n\n {loading && schedules.length === 0 ? (\n <div className=\"px-4 py-6 text-center text-sm text-text-secondary\">\n {tr('vibecontrols.actions.schedules.loading', 'Loading schedules…')}\n </div>\n ) : error ? (\n <div className=\"px-4 py-6 text-center text-sm text-status-error-text\">\n {tr('vibecontrols.actions.schedules.error', 'Failed to load schedules')}\n </div>\n ) : schedules.length === 0 ? (\n <div className=\"px-4 py-6 text-center text-sm text-text-secondary\">\n {tr('vibecontrols.actions.schedules.empty', 'No schedules attached to this action')}\n </div>\n ) : (\n <div className=\"divide-y divide-border-default\">\n {schedules.map((job: ScheduledJobItem) => {\n const tone = STATUS_TONE[job.status] ?? 'text-text-secondary';\n const statusKey = STATUS_LABEL[job.status] ?? job.status.toLowerCase();\n return (\n <button\n type=\"button\"\n key={job.id}\n onClick={() => navigate(`/scheduler/${encodeURIComponent(job.id)}`)}\n className=\"w-full text-left px-4 py-3 hover:bg-bg-sunken/30 transition-colors flex items-center justify-between gap-3\"\n >\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm font-medium text-text-primary truncate\">\n {job.name || job.id}\n </span>\n <span className={`text-xs capitalize ${tone}`}>{statusKey}</span>\n </div>\n <div className=\"text-xs text-text-placeholder mt-0.5 truncate\">\n {describeSchedule(job)} · next {formatTime(job.nextExecutionAt)}\n </div>\n </div>\n <ArrowUpRight size={14} className=\"text-text-placeholder flex-shrink-0\" />\n </button>\n );\n })}\n </div>\n )}\n </motion.div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -4,7 +4,7 @@ import { VIBE_PERMISSIONS as i } from "../../constants/permissions.js";
|
|
|
4
4
|
import { relativeTime as a } from "../../utils/relativeTime.js";
|
|
5
5
|
import { useState as o } from "react";
|
|
6
6
|
import { Bookmark as s, Check as c, ChevronRight as l, ExternalLink as u, Pencil as d, Trash2 as f, X as p } from "lucide-react";
|
|
7
|
-
import { useNavigate as m } from "react-router
|
|
7
|
+
import { useNavigate as m } from "react-router";
|
|
8
8
|
import { usePermissions as h } from "@burdenoff/fe-libs/shared/providers/shell";
|
|
9
9
|
import { jsx as g, jsxs as _ } from "react/jsx-runtime";
|
|
10
10
|
//#region src/components/agent-manager/AgentManagerBookmarksDrawer.tsx
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AgentManagerBookmarksDrawer.js","names":[],"sources":["../../../src/components/agent-manager/AgentManagerBookmarksDrawer.tsx"],"sourcesContent":["/**\n * Side drawer listing the current user's bookmarks for ONE chat session.\n *\n * Each row resolves its message body from the currently-loaded session\n * messages. When the bookmarked message is no longer in the session\n * (agent log pruning / rotation), the row degrades to a \"Message no\n * longer available\" affordance with a remove-only action.\n */\n\nimport { useState, type FC } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport { Bookmark, X, ChevronRight, Pencil, Trash2, Check, ExternalLink } from 'lucide-react';\nimport { relativeTime } from '@/utils/relativeTime';\nimport {\n useDeleteAgentMessageBookmarkMutation,\n useMyAgentMessageBookmarksQuery,\n useUpdateAgentMessageBookmarkMutation,\n type AgentMessageBookmarkFieldsFragment,\n} from '../../generated/wspace-operations';\nimport type { AgentManagerMessage } from '@/types/agentManager';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { VIBE_PERMISSIONS } from '@/constants/permissions';\n\ninterface AgentManagerBookmarksDrawerProps {\n open: boolean;\n onClose: () => void;\n sessionId: string;\n messages: AgentManagerMessage[];\n onJump: (messageId: string) => void;\n}\n\nconst PREVIEW_LEN = 80;\n\nfunction snippet(content: string): string {\n if (!content) return '';\n const trimmed = content.trim().replace(/\\s+/g, ' ');\n return trimmed.length > PREVIEW_LEN ? `${trimmed.slice(0, PREVIEW_LEN)}…` : trimmed;\n}\n\nexport const AgentManagerBookmarksDrawer: FC<AgentManagerBookmarksDrawerProps> = ({\n open,\n onClose,\n sessionId,\n messages,\n onJump,\n}) => {\n const navigate = useNavigate();\n const tr = useTr();\n const { hasPermission } = usePermissions();\n const canUpdateBookmark = hasPermission(VIBE_PERMISSIONS.AGENT_MESSAGE_BOOKMARK_UPDATE);\n const canDeleteBookmark = hasPermission(VIBE_PERMISSIONS.AGENT_MESSAGE_BOOKMARK_DELETE);\n const [editingId, setEditingId] = useState<string | null>(null);\n const [labelDraft, setLabelDraft] = useState('');\n\n const { data, loading } = useMyAgentMessageBookmarksQuery({\n variables: { filter: { sessionId } },\n skip: !open,\n fetchPolicy: 'cache-and-network',\n });\n\n const [updateMutation] = useUpdateAgentMessageBookmarkMutation({\n refetchQueries: ['MyAgentMessageBookmarks'],\n });\n const [deleteMutation] = useDeleteAgentMessageBookmarkMutation({\n refetchQueries: ['MyAgentMessageBookmarks'],\n });\n\n const bookmarks = data?.myAgentMessageBookmarks.items ?? [];\n const messageById = new Map(messages.map((m) => [m.id, m]));\n\n const startEdit = (id: string, current: string | null | undefined) => {\n setEditingId(id);\n setLabelDraft(current ?? '');\n };\n\n const saveEdit = async (id: string) => {\n await updateMutation({ variables: { id, input: { label: labelDraft || null } } });\n setEditingId(null);\n setLabelDraft('');\n };\n\n const removeBookmark = async (id: string) => {\n await deleteMutation({ variables: { id } });\n };\n\n if (!open) return null;\n\n return (\n <div className=\"fixed inset-0 z-40 flex\">\n <div className=\"flex-1 bg-bg-overlay/40\" onClick={onClose} aria-hidden />\n <aside\n className=\"w-full max-w-md h-full bg-bg-surface border-l border-border-default shadow-xl flex flex-col\"\n role=\"dialog\"\n aria-label={tr(\n 'vibecontrols.agentManager.bookmarksDrawer.title',\n 'Bookmarks in this session'\n )}\n data-testid=\"agent-manager-bookmarks-drawer\"\n >\n <header className=\"flex items-center justify-between px-4 py-3 border-b border-border-default flex-shrink-0\">\n <div className=\"flex items-center gap-2\">\n <Bookmark className=\"size-4 text-text-secondary\" />\n <h3 className=\"text-sm font-semibold text-text-primary\">\n {tr('vibecontrols.agentManager.bookmarksDrawer.title', 'Bookmarks in this session')}\n </h3>\n </div>\n <button\n type=\"button\"\n onClick={onClose}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors\"\n aria-label={tr('vibecontrols.common.close', 'Close')}\n >\n <X className=\"size-4 text-text-secondary\" />\n </button>\n </header>\n\n <div className=\"flex-1 min-h-0 overflow-y-auto\">\n {loading && bookmarks.length === 0 && (\n <div className=\"px-4 py-6 text-xs text-text-tertiary\">\n {tr('vibecontrols.common.loading', 'Loading...')}\n </div>\n )}\n\n {!loading && bookmarks.length === 0 && (\n <div className=\"px-4 py-10 flex flex-col items-center text-center gap-2\">\n <Bookmark className=\"size-8 text-text-muted\" />\n <p className=\"text-sm font-medium text-text-primary\">\n {tr(\n 'vibecontrols.agentManager.bookmarksDrawer.empty',\n 'No bookmarks in this session yet'\n )}\n </p>\n <p className=\"text-xs text-text-secondary max-w-xs\">\n {tr(\n 'vibecontrols.agentManager.bookmarksDrawer.emptyHint',\n 'Click the bookmark icon on any message to save it for later.'\n )}\n </p>\n </div>\n )}\n\n <ul className=\"divide-y divide-border-subtle\">\n {bookmarks.map((bm: AgentMessageBookmarkFieldsFragment) => {\n const msg = messageById.get(bm.messageId);\n const isStale = !msg;\n const isEditing = editingId === bm.id;\n\n return (\n <li key={bm.id} className=\"px-4 py-3 hover:bg-bg-elevated/40 transition-colors\">\n <div className=\"flex items-start gap-2\">\n <div className=\"flex-1 min-w-0\">\n {isEditing ? (\n <input\n aria-label=\"control\"\n value={labelDraft}\n onChange={(e) => setLabelDraft(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Enter') void saveEdit(bm.id);\n if (e.key === 'Escape') {\n setEditingId(null);\n setLabelDraft('');\n }\n }}\n placeholder={tr(\n 'vibecontrols.agentManager.bookmarksDrawer.labelPlaceholder',\n 'Add a label (optional)'\n )}\n className=\"w-full bg-bg-sunken border border-border-subtle rounded px-2 py-1 text-xs text-text-primary placeholder:text-text-tertiary focus:outline-none focus:border-action-primary-bg\"\n />\n ) : (\n <p className=\"text-sm font-medium text-text-primary truncate\">\n {bm.label ||\n (msg?.role === 'user'\n ? tr('vibecontrols.agentManager.bookmarksDrawer.fromUser', 'From you')\n : tr(\n 'vibecontrols.agentManager.bookmarksDrawer.fromAssistant',\n 'Assistant reply'\n ))}\n </p>\n )}\n\n {isStale ? (\n <p className=\"text-xs text-text-tertiary italic mt-1\">\n {tr(\n 'vibecontrols.agentManager.bookmarksDrawer.stale',\n 'Message no longer available'\n )}\n </p>\n ) : (\n <p className=\"text-xs text-text-secondary mt-1 line-clamp-2\">\n {snippet(msg.content)}\n </p>\n )}\n\n <p className=\"text-[10px] text-text-tertiary mt-1\">\n {relativeTime(bm.createdAt)}\n </p>\n </div>\n\n <div className=\"flex items-center gap-0.5 shrink-0\">\n {!isStale && !isEditing && (\n <button\n type=\"button\"\n onClick={() => {\n onJump(bm.messageId);\n onClose();\n }}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors\"\n title={tr(\n 'vibecontrols.agentManager.bookmarksDrawer.jump',\n 'Jump to message'\n )}\n aria-label={tr(\n 'vibecontrols.agentManager.bookmarksDrawer.jump',\n 'Jump to message'\n )}\n >\n <ChevronRight className=\"size-3.5 text-text-secondary\" />\n </button>\n )}\n\n {isEditing ? (\n <button\n type=\"button\"\n onClick={() => void saveEdit(bm.id)}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors\"\n aria-label={tr('vibecontrols.common.save', 'Save')}\n >\n <Check className=\"size-3.5 text-status-success-text\" />\n </button>\n ) : canUpdateBookmark ? (\n <button\n type=\"button\"\n onClick={() => startEdit(bm.id, bm.label)}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors\"\n aria-label={tr(\n 'vibecontrols.agentManager.bookmarksDrawer.editLabel',\n 'Edit label'\n )}\n >\n <Pencil className=\"size-3.5 text-text-secondary\" />\n </button>\n ) : null}\n\n {canDeleteBookmark && (\n <button\n type=\"button\"\n onClick={() => void removeBookmark(bm.id)}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors\"\n aria-label={tr(\n 'vibecontrols.agentManager.messageItem.removeBookmark',\n 'Remove bookmark'\n )}\n >\n <Trash2 className=\"size-3.5 text-status-error-text\" />\n </button>\n )}\n </div>\n </div>\n </li>\n );\n })}\n </ul>\n </div>\n\n <footer className=\"px-4 py-3 border-t border-border-default flex-shrink-0\">\n <button\n type=\"button\"\n onClick={() => {\n onClose();\n navigate('/vibecontrols/ai/bookmarks');\n }}\n className=\"inline-flex items-center gap-1.5 text-xs font-medium text-action-primary-text hover:underline\"\n >\n <ExternalLink className=\"size-3\" />\n {tr(\n 'vibecontrols.agentManager.bookmarksDrawer.seeAll',\n 'See all bookmarks across sessions'\n )}\n </button>\n </footer>\n </aside>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AgentManagerBookmarksDrawer.js","names":[],"sources":["../../../src/components/agent-manager/AgentManagerBookmarksDrawer.tsx"],"sourcesContent":["/**\n * Side drawer listing the current user's bookmarks for ONE chat session.\n *\n * Each row resolves its message body from the currently-loaded session\n * messages. When the bookmarked message is no longer in the session\n * (agent log pruning / rotation), the row degrades to a \"Message no\n * longer available\" affordance with a remove-only action.\n */\n\nimport { useState, type FC } from 'react';\nimport { useNavigate } from 'react-router';\nimport { Bookmark, X, ChevronRight, Pencil, Trash2, Check, ExternalLink } from 'lucide-react';\nimport { relativeTime } from '@/utils/relativeTime';\nimport {\n useDeleteAgentMessageBookmarkMutation,\n useMyAgentMessageBookmarksQuery,\n useUpdateAgentMessageBookmarkMutation,\n type AgentMessageBookmarkFieldsFragment,\n} from '../../generated/wspace-operations';\nimport type { AgentManagerMessage } from '@/types/agentManager';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { VIBE_PERMISSIONS } from '@/constants/permissions';\n\ninterface AgentManagerBookmarksDrawerProps {\n open: boolean;\n onClose: () => void;\n sessionId: string;\n messages: AgentManagerMessage[];\n onJump: (messageId: string) => void;\n}\n\nconst PREVIEW_LEN = 80;\n\nfunction snippet(content: string): string {\n if (!content) return '';\n const trimmed = content.trim().replace(/\\s+/g, ' ');\n return trimmed.length > PREVIEW_LEN ? `${trimmed.slice(0, PREVIEW_LEN)}…` : trimmed;\n}\n\nexport const AgentManagerBookmarksDrawer: FC<AgentManagerBookmarksDrawerProps> = ({\n open,\n onClose,\n sessionId,\n messages,\n onJump,\n}) => {\n const navigate = useNavigate();\n const tr = useTr();\n const { hasPermission } = usePermissions();\n const canUpdateBookmark = hasPermission(VIBE_PERMISSIONS.AGENT_MESSAGE_BOOKMARK_UPDATE);\n const canDeleteBookmark = hasPermission(VIBE_PERMISSIONS.AGENT_MESSAGE_BOOKMARK_DELETE);\n const [editingId, setEditingId] = useState<string | null>(null);\n const [labelDraft, setLabelDraft] = useState('');\n\n const { data, loading } = useMyAgentMessageBookmarksQuery({\n variables: { filter: { sessionId } },\n skip: !open,\n fetchPolicy: 'cache-and-network',\n });\n\n const [updateMutation] = useUpdateAgentMessageBookmarkMutation({\n refetchQueries: ['MyAgentMessageBookmarks'],\n });\n const [deleteMutation] = useDeleteAgentMessageBookmarkMutation({\n refetchQueries: ['MyAgentMessageBookmarks'],\n });\n\n const bookmarks = data?.myAgentMessageBookmarks.items ?? [];\n const messageById = new Map(messages.map((m) => [m.id, m]));\n\n const startEdit = (id: string, current: string | null | undefined) => {\n setEditingId(id);\n setLabelDraft(current ?? '');\n };\n\n const saveEdit = async (id: string) => {\n await updateMutation({ variables: { id, input: { label: labelDraft || null } } });\n setEditingId(null);\n setLabelDraft('');\n };\n\n const removeBookmark = async (id: string) => {\n await deleteMutation({ variables: { id } });\n };\n\n if (!open) return null;\n\n return (\n <div className=\"fixed inset-0 z-40 flex\">\n <div className=\"flex-1 bg-bg-overlay/40\" onClick={onClose} aria-hidden />\n <aside\n className=\"w-full max-w-md h-full bg-bg-surface border-l border-border-default shadow-xl flex flex-col\"\n role=\"dialog\"\n aria-label={tr(\n 'vibecontrols.agentManager.bookmarksDrawer.title',\n 'Bookmarks in this session'\n )}\n data-testid=\"agent-manager-bookmarks-drawer\"\n >\n <header className=\"flex items-center justify-between px-4 py-3 border-b border-border-default flex-shrink-0\">\n <div className=\"flex items-center gap-2\">\n <Bookmark className=\"size-4 text-text-secondary\" />\n <h3 className=\"text-sm font-semibold text-text-primary\">\n {tr('vibecontrols.agentManager.bookmarksDrawer.title', 'Bookmarks in this session')}\n </h3>\n </div>\n <button\n type=\"button\"\n onClick={onClose}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors\"\n aria-label={tr('vibecontrols.common.close', 'Close')}\n >\n <X className=\"size-4 text-text-secondary\" />\n </button>\n </header>\n\n <div className=\"flex-1 min-h-0 overflow-y-auto\">\n {loading && bookmarks.length === 0 && (\n <div className=\"px-4 py-6 text-xs text-text-tertiary\">\n {tr('vibecontrols.common.loading', 'Loading...')}\n </div>\n )}\n\n {!loading && bookmarks.length === 0 && (\n <div className=\"px-4 py-10 flex flex-col items-center text-center gap-2\">\n <Bookmark className=\"size-8 text-text-muted\" />\n <p className=\"text-sm font-medium text-text-primary\">\n {tr(\n 'vibecontrols.agentManager.bookmarksDrawer.empty',\n 'No bookmarks in this session yet'\n )}\n </p>\n <p className=\"text-xs text-text-secondary max-w-xs\">\n {tr(\n 'vibecontrols.agentManager.bookmarksDrawer.emptyHint',\n 'Click the bookmark icon on any message to save it for later.'\n )}\n </p>\n </div>\n )}\n\n <ul className=\"divide-y divide-border-subtle\">\n {bookmarks.map((bm: AgentMessageBookmarkFieldsFragment) => {\n const msg = messageById.get(bm.messageId);\n const isStale = !msg;\n const isEditing = editingId === bm.id;\n\n return (\n <li key={bm.id} className=\"px-4 py-3 hover:bg-bg-elevated/40 transition-colors\">\n <div className=\"flex items-start gap-2\">\n <div className=\"flex-1 min-w-0\">\n {isEditing ? (\n <input\n aria-label=\"control\"\n value={labelDraft}\n onChange={(e) => setLabelDraft(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Enter') void saveEdit(bm.id);\n if (e.key === 'Escape') {\n setEditingId(null);\n setLabelDraft('');\n }\n }}\n placeholder={tr(\n 'vibecontrols.agentManager.bookmarksDrawer.labelPlaceholder',\n 'Add a label (optional)'\n )}\n className=\"w-full bg-bg-sunken border border-border-subtle rounded px-2 py-1 text-xs text-text-primary placeholder:text-text-tertiary focus:outline-none focus:border-action-primary-bg\"\n />\n ) : (\n <p className=\"text-sm font-medium text-text-primary truncate\">\n {bm.label ||\n (msg?.role === 'user'\n ? tr('vibecontrols.agentManager.bookmarksDrawer.fromUser', 'From you')\n : tr(\n 'vibecontrols.agentManager.bookmarksDrawer.fromAssistant',\n 'Assistant reply'\n ))}\n </p>\n )}\n\n {isStale ? (\n <p className=\"text-xs text-text-tertiary italic mt-1\">\n {tr(\n 'vibecontrols.agentManager.bookmarksDrawer.stale',\n 'Message no longer available'\n )}\n </p>\n ) : (\n <p className=\"text-xs text-text-secondary mt-1 line-clamp-2\">\n {snippet(msg.content)}\n </p>\n )}\n\n <p className=\"text-[10px] text-text-tertiary mt-1\">\n {relativeTime(bm.createdAt)}\n </p>\n </div>\n\n <div className=\"flex items-center gap-0.5 shrink-0\">\n {!isStale && !isEditing && (\n <button\n type=\"button\"\n onClick={() => {\n onJump(bm.messageId);\n onClose();\n }}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors\"\n title={tr(\n 'vibecontrols.agentManager.bookmarksDrawer.jump',\n 'Jump to message'\n )}\n aria-label={tr(\n 'vibecontrols.agentManager.bookmarksDrawer.jump',\n 'Jump to message'\n )}\n >\n <ChevronRight className=\"size-3.5 text-text-secondary\" />\n </button>\n )}\n\n {isEditing ? (\n <button\n type=\"button\"\n onClick={() => void saveEdit(bm.id)}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors\"\n aria-label={tr('vibecontrols.common.save', 'Save')}\n >\n <Check className=\"size-3.5 text-status-success-text\" />\n </button>\n ) : canUpdateBookmark ? (\n <button\n type=\"button\"\n onClick={() => startEdit(bm.id, bm.label)}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors\"\n aria-label={tr(\n 'vibecontrols.agentManager.bookmarksDrawer.editLabel',\n 'Edit label'\n )}\n >\n <Pencil className=\"size-3.5 text-text-secondary\" />\n </button>\n ) : null}\n\n {canDeleteBookmark && (\n <button\n type=\"button\"\n onClick={() => void removeBookmark(bm.id)}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors\"\n aria-label={tr(\n 'vibecontrols.agentManager.messageItem.removeBookmark',\n 'Remove bookmark'\n )}\n >\n <Trash2 className=\"size-3.5 text-status-error-text\" />\n </button>\n )}\n </div>\n </div>\n </li>\n );\n })}\n </ul>\n </div>\n\n <footer className=\"px-4 py-3 border-t border-border-default flex-shrink-0\">\n <button\n type=\"button\"\n onClick={() => {\n onClose();\n navigate('/vibecontrols/ai/bookmarks');\n }}\n className=\"inline-flex items-center gap-1.5 text-xs font-medium text-action-primary-text hover:underline\"\n >\n <ExternalLink className=\"size-3\" />\n {tr(\n 'vibecontrols.agentManager.bookmarksDrawer.seeAll',\n 'See all bookmarks across sessions'\n )}\n </button>\n </footer>\n </aside>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -12,7 +12,7 @@ import { PluginInstallModal as u } from "./PluginInstallModal.js";
|
|
|
12
12
|
import { AIProviderCredentialsCard as d } from "../ai/AIProviderCredentialsCard.js";
|
|
13
13
|
import { useCallback as f, useEffect as p, useMemo as m, useRef as h, useState as g } from "react";
|
|
14
14
|
import { Bookmark as _, PackagePlus as v, Search as ee, X as y } from "lucide-react";
|
|
15
|
-
import { useSearchParams as b } from "react-router
|
|
15
|
+
import { useSearchParams as b } from "react-router";
|
|
16
16
|
import { jsx as x, jsxs as S } from "react/jsx-runtime";
|
|
17
17
|
//#region src/components/agent-manager/AgentManagerChatPane.tsx
|
|
18
18
|
var C = ({ sessionId: d, agentId: C }) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AgentManagerChatPane.js","names":[],"sources":["../../../src/components/agent-manager/AgentManagerChatPane.tsx"],"sourcesContent":["/**\n * Main chat interface for a single Agent Manager session.\n *\n * Renders message list + input area, manages the send/receive flow,\n * fetches existing conversation logs on mount, and provides\n * auto-scroll, cancel, and in-session search support.\n */\n\nimport { useEffect, useRef, useState, useCallback, useMemo, type FC } from 'react';\nimport { useSearchParams } from 'react-router-dom';\nimport { Bookmark, PackagePlus, Search, X } from 'lucide-react';\nimport { useAgentManagerStore } from '@/store/agentManagerStore';\nimport { useAgentManagerStreaming } from '@/hooks/useAgentManagerStreaming';\nimport { useCachedInitialData } from '@/hooks/useCachedInitialData';\nimport { fetchSessionLogs } from '@/services/aiApi';\nimport { useActiveAgentProfile } from '@/hooks/useActiveAgentProfile';\nimport { useToggleAgentBookmark } from '@/hooks/useToggleAgentBookmark';\nimport { AgentManagerMessageList } from './AgentManagerMessageList';\nimport { AgentManagerInputArea } from './AgentManagerInputArea';\nimport { AgentManagerBookmarksDrawer } from './AgentManagerBookmarksDrawer';\nimport { PluginInstallModal } from './PluginInstallModal';\nimport { AIProviderCredentialsCard } from '@/components/ai/AIProviderCredentialsCard';\nimport type { AgentManagerMessage } from '@/types/agentManager';\nimport { useTr } from '../../shared/hooks/useTr';\n\ninterface AgentManagerChatPaneProps {\n sessionId: string;\n agentId: string;\n}\n\nexport const AgentManagerChatPane: FC<AgentManagerChatPaneProps> = ({ sessionId, agentId }) => {\n const [loadingLogs, setLoadingLogs] = useState(false);\n const [searchOpen, setSearchOpen] = useState(false);\n const [searchQuery, setSearchQuery] = useState('');\n // Provider-install modal triggered from the header. Lets users add a\n // claude/codex/gemini/opencode plugin without leaving the conversation.\n const [installModalOpen, setInstallModalOpen] = useState(false);\n const [bookmarksDrawerOpen, setBookmarksDrawerOpen] = useState(false);\n const [flashMessageId, setFlashMessageId] = useState<string | null>(null);\n const [searchParams, setSearchParams] = useSearchParams();\n const pendingScrollMessageId = searchParams.get('messageId');\n const consumedScrollTargetRef = useRef<string | null>(null);\n const tr = useTr();\n\n const { agents } = useCachedInitialData();\n const activeAgent = useMemo(\n () => agents.find((a) => a.id === agentId) ?? null,\n [agents, agentId]\n );\n\n const sessionState = useAgentManagerStore((s) => s.sessionStates[sessionId]);\n const addMessage = useAgentManagerStore((s) => s.addMessage);\n\n const messages = useMemo(() => sessionState?.messages ?? [], [sessionState?.messages]);\n\n const { active: activeProfile } = useActiveAgentProfile(activeAgent?.id ?? null);\n const agentRef = useMemo(\n () => ({ id: agentId, profile: activeProfile }),\n [agentId, activeProfile]\n );\n\n const { sendMessage, cancelStream, isStreaming } = useAgentManagerStreaming({\n agent: agentRef,\n sessionId,\n });\n\n const { bookmarkedIds, toggle: toggleBookmark } = useToggleAgentBookmark(sessionId);\n\n const jumpToMessage = useCallback((messageId: string) => {\n const el = document.querySelector(`[data-message-id=\"${messageId}\"]`);\n if (!el) return;\n el.scrollIntoView({ behavior: 'smooth', block: 'center' });\n setFlashMessageId(messageId);\n window.setTimeout(() => {\n setFlashMessageId((current) => (current === messageId ? null : current));\n }, 1500);\n }, []);\n\n const handleToggleBookmark = useCallback(\n (messageId: string) => {\n void toggleBookmark({ agentId, sessionId, messageId });\n },\n [toggleBookmark, agentId, sessionId]\n );\n\n const toggleSearch = useCallback(() => {\n setSearchOpen((prev) => {\n if (prev) setSearchQuery('');\n return !prev;\n });\n }, []);\n\n // Fetch existing conversation logs on mount\n useEffect(() => {\n let cancelled = false;\n\n const loadExistingLogs = async () => {\n // Only load if session has no messages yet\n if (messages.length > 0) return;\n\n setLoadingLogs(true);\n try {\n const logs = await fetchSessionLogs(agentRef, sessionId, {\n types: ['input', 'output'],\n });\n\n if (cancelled || logs.length === 0) return;\n\n const seenLogKeys = new Set<string>();\n\n // Convert session logs to AgentManagerMessage format. The core AI\n // route owns logging, but older provider builds also wrote the same\n // input/output records; collapse same-second duplicates on restore.\n for (const log of logs) {\n const content = log.message || log.content || '';\n const timestamp = log.timestamp || log.createdAt;\n const timestampSecond = timestamp ? Math.floor(new Date(timestamp).getTime() / 1000) : 0;\n const logKey = [log.type, content, log.model ?? '', timestampSecond].join('\\u0000');\n if (seenLogKeys.has(logKey)) continue;\n seenLogKeys.add(logKey);\n\n const msg: AgentManagerMessage = {\n id: log.id,\n role: log.type === 'input' ? 'user' : 'assistant',\n content,\n timestamp,\n tokens: log.tokenCount ? { input: 0, output: log.tokenCount } : undefined,\n model: log.model,\n };\n addMessage(sessionId, msg);\n }\n } catch {\n // Silent fail -- session may not have logs yet\n } finally {\n if (!cancelled) setLoadingLogs(false);\n }\n };\n\n void loadExistingLogs();\n\n return () => {\n cancelled = true;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- only run on mount\n }, [agentId, sessionId]);\n\n // Auto-scroll to a message linked via ?messageId=... (from drawer / cross-session page).\n // Waits for logs to finish loading and the target to be in the DOM before scrolling.\n useEffect(() => {\n if (!pendingScrollMessageId || loadingLogs) return;\n if (consumedScrollTargetRef.current === pendingScrollMessageId) return;\n\n const exists = messages.some((m) => m.id === pendingScrollMessageId);\n if (!exists) return;\n\n consumedScrollTargetRef.current = pendingScrollMessageId;\n jumpToMessage(pendingScrollMessageId);\n\n // Strip the query param so a manual refresh doesn't re-trigger the flash.\n const next = new URLSearchParams(searchParams);\n next.delete('messageId');\n setSearchParams(next, { replace: true });\n }, [pendingScrollMessageId, loadingLogs, messages, jumpToMessage, searchParams, setSearchParams]);\n\n return (\n <div className=\"flex flex-col h-full min-h-0\" data-testid=\"agent-manager-chat-pane\">\n {/* In-session search bar.\n Layout: [search controls (flex-1)] ... [Manage AI providers].\n The search input expands inside the LEFT cluster only, so the\n Manage button stays anchored on the right and never shifts. */}\n <div className=\"flex items-center gap-1 px-3 py-1 border-b border-border-default bg-bg-surface flex-shrink-0\">\n <div className=\"flex items-center gap-1.5 min-w-0 flex-1\">\n <button\n type=\"button\"\n onClick={toggleSearch}\n className={`p-1 rounded transition-colors shrink-0 ${\n searchOpen\n ? 'bg-[var(--color-accent-soft)] text-text-primary'\n : 'text-text-tertiary hover:text-text-secondary hover:bg-bg-elevated'\n }`}\n title={tr(\n 'vibecontrols.agentManager.chatPane.searchInSession',\n 'Search in session (Ctrl+F)'\n )}\n aria-label={tr(\n 'vibecontrols.agentManager.chatPane.searchInSession',\n 'Search in session'\n )}\n >\n <Search className=\"size-3.5\" />\n </button>\n {searchOpen && (\n <div className=\"flex items-center gap-1.5 flex-1 min-w-0\">\n <input\n aria-label=\"control\"\n type=\"text\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n placeholder={tr(\n 'vibecontrols.agentManager.chatPane.searchPlaceholder',\n 'Search in conversation...'\n )}\n className=\"flex-1 min-w-0 bg-transparent text-xs text-text-primary placeholder:text-text-tertiary focus:outline-none\"\n />\n {searchQuery && (\n <button\n type=\"button\"\n onClick={() => setSearchQuery('')}\n className=\"p-0.5 rounded hover:bg-bg-elevated transition-colors\"\n >\n <X className=\"size-3 text-text-tertiary\" />\n </button>\n )}\n </div>\n )}\n </div>\n <button\n type=\"button\"\n onClick={() => setBookmarksDrawerOpen(true)}\n className=\"shrink-0 p-1 rounded transition-colors text-text-tertiary hover:text-text-secondary hover:bg-bg-elevated\"\n title={tr(\n 'vibecontrols.agentManager.chatPane.bookmarksTooltip',\n 'View bookmarked messages'\n )}\n aria-label={tr('vibecontrols.agentManager.chatPane.bookmarksLabel', 'Bookmarks')}\n data-testid=\"agent-manager-bookmarks-trigger\"\n >\n <Bookmark className=\"size-3.5\" />\n </button>\n <button\n type=\"button\"\n onClick={() => setInstallModalOpen(true)}\n className=\"shrink-0 inline-flex items-center gap-1.5 rounded-md border border-border-default bg-bg-elevated px-2 py-1 text-xs font-medium text-text-primary transition-colors hover:bg-bg-sunken hover:border-[var(--color-accent-soft)]\"\n title={tr(\n 'vibecontrols.agentManager.chatPane.manageProvidersTooltip',\n 'Install or remove AI providers (Claude, Codex, Gemini, OpenCode)'\n )}\n aria-label={tr(\n 'vibecontrols.agentManager.chatPane.manageProviders',\n 'Manage AI providers'\n )}\n data-testid=\"agent-manager-manage-providers\"\n >\n <PackagePlus className=\"size-3.5\" />\n <span className=\"hidden sm:inline\">\n {tr('vibecontrols.agentManager.chatPane.manageProviders', 'Manage AI providers')}\n </span>\n </button>\n </div>\n\n {/* Message list */}\n {loadingLogs ? (\n <div className=\"flex-1 flex items-center justify-center\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'vibecontrols.agentManager.chatPane.loadingConversation',\n 'Loading conversation...'\n )}\n </p>\n </div>\n ) : (\n <AgentManagerMessageList\n messages={messages}\n isStreaming={isStreaming}\n onCancelStream={cancelStream}\n searchQuery={searchQuery}\n bookmarkedIds={bookmarkedIds}\n onToggleBookmark={handleToggleBookmark}\n flashMessageId={flashMessageId}\n />\n )}\n\n {/* Rich input area with controls */}\n <AgentManagerInputArea\n agentId={agentId}\n sessionId={sessionId}\n onSend={(text) => void sendMessage(text)}\n onCancel={cancelStream}\n agentTunnelUrl={activeAgent?.tunnelUrl ?? undefined}\n />\n\n {/* Per-session bookmarks drawer */}\n <AgentManagerBookmarksDrawer\n open={bookmarksDrawerOpen}\n onClose={() => setBookmarksDrawerOpen(false)}\n sessionId={sessionId}\n messages={messages}\n onJump={jumpToMessage}\n />\n\n {/* Header-triggered provider install/remove. Sits below the chat\n tree so it can render full-screen without disturbing layout. */}\n <PluginInstallModal\n open={installModalOpen}\n agentId={agentId}\n onClose={() => setInstallModalOpen(false)}\n />\n\n {/* SDK auth prompt: opens when the streaming hook detects a missing\n ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN, so the user can save the\n credential inline instead of being told to go look elsewhere.\n The session's currently-selected SDK is forwarded so the picker\n can preselect the right provider. */}\n <SdkAuthPromptModal\n agentId={agentId}\n agentRef={agentRef}\n initialProviderId={sessionState?.sdk ?? undefined}\n />\n </div>\n );\n};\n\ninterface SdkAuthPromptModalProps {\n agentId: string;\n agentRef: { id: string; profile: string };\n initialProviderId?: string;\n}\n\nconst SdkAuthPromptModal: FC<SdkAuthPromptModalProps> = ({\n agentId,\n agentRef,\n initialProviderId,\n}) => {\n const sdkAuthPrompt = useAgentManagerStore((s) => s.sdkAuthPrompt);\n const setSdkAuthPrompt = useAgentManagerStore((s) => s.setSdkAuthPrompt);\n // Track whether the inner card has at least one primary credential\n // saved on the agent. The Continue button is gated on this so the user\n // can't dismiss the modal without actually persisting a key — but the\n // moment they save *any* of the either-or options it lights up.\n const [authSatisfied, setAuthSatisfied] = useState(false);\n const tr = useTr();\n\n // Reset the satisfied flag whenever the modal re-opens for a fresh\n // prompt (different agent / different session). Without this, dismissing\n // and re-opening would leave the flag stuck at its previous value.\n useEffect(() => {\n if (sdkAuthPrompt && sdkAuthPrompt.agentId === agentId) {\n setAuthSatisfied(false);\n }\n }, [sdkAuthPrompt, agentId]);\n\n if (!sdkAuthPrompt || sdkAuthPrompt.agentId !== agentId) return null;\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center p-4\">\n <div\n className=\"absolute inset-0 bg-bg-overlay\"\n onClick={() => setSdkAuthPrompt(null)}\n aria-hidden\n />\n {/* Constrain height to the viewport and let the inner body scroll so the\n modal never escapes the screen no matter how many providers/fields\n render inside AIProviderCredentialsCard. The header + footer stay\n sticky so the close + Continue buttons are always reachable. */}\n <div className=\"relative w-full max-w-xl max-h-[85vh] flex flex-col rounded-lg border border-border-default bg-bg-surface shadow-xl overflow-hidden\">\n <div className=\"flex items-center justify-between px-4 py-3 border-b border-border-default bg-bg-surface flex-shrink-0\">\n <h3 className=\"text-base font-semibold text-text-primary truncate pr-2\">\n {tr('vibecontrols.ai.sdkAuth.title', 'Configure AI provider credentials')}\n </h3>\n <button\n type=\"button\"\n onClick={() => setSdkAuthPrompt(null)}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors flex-shrink-0\"\n aria-label={tr('vibecontrols.common.close', 'Close')}\n >\n <X className=\"size-5 text-text-secondary\" />\n </button>\n </div>\n <div className=\"flex-1 min-h-0 overflow-y-auto px-4 py-3 space-y-3\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'vibecontrols.ai.sdkAuth.body',\n \"The agent doesn't have a credential configured for this provider. Save any one of the listed keys (you don't need all of them) and click Continue to retry.\"\n )}\n </p>\n <AIProviderCredentialsCard\n agent={agentRef}\n initialProviderId={initialProviderId}\n onProviderAuthSatisfiedChange={setAuthSatisfied}\n />\n </div>\n <div className=\"flex items-center justify-end gap-2 px-4 py-3 border-t border-border-default bg-bg-surface flex-shrink-0\">\n <button\n type=\"button\"\n onClick={() => setSdkAuthPrompt(null)}\n className=\"px-3 py-1.5 rounded-md text-sm text-text-secondary hover:text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n {tr('vibecontrols.common.close', 'Close')}\n </button>\n <button\n type=\"button\"\n onClick={() => setSdkAuthPrompt(null)}\n disabled={!authSatisfied}\n title={\n authSatisfied\n ? undefined\n : tr(\n 'vibecontrols.ai.sdkAuth.saveOneFirst',\n 'Save at least one of the listed credentials to continue'\n )\n }\n className=\"px-3 py-1.5 rounded-md text-sm font-medium bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg-hover disabled:opacity-50 disabled:cursor-not-allowed transition-colors\"\n >\n {tr('vibecontrols.ai.sdkAuth.continue', 'Continue')}\n </button>\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AgentManagerChatPane.js","names":[],"sources":["../../../src/components/agent-manager/AgentManagerChatPane.tsx"],"sourcesContent":["/**\n * Main chat interface for a single Agent Manager session.\n *\n * Renders message list + input area, manages the send/receive flow,\n * fetches existing conversation logs on mount, and provides\n * auto-scroll, cancel, and in-session search support.\n */\n\nimport { useEffect, useRef, useState, useCallback, useMemo, type FC } from 'react';\nimport { useSearchParams } from 'react-router';\nimport { Bookmark, PackagePlus, Search, X } from 'lucide-react';\nimport { useAgentManagerStore } from '@/store/agentManagerStore';\nimport { useAgentManagerStreaming } from '@/hooks/useAgentManagerStreaming';\nimport { useCachedInitialData } from '@/hooks/useCachedInitialData';\nimport { fetchSessionLogs } from '@/services/aiApi';\nimport { useActiveAgentProfile } from '@/hooks/useActiveAgentProfile';\nimport { useToggleAgentBookmark } from '@/hooks/useToggleAgentBookmark';\nimport { AgentManagerMessageList } from './AgentManagerMessageList';\nimport { AgentManagerInputArea } from './AgentManagerInputArea';\nimport { AgentManagerBookmarksDrawer } from './AgentManagerBookmarksDrawer';\nimport { PluginInstallModal } from './PluginInstallModal';\nimport { AIProviderCredentialsCard } from '@/components/ai/AIProviderCredentialsCard';\nimport type { AgentManagerMessage } from '@/types/agentManager';\nimport { useTr } from '../../shared/hooks/useTr';\n\ninterface AgentManagerChatPaneProps {\n sessionId: string;\n agentId: string;\n}\n\nexport const AgentManagerChatPane: FC<AgentManagerChatPaneProps> = ({ sessionId, agentId }) => {\n const [loadingLogs, setLoadingLogs] = useState(false);\n const [searchOpen, setSearchOpen] = useState(false);\n const [searchQuery, setSearchQuery] = useState('');\n // Provider-install modal triggered from the header. Lets users add a\n // claude/codex/gemini/opencode plugin without leaving the conversation.\n const [installModalOpen, setInstallModalOpen] = useState(false);\n const [bookmarksDrawerOpen, setBookmarksDrawerOpen] = useState(false);\n const [flashMessageId, setFlashMessageId] = useState<string | null>(null);\n const [searchParams, setSearchParams] = useSearchParams();\n const pendingScrollMessageId = searchParams.get('messageId');\n const consumedScrollTargetRef = useRef<string | null>(null);\n const tr = useTr();\n\n const { agents } = useCachedInitialData();\n const activeAgent = useMemo(\n () => agents.find((a) => a.id === agentId) ?? null,\n [agents, agentId]\n );\n\n const sessionState = useAgentManagerStore((s) => s.sessionStates[sessionId]);\n const addMessage = useAgentManagerStore((s) => s.addMessage);\n\n const messages = useMemo(() => sessionState?.messages ?? [], [sessionState?.messages]);\n\n const { active: activeProfile } = useActiveAgentProfile(activeAgent?.id ?? null);\n const agentRef = useMemo(\n () => ({ id: agentId, profile: activeProfile }),\n [agentId, activeProfile]\n );\n\n const { sendMessage, cancelStream, isStreaming } = useAgentManagerStreaming({\n agent: agentRef,\n sessionId,\n });\n\n const { bookmarkedIds, toggle: toggleBookmark } = useToggleAgentBookmark(sessionId);\n\n const jumpToMessage = useCallback((messageId: string) => {\n const el = document.querySelector(`[data-message-id=\"${messageId}\"]`);\n if (!el) return;\n el.scrollIntoView({ behavior: 'smooth', block: 'center' });\n setFlashMessageId(messageId);\n window.setTimeout(() => {\n setFlashMessageId((current) => (current === messageId ? null : current));\n }, 1500);\n }, []);\n\n const handleToggleBookmark = useCallback(\n (messageId: string) => {\n void toggleBookmark({ agentId, sessionId, messageId });\n },\n [toggleBookmark, agentId, sessionId]\n );\n\n const toggleSearch = useCallback(() => {\n setSearchOpen((prev) => {\n if (prev) setSearchQuery('');\n return !prev;\n });\n }, []);\n\n // Fetch existing conversation logs on mount\n useEffect(() => {\n let cancelled = false;\n\n const loadExistingLogs = async () => {\n // Only load if session has no messages yet\n if (messages.length > 0) return;\n\n setLoadingLogs(true);\n try {\n const logs = await fetchSessionLogs(agentRef, sessionId, {\n types: ['input', 'output'],\n });\n\n if (cancelled || logs.length === 0) return;\n\n const seenLogKeys = new Set<string>();\n\n // Convert session logs to AgentManagerMessage format. The core AI\n // route owns logging, but older provider builds also wrote the same\n // input/output records; collapse same-second duplicates on restore.\n for (const log of logs) {\n const content = log.message || log.content || '';\n const timestamp = log.timestamp || log.createdAt;\n const timestampSecond = timestamp ? Math.floor(new Date(timestamp).getTime() / 1000) : 0;\n const logKey = [log.type, content, log.model ?? '', timestampSecond].join('\\u0000');\n if (seenLogKeys.has(logKey)) continue;\n seenLogKeys.add(logKey);\n\n const msg: AgentManagerMessage = {\n id: log.id,\n role: log.type === 'input' ? 'user' : 'assistant',\n content,\n timestamp,\n tokens: log.tokenCount ? { input: 0, output: log.tokenCount } : undefined,\n model: log.model,\n };\n addMessage(sessionId, msg);\n }\n } catch {\n // Silent fail -- session may not have logs yet\n } finally {\n if (!cancelled) setLoadingLogs(false);\n }\n };\n\n void loadExistingLogs();\n\n return () => {\n cancelled = true;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- only run on mount\n }, [agentId, sessionId]);\n\n // Auto-scroll to a message linked via ?messageId=... (from drawer / cross-session page).\n // Waits for logs to finish loading and the target to be in the DOM before scrolling.\n useEffect(() => {\n if (!pendingScrollMessageId || loadingLogs) return;\n if (consumedScrollTargetRef.current === pendingScrollMessageId) return;\n\n const exists = messages.some((m) => m.id === pendingScrollMessageId);\n if (!exists) return;\n\n consumedScrollTargetRef.current = pendingScrollMessageId;\n jumpToMessage(pendingScrollMessageId);\n\n // Strip the query param so a manual refresh doesn't re-trigger the flash.\n const next = new URLSearchParams(searchParams);\n next.delete('messageId');\n setSearchParams(next, { replace: true });\n }, [pendingScrollMessageId, loadingLogs, messages, jumpToMessage, searchParams, setSearchParams]);\n\n return (\n <div className=\"flex flex-col h-full min-h-0\" data-testid=\"agent-manager-chat-pane\">\n {/* In-session search bar.\n Layout: [search controls (flex-1)] ... [Manage AI providers].\n The search input expands inside the LEFT cluster only, so the\n Manage button stays anchored on the right and never shifts. */}\n <div className=\"flex items-center gap-1 px-3 py-1 border-b border-border-default bg-bg-surface flex-shrink-0\">\n <div className=\"flex items-center gap-1.5 min-w-0 flex-1\">\n <button\n type=\"button\"\n onClick={toggleSearch}\n className={`p-1 rounded transition-colors shrink-0 ${\n searchOpen\n ? 'bg-[var(--color-accent-soft)] text-text-primary'\n : 'text-text-tertiary hover:text-text-secondary hover:bg-bg-elevated'\n }`}\n title={tr(\n 'vibecontrols.agentManager.chatPane.searchInSession',\n 'Search in session (Ctrl+F)'\n )}\n aria-label={tr(\n 'vibecontrols.agentManager.chatPane.searchInSession',\n 'Search in session'\n )}\n >\n <Search className=\"size-3.5\" />\n </button>\n {searchOpen && (\n <div className=\"flex items-center gap-1.5 flex-1 min-w-0\">\n <input\n aria-label=\"control\"\n type=\"text\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n placeholder={tr(\n 'vibecontrols.agentManager.chatPane.searchPlaceholder',\n 'Search in conversation...'\n )}\n className=\"flex-1 min-w-0 bg-transparent text-xs text-text-primary placeholder:text-text-tertiary focus:outline-none\"\n />\n {searchQuery && (\n <button\n type=\"button\"\n onClick={() => setSearchQuery('')}\n className=\"p-0.5 rounded hover:bg-bg-elevated transition-colors\"\n >\n <X className=\"size-3 text-text-tertiary\" />\n </button>\n )}\n </div>\n )}\n </div>\n <button\n type=\"button\"\n onClick={() => setBookmarksDrawerOpen(true)}\n className=\"shrink-0 p-1 rounded transition-colors text-text-tertiary hover:text-text-secondary hover:bg-bg-elevated\"\n title={tr(\n 'vibecontrols.agentManager.chatPane.bookmarksTooltip',\n 'View bookmarked messages'\n )}\n aria-label={tr('vibecontrols.agentManager.chatPane.bookmarksLabel', 'Bookmarks')}\n data-testid=\"agent-manager-bookmarks-trigger\"\n >\n <Bookmark className=\"size-3.5\" />\n </button>\n <button\n type=\"button\"\n onClick={() => setInstallModalOpen(true)}\n className=\"shrink-0 inline-flex items-center gap-1.5 rounded-md border border-border-default bg-bg-elevated px-2 py-1 text-xs font-medium text-text-primary transition-colors hover:bg-bg-sunken hover:border-[var(--color-accent-soft)]\"\n title={tr(\n 'vibecontrols.agentManager.chatPane.manageProvidersTooltip',\n 'Install or remove AI providers (Claude, Codex, Gemini, OpenCode)'\n )}\n aria-label={tr(\n 'vibecontrols.agentManager.chatPane.manageProviders',\n 'Manage AI providers'\n )}\n data-testid=\"agent-manager-manage-providers\"\n >\n <PackagePlus className=\"size-3.5\" />\n <span className=\"hidden sm:inline\">\n {tr('vibecontrols.agentManager.chatPane.manageProviders', 'Manage AI providers')}\n </span>\n </button>\n </div>\n\n {/* Message list */}\n {loadingLogs ? (\n <div className=\"flex-1 flex items-center justify-center\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'vibecontrols.agentManager.chatPane.loadingConversation',\n 'Loading conversation...'\n )}\n </p>\n </div>\n ) : (\n <AgentManagerMessageList\n messages={messages}\n isStreaming={isStreaming}\n onCancelStream={cancelStream}\n searchQuery={searchQuery}\n bookmarkedIds={bookmarkedIds}\n onToggleBookmark={handleToggleBookmark}\n flashMessageId={flashMessageId}\n />\n )}\n\n {/* Rich input area with controls */}\n <AgentManagerInputArea\n agentId={agentId}\n sessionId={sessionId}\n onSend={(text) => void sendMessage(text)}\n onCancel={cancelStream}\n agentTunnelUrl={activeAgent?.tunnelUrl ?? undefined}\n />\n\n {/* Per-session bookmarks drawer */}\n <AgentManagerBookmarksDrawer\n open={bookmarksDrawerOpen}\n onClose={() => setBookmarksDrawerOpen(false)}\n sessionId={sessionId}\n messages={messages}\n onJump={jumpToMessage}\n />\n\n {/* Header-triggered provider install/remove. Sits below the chat\n tree so it can render full-screen without disturbing layout. */}\n <PluginInstallModal\n open={installModalOpen}\n agentId={agentId}\n onClose={() => setInstallModalOpen(false)}\n />\n\n {/* SDK auth prompt: opens when the streaming hook detects a missing\n ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN, so the user can save the\n credential inline instead of being told to go look elsewhere.\n The session's currently-selected SDK is forwarded so the picker\n can preselect the right provider. */}\n <SdkAuthPromptModal\n agentId={agentId}\n agentRef={agentRef}\n initialProviderId={sessionState?.sdk ?? undefined}\n />\n </div>\n );\n};\n\ninterface SdkAuthPromptModalProps {\n agentId: string;\n agentRef: { id: string; profile: string };\n initialProviderId?: string;\n}\n\nconst SdkAuthPromptModal: FC<SdkAuthPromptModalProps> = ({\n agentId,\n agentRef,\n initialProviderId,\n}) => {\n const sdkAuthPrompt = useAgentManagerStore((s) => s.sdkAuthPrompt);\n const setSdkAuthPrompt = useAgentManagerStore((s) => s.setSdkAuthPrompt);\n // Track whether the inner card has at least one primary credential\n // saved on the agent. The Continue button is gated on this so the user\n // can't dismiss the modal without actually persisting a key — but the\n // moment they save *any* of the either-or options it lights up.\n const [authSatisfied, setAuthSatisfied] = useState(false);\n const tr = useTr();\n\n // Reset the satisfied flag whenever the modal re-opens for a fresh\n // prompt (different agent / different session). Without this, dismissing\n // and re-opening would leave the flag stuck at its previous value.\n useEffect(() => {\n if (sdkAuthPrompt && sdkAuthPrompt.agentId === agentId) {\n setAuthSatisfied(false);\n }\n }, [sdkAuthPrompt, agentId]);\n\n if (!sdkAuthPrompt || sdkAuthPrompt.agentId !== agentId) return null;\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center p-4\">\n <div\n className=\"absolute inset-0 bg-bg-overlay\"\n onClick={() => setSdkAuthPrompt(null)}\n aria-hidden\n />\n {/* Constrain height to the viewport and let the inner body scroll so the\n modal never escapes the screen no matter how many providers/fields\n render inside AIProviderCredentialsCard. The header + footer stay\n sticky so the close + Continue buttons are always reachable. */}\n <div className=\"relative w-full max-w-xl max-h-[85vh] flex flex-col rounded-lg border border-border-default bg-bg-surface shadow-xl overflow-hidden\">\n <div className=\"flex items-center justify-between px-4 py-3 border-b border-border-default bg-bg-surface flex-shrink-0\">\n <h3 className=\"text-base font-semibold text-text-primary truncate pr-2\">\n {tr('vibecontrols.ai.sdkAuth.title', 'Configure AI provider credentials')}\n </h3>\n <button\n type=\"button\"\n onClick={() => setSdkAuthPrompt(null)}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors flex-shrink-0\"\n aria-label={tr('vibecontrols.common.close', 'Close')}\n >\n <X className=\"size-5 text-text-secondary\" />\n </button>\n </div>\n <div className=\"flex-1 min-h-0 overflow-y-auto px-4 py-3 space-y-3\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'vibecontrols.ai.sdkAuth.body',\n \"The agent doesn't have a credential configured for this provider. Save any one of the listed keys (you don't need all of them) and click Continue to retry.\"\n )}\n </p>\n <AIProviderCredentialsCard\n agent={agentRef}\n initialProviderId={initialProviderId}\n onProviderAuthSatisfiedChange={setAuthSatisfied}\n />\n </div>\n <div className=\"flex items-center justify-end gap-2 px-4 py-3 border-t border-border-default bg-bg-surface flex-shrink-0\">\n <button\n type=\"button\"\n onClick={() => setSdkAuthPrompt(null)}\n className=\"px-3 py-1.5 rounded-md text-sm text-text-secondary hover:text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n {tr('vibecontrols.common.close', 'Close')}\n </button>\n <button\n type=\"button\"\n onClick={() => setSdkAuthPrompt(null)}\n disabled={!authSatisfied}\n title={\n authSatisfied\n ? undefined\n : tr(\n 'vibecontrols.ai.sdkAuth.saveOneFirst',\n 'Save at least one of the listed credentials to continue'\n )\n }\n className=\"px-3 py-1.5 rounded-md text-sm font-medium bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg-hover disabled:opacity-50 disabled:cursor-not-allowed transition-colors\"\n >\n {tr('vibecontrols.ai.sdkAuth.continue', 'Continue')}\n </button>\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;AA8BA,IAAa,KAAuD,EAAE,cAAW,iBAAc;CAC7F,IAAM,CAAC,GAAa,KAAkB,EAAS,GAAM,EAC/C,CAAC,GAAY,KAAiB,EAAS,GAAM,EAC7C,CAAC,GAAa,KAAkB,EAAS,GAAG,EAG5C,CAAC,GAAkB,KAAuB,EAAS,GAAM,EACzD,CAAC,IAAqB,KAA0B,EAAS,GAAM,EAC/D,CAAC,GAAgB,KAAqB,EAAwB,KAAK,EACnE,CAAC,GAAc,KAAmB,GAAiB,EACnD,IAAyB,EAAa,IAAI,YAAY,EACtD,IAA0B,EAAsB,KAAK,EACrD,IAAK,GAAO,EAEZ,EAAE,cAAW,GAAsB,EACnC,IAAc,QACZ,EAAO,MAAM,MAAM,EAAE,OAAO,EAAQ,IAAI,MAC9C,CAAC,GAAQ,EAAQ,CAClB,EAEK,IAAe,GAAsB,MAAM,EAAE,cAAc,GAAW,EACtE,IAAa,GAAsB,MAAM,EAAE,WAAW,EAEtD,IAAW,QAAc,GAAc,YAAY,EAAE,EAAE,CAAC,GAAc,SAAS,CAAC,EAEhF,EAAE,QAAQ,MAAkB,EAAsB,GAAa,MAAM,KAAK,EAC1E,IAAW,SACR;EAAE,IAAI;EAAS,SAAS;EAAe,GAC9C,CAAC,GAAS,EAAc,CACzB,EAEK,EAAE,gBAAa,iBAAc,mBAAgB,EAAyB;EAC1E,OAAO;EACP;EACD,CAAC,EAEI,EAAE,kBAAe,QAAQ,MAAmB,EAAuB,EAAU,EAE7E,IAAgB,GAAa,MAAsB;EACvD,IAAM,IAAK,SAAS,cAAc,qBAAqB,EAAU,IAAI;AAChE,QACL,EAAG,eAAe;GAAE,UAAU;GAAU,OAAO;GAAU,CAAC,EAC1D,EAAkB,EAAU,EAC5B,OAAO,iBAAiB;AACtB,MAAmB,MAAa,MAAY,IAAY,OAAO,EAAS;KACvE,KAAK;IACP,EAAE,CAAC,EAEA,KAAuB,GAC1B,MAAsB;AAChB,IAAe;GAAE;GAAS;GAAW;GAAW,CAAC;IAExD;EAAC;EAAgB;EAAS;EAAU,CACrC,EAEK,KAAe,QAAkB;AACrC,KAAe,OACT,KAAM,EAAe,GAAG,EACrB,CAAC,GACR;IACD,EAAE,CAAC;AA0EN,QAvEA,QAAgB;EACd,IAAI,IAAY;AA8ChB,UA5CyB,YAAY;AAE/B,WAAS,SAAS,IAEtB;MAAe,GAAK;AACpB,QAAI;KACF,IAAM,IAAO,MAAM,EAAiB,GAAU,GAAW,EACvD,OAAO,CAAC,SAAS,SAAS,EAC3B,CAAC;AAEF,SAAI,KAAa,EAAK,WAAW,EAAG;KAEpC,IAAM,oBAAc,IAAI,KAAa;AAKrC,UAAK,IAAM,KAAO,GAAM;MACtB,IAAM,IAAU,EAAI,WAAW,EAAI,WAAW,IACxC,IAAY,EAAI,aAAa,EAAI,WACjC,IAAkB,IAAY,KAAK,MAAM,IAAI,KAAK,EAAU,CAAC,SAAS,GAAG,IAAK,GAAG,GACjF,IAAS;OAAC,EAAI;OAAM;OAAS,EAAI,SAAS;OAAI;OAAgB,CAAC,KAAK,KAAS;AAC/E,QAAY,IAAI,EAAO,KAC3B,EAAY,IAAI,EAAO,EAUvB,EAAW,GARsB;OAC/B,IAAI,EAAI;OACR,MAAM,EAAI,SAAS,UAAU,SAAS;OACtC;OACA;OACA,QAAQ,EAAI,aAAa;QAAE,OAAO;QAAG,QAAQ,EAAI;QAAY,GAAG,KAAA;OAChE,OAAO,EAAI;OACZ,CACyB;;YAEtB,WAEE;AACR,KAAK,KAAW,EAAe,GAAM;;;MAIlB,QAEV;AACX,OAAY;;IAGb,CAAC,GAAS,EAAU,CAAC,EAIxB,QAAgB;AAKd,MAJI,CAAC,KAA0B,KAC3B,EAAwB,YAAY,KAGpC,CADW,EAAS,MAAM,MAAM,EAAE,OAAO,EAAuB,CACvD;AAGb,EADA,EAAwB,UAAU,GAClC,EAAc,EAAuB;EAGrC,IAAM,IAAO,IAAI,gBAAgB,EAAa;AAE9C,EADA,EAAK,OAAO,YAAY,EACxB,EAAgB,GAAM,EAAE,SAAS,IAAM,CAAC;IACvC;EAAC;EAAwB;EAAa;EAAU;EAAe;EAAc;EAAgB,CAAC,EAG/F,kBAAC,OAAD;EAAK,WAAU;EAA+B,eAAY;YAA1D;GAKE,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,UAAD;OACE,MAAK;OACL,SAAS;OACT,WAAW,0CACT,IACI,oDACA;OAEN,OAAO,EACL,sDACA,6BACD;OACD,cAAY,EACV,sDACA,oBACD;iBAED,kBAAC,IAAD,EAAQ,WAAU,YAAa,CAAA;OACxB,CAAA,EACR,KACC,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,SAAD;QACE,cAAW;QACX,MAAK;QACL,OAAO;QACP,WAAW,MAAM,EAAe,EAAE,OAAO,MAAM;QAC/C,aAAa,EACX,wDACA,4BACD;QACD,WAAU;QACV,CAAA,EACD,KACC,kBAAC,UAAD;QACE,MAAK;QACL,eAAe,EAAe,GAAG;QACjC,WAAU;kBAEV,kBAAC,GAAD,EAAG,WAAU,6BAA8B,CAAA;QACpC,CAAA,CAEP;SAEJ;;KACN,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAuB,GAAK;MAC3C,WAAU;MACV,OAAO,EACL,uDACA,2BACD;MACD,cAAY,EAAG,qDAAqD,YAAY;MAChF,eAAY;gBAEZ,kBAAC,GAAD,EAAU,WAAU,YAAa,CAAA;MAC1B,CAAA;KACT,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAoB,GAAK;MACxC,WAAU;MACV,OAAO,EACL,6DACA,mEACD;MACD,cAAY,EACV,sDACA,sBACD;MACD,eAAY;gBAZd,CAcE,kBAAC,GAAD,EAAa,WAAU,YAAa,CAAA,EACpC,kBAAC,QAAD;OAAM,WAAU;iBACb,EAAG,sDAAsD,sBAAsB;OAC3E,CAAA,CACA;;KACL;;GAGL,IACC,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,KAAD;KAAG,WAAU;eACV,EACC,0DACA,0BACD;KACC,CAAA;IACA,CAAA,GAEN,kBAAC,GAAD;IACY;IACG;IACb,gBAAgB;IACH;IACE;IACf,kBAAkB;IACF;IAChB,CAAA;GAIJ,kBAAC,GAAD;IACW;IACE;IACX,SAAS,MAAS,KAAK,EAAY,EAAK;IACxC,UAAU;IACV,gBAAgB,GAAa,aAAa,KAAA;IAC1C,CAAA;GAGF,kBAAC,GAAD;IACE,MAAM;IACN,eAAe,EAAuB,GAAM;IACjC;IACD;IACV,QAAQ;IACR,CAAA;GAIF,kBAAC,GAAD;IACE,MAAM;IACG;IACT,eAAe,EAAoB,GAAM;IACzC,CAAA;GAOF,kBAAC,GAAD;IACW;IACC;IACV,mBAAmB,GAAc,OAAO,KAAA;IACxC,CAAA;GACE;;GAUJ,KAAmD,EACvD,YACA,aACA,2BACI;CACJ,IAAM,IAAgB,GAAsB,MAAM,EAAE,cAAc,EAC5D,IAAmB,GAAsB,MAAM,EAAE,iBAAiB,EAKlE,CAAC,GAAe,KAAoB,EAAS,GAAM,EACnD,IAAK,GAAO;AAalB,QARA,QAAgB;AACd,EAAI,KAAiB,EAAc,YAAY,KAC7C,EAAiB,GAAM;IAExB,CAAC,GAAe,EAAQ,CAAC,EAExB,CAAC,KAAiB,EAAc,YAAY,IAAgB,OAG9D,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GACE,WAAU;GACV,eAAe,EAAiB,KAAK;GACrC,eAAA;GACA,CAAA,EAKF,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,MAAD;MAAI,WAAU;gBACX,EAAG,iCAAiC,oCAAoC;MACtE,CAAA,EACL,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAiB,KAAK;MACrC,WAAU;MACV,cAAY,EAAG,6BAA6B,QAAQ;gBAEpD,kBAAC,GAAD,EAAG,WAAU,8BAA+B,CAAA;MACrC,CAAA,CACL;;IACN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,KAAD;MAAG,WAAU;gBACV,EACC,gCACA,8JACD;MACC,CAAA,EACJ,kBAAC,GAAD;MACE,OAAO;MACY;MACnB,+BAA+B;MAC/B,CAAA,CACE;;IACN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAiB,KAAK;MACrC,WAAU;gBAET,EAAG,6BAA6B,QAAQ;MAClC,CAAA,EACT,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAiB,KAAK;MACrC,UAAU,CAAC;MACX,OACE,IACI,KAAA,IACA,EACE,wCACA,0DACD;MAEP,WAAU;gBAET,EAAG,oCAAoC,WAAW;MAC5C,CAAA,CACL;;IACF;KACF"}
|
|
@@ -2,7 +2,7 @@ import { useVibeNavigate as e } from "../../providers/VibeControlsProvider.js";
|
|
|
2
2
|
import { useCallback as t } from "react";
|
|
3
3
|
import { useI18n as n } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
4
4
|
import { Brain as r, MapIcon as i } from "lucide-react";
|
|
5
|
-
import { useLocation as a } from "react-router
|
|
5
|
+
import { useLocation as a } from "react-router";
|
|
6
6
|
import { jsx as o, jsxs as s } from "react/jsx-runtime";
|
|
7
7
|
//#region src/components/ai/AIModeToggle.tsx
|
|
8
8
|
var c = "/ai-tools/plan";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AIModeToggle.js","names":[],"sources":["../../../src/components/ai/AIModeToggle.tsx"],"sourcesContent":["/**\n * AIModeToggle — Plan / AI mode pivot.\n *\n * Rendered both on /ai-tools (AIPage) above the 9-tab sub-nav and on\n * /ai (AgentManagerPage) in the page header. Active state is computed\n * by matching the current pathname against the two route patterns.\n * Plan click navigates to the canonical Plan workspace\n * (/ai-tools/plan); AI click takes the caller's preferred AI landing\n * page (the AgentManagerPage points back to /ai, while AIPage stays\n * inside /ai-tools/ai/:tab so the active sub-tab is preserved).\n */\n\nimport { useCallback } from 'react';\nimport { useLocation } from 'react-router
|
|
1
|
+
{"version":3,"file":"AIModeToggle.js","names":[],"sources":["../../../src/components/ai/AIModeToggle.tsx"],"sourcesContent":["/**\n * AIModeToggle — Plan / AI mode pivot.\n *\n * Rendered both on /ai-tools (AIPage) above the 9-tab sub-nav and on\n * /ai (AgentManagerPage) in the page header. Active state is computed\n * by matching the current pathname against the two route patterns.\n * Plan click navigates to the canonical Plan workspace\n * (/ai-tools/plan); AI click takes the caller's preferred AI landing\n * page (the AgentManagerPage points back to /ai, while AIPage stays\n * inside /ai-tools/ai/:tab so the active sub-tab is preserved).\n */\n\nimport { useCallback } from 'react';\nimport { useLocation } from 'react-router';\nimport { Brain, MapIcon } from 'lucide-react';\n\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useVibeNavigate } from '@/providers/VibeControlsProvider';\n\nexport type AIMode = 'plan' | 'ai';\n\ninterface AIModeToggleProps {\n /** Path Plan toggle navigates to. Defaults to `/ai-tools/plan`. */\n planHref?: string;\n /** Path AI toggle navigates to when active mode is `plan`. */\n aiHref: string;\n /**\n * Optional explicit active-mode override. When unset the component\n * derives the active mode from the current pathname.\n */\n activeMode?: AIMode;\n className?: string;\n}\n\nconst DEFAULT_PLAN_HREF = '/ai-tools/plan';\n\nfunction isPlanRoute(pathname: string): boolean {\n return pathname.endsWith('/ai-tools/plan') || pathname.includes('/ai-tools/plan/');\n}\n\nexport function AIModeToggle({\n planHref = DEFAULT_PLAN_HREF,\n aiHref,\n activeMode,\n className,\n}: AIModeToggleProps) {\n const { t } = useI18n();\n const { pathname } = useLocation();\n const navigate = useVibeNavigate();\n\n const tr = useCallback(\n (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n },\n [t]\n );\n\n const resolvedMode: AIMode = activeMode ?? (isPlanRoute(pathname) ? 'plan' : 'ai');\n\n const onClick = useCallback(\n (mode: AIMode) => {\n navigate(mode === 'plan' ? planHref : aiHref);\n },\n [aiHref, navigate, planHref]\n );\n\n return (\n <div\n role=\"tablist\"\n aria-label={tr('ai.modeToggleLabel', 'AI workspace mode')}\n className={`inline-flex rounded-lg border border-border-default bg-bg-sunken p-0.5 ${className ?? ''}`}\n >\n {(['plan', 'ai'] as const).map((m) => {\n const isActive = resolvedMode === m;\n const Icon = m === 'plan' ? MapIcon : Brain;\n return (\n <button\n type=\"button\"\n key={m}\n role=\"tab\"\n aria-selected={isActive}\n data-testid={`ai-mode-toggle-${m}`}\n onClick={() => onClick(m)}\n className={`flex items-center gap-2 px-4 py-1.5 text-sm font-medium rounded-md transition-colors ${\n isActive\n ? 'bg-bg-surface text-action-primary-bg shadow-sm'\n : 'text-text-secondary hover:text-text-primary'\n }`}\n >\n <Icon className=\"size-4\" />\n {m === 'plan' ? tr('ai.mode.plan', 'Plan') : tr('ai.mode.ai', 'AI')}\n </button>\n );\n })}\n </div>\n );\n}\n"],"mappings":";;;;;;;AAkCA,IAAM,IAAoB;AAE1B,SAAS,EAAY,GAA2B;AAC9C,QAAO,EAAS,SAAS,iBAAiB,IAAI,EAAS,SAAS,kBAAkB;;AAGpF,SAAgB,EAAa,EAC3B,cAAW,GACX,WACA,eACA,gBACoB;CACpB,IAAM,EAAE,SAAM,GAAS,EACjB,EAAE,gBAAa,GAAa,EAC5B,IAAW,GAAiB,EAE5B,IAAK,GACR,GAAa,MAA6B;EACzC,IAAM,IAAQ,EAAE,EAAI;AACpB,SAAO,MAAU,IAAM,IAAW;IAEpC,CAAC,EAAE,CACJ,EAEK,IAAuB,MAAe,EAAY,EAAS,GAAG,SAAS,OAEvE,IAAU,GACb,MAAiB;AAChB,IAAS,MAAS,SAAS,IAAW,EAAO;IAE/C;EAAC;EAAQ;EAAU;EAAS,CAC7B;AAED,QACE,kBAAC,OAAD;EACE,MAAK;EACL,cAAY,EAAG,sBAAsB,oBAAoB;EACzD,WAAW,0EAA0E,KAAa;YAEhG,CAAC,QAAQ,KAAK,CAAW,KAAK,MAAM;GACpC,IAAM,IAAW,MAAiB,GAC5B,IAAO,MAAM,SAAS,IAAU;AACtC,UACE,kBAAC,UAAD;IACE,MAAK;IAEL,MAAK;IACL,iBAAe;IACf,eAAa,kBAAkB;IAC/B,eAAe,EAAQ,EAAE;IACzB,WAAW,wFACT,IACI,mDACA;cAVR,CAaE,kBAAC,GAAD,EAAM,WAAU,UAAW,CAAA,EAC1B,MAAM,SAAS,EAAG,gBAAgB,OAAO,GAAG,EAAG,cAAc,KAAK,CAC5D;MAbF,EAaE;IAEX;EACE,CAAA"}
|
|
@@ -4,7 +4,7 @@ import { AgentStatusBadge as r } from "../agents/AgentStatusBadge.js";
|
|
|
4
4
|
import { resolveAgentTarget as i } from "./actionAgentTarget.js";
|
|
5
5
|
import { useMemo as a } from "react";
|
|
6
6
|
import { AlertTriangle as o, ArrowUpRight as s, ServerCog as c } from "lucide-react";
|
|
7
|
-
import { useNavigate as l } from "react-router
|
|
7
|
+
import { useNavigate as l } from "react-router";
|
|
8
8
|
import { Fragment as u, jsx as d, jsxs as f } from "react/jsx-runtime";
|
|
9
9
|
//#region src/components/common/ActionAgentTargetNotice.tsx
|
|
10
10
|
var p = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ActionAgentTargetNotice.js","names":[],"sources":["../../../src/components/common/ActionAgentTargetNotice.tsx"],"sourcesContent":["import { useMemo } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport { AlertTriangle, ArrowUpRight, ServerCog } from 'lucide-react';\n\nimport {\n useListAgentsQuery,\n useVibecontrolsAgentHealthProbesQuery,\n} from '@/generated/wspace-operations';\nimport type { AgentHealthProbe } from '@/utils/agentStatus';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { AgentStatusBadge } from '../agents/AgentStatusBadge';\nimport {\n resolveAgentTarget,\n type AgentTargetActionLike,\n type AgentTargetAgentLike,\n} from './actionAgentTarget';\n\ninterface ActionAgentTargetNoticeProps {\n /** The action being linked. Needs id + type + agent targeting fields. */\n action: AgentTargetActionLike & { id: string };\n className?: string;\n}\n\ntype Tone = 'info' | 'success' | 'warning' | 'error';\n\nconst TONE_CLASS: Record<Tone, string> = {\n info: 'border-border-subtle bg-bg-sunken text-text-secondary',\n success: 'border-status-success-border bg-status-success-bg-subtle text-status-success-text',\n warning: 'border-status-warning-border bg-status-warning-bg-subtle text-status-warning-text',\n error: 'border-status-error-border bg-status-error-bg-subtle text-status-error-text',\n};\n\n/**\n * Shown when an action is being linked to another surface (vibe-deck button,\n * scheduler, …). It tells the user which agent the action will run on and, when\n * that agent is unhealthy or no longer exists, warns them right in the linking\n * flow and links to the page where they can set the right agent.\n */\nexport function ActionAgentTargetNotice({ action, className = '' }: ActionAgentTargetNoticeProps) {\n const tr = useTr();\n const navigate = useNavigate();\n\n // Only COMMAND/SCRIPT actions run on an agent — skip the agent + health-probe\n // fetches entirely for webhook/workflow actions where the result is unused.\n const needsAgent = action.actionType === 'COMMAND' || action.actionType === 'SCRIPT';\n const { data: agentsData } = useListAgentsQuery({\n variables: { pagination: { limit: 200 } },\n fetchPolicy: 'cache-and-network',\n skip: !needsAgent,\n });\n const probeQuery = useVibecontrolsAgentHealthProbesQuery({\n fetchPolicy: 'cache-and-network',\n nextFetchPolicy: 'cache-first',\n pollInterval: 60_000,\n skip: !needsAgent,\n });\n const probesLoading = probeQuery.data === undefined;\n\n const agents = useMemo<AgentTargetAgentLike[]>(() => {\n const raw = agentsData?.vibecontrolsAgents ?? [];\n return raw.map((a: NonNullable<typeof raw>[number]) => ({\n id: a.id,\n name: a.name,\n isActive: a.isActive,\n platform: a.platform ?? null,\n metadata: (a.metadata as Record<string, unknown> | null) ?? null,\n }));\n }, [agentsData]);\n\n const probesById = useMemo(() => {\n const m = new Map<string, AgentHealthProbe>();\n for (const p of probeQuery.data?.vibecontrolsAgentHealthProbes ?? []) {\n m.set(p.agentId, { agentId: p.agentId, healthy: p.healthy, errorMessage: p.errorMessage });\n }\n return m;\n }, [probeQuery.data]);\n\n const res = useMemo(\n () => resolveAgentTarget(action, agents, probesById, probesLoading),\n [action, agents, probesById, probesLoading]\n );\n\n const goAgents = () => navigate('/vibecontrols/agents');\n const goAgent = (id: string) => navigate(`/vibecontrols/agents/${encodeURIComponent(id)}`);\n const goEditAction = () =>\n navigate(`/vibecontrols/actions?editAction=${encodeURIComponent(action.id)}`);\n\n // Webhook / workflow actions don't run on an agent.\n if (res.mode === 'no-agent') {\n if (action.actionType === 'WEBHOOK') {\n return (\n <div\n className={`flex items-center gap-2 rounded-card border border-border-subtle bg-bg-sunken px-3 py-2 text-xs text-text-secondary ${className}`}\n >\n <ServerCog size={14} className=\"flex-shrink-0\" />\n <span>\n {tr(\n 'vibecontrols.actions.agentTarget.webhook',\n 'Runs as a webhook request — no agent needed.'\n )}\n </span>\n </div>\n );\n }\n return null;\n }\n\n const LinkButton = ({ label, onClick }: { label: string; onClick: () => void }) => (\n <button\n type=\"button\"\n onClick={onClick}\n className=\"inline-flex items-center gap-0.5 font-medium underline-offset-2 hover:underline\"\n >\n {label}\n <ArrowUpRight size={12} />\n </button>\n );\n\n let tone: Tone = 'info';\n let icon = <ServerCog size={14} className=\"mt-0.5 flex-shrink-0\" />;\n let title = '';\n let body: React.ReactNode = null;\n let links: React.ReactNode = null;\n\n if (res.mode === 'any-available') {\n if (res.noHealthyAgent) {\n tone = 'warning';\n icon = <AlertTriangle size={14} className=\"mt-0.5 flex-shrink-0\" />;\n title = tr('vibecontrols.actions.agentTarget.anyTitle', 'Runs on: any available agent');\n body = tr(\n 'vibecontrols.actions.agentTarget.anyNone',\n 'No healthy agent is available right now, so this action will fail until you add or start one.'\n );\n links = (\n <LinkButton\n label={tr('vibecontrols.actions.agentTarget.manageAgents', 'Manage agents')}\n onClick={goAgents}\n />\n );\n } else {\n tone = 'info';\n title = tr('vibecontrols.actions.agentTarget.anyTitle', 'Runs on: any available agent');\n body = probesLoading\n ? tr('vibecontrols.actions.agentTarget.anyChecking', 'Checking agent availability…')\n : tr(\n 'vibecontrols.actions.agentTarget.anyOk',\n 'A healthy agent is picked automatically when this runs.'\n ) + (res.healthyCount > 0 ? ` (${res.healthyCount} available)` : '');\n }\n } else if (res.missing) {\n tone = 'error';\n icon = <AlertTriangle size={14} className=\"mt-0.5 flex-shrink-0\" />;\n title = tr(\n 'vibecontrols.actions.agentTarget.missingTitle',\n 'The agent set for this action no longer exists'\n );\n body = tr(\n 'vibecontrols.actions.agentTarget.missingBody',\n 'Update the action to choose an available agent, otherwise it will fail when it runs.'\n );\n links = (\n <>\n <LinkButton\n label={tr('vibecontrols.actions.agentTarget.setAgent', 'Set agent')}\n onClick={goEditAction}\n />\n <LinkButton\n label={tr('vibecontrols.actions.agentTarget.manageAgents', 'Manage agents')}\n onClick={goAgents}\n />\n </>\n );\n } else if (res.agent) {\n const healthy = res.healthy;\n tone = healthy ? 'success' : 'warning';\n icon = healthy ? (\n <ServerCog size={14} className=\"mt-0.5 flex-shrink-0\" />\n ) : (\n <AlertTriangle size={14} className=\"mt-0.5 flex-shrink-0\" />\n );\n title = `${tr('vibecontrols.actions.agentTarget.runsOn', 'Runs on')}: ${res.agent.name}`;\n body = healthy\n ? null\n : tr(\n 'vibecontrols.actions.agentTarget.unhealthyBody',\n \"This agent isn't healthy, so the action may fail. Pick a different agent or fix this one before linking.\"\n );\n links = healthy ? null : (\n <>\n <LinkButton\n label={tr('vibecontrols.actions.agentTarget.setAgent', 'Set agent')}\n onClick={goEditAction}\n />\n <LinkButton\n label={tr('vibecontrols.actions.agentTarget.viewAgent', 'View agent')}\n onClick={() => res.agent && goAgent(res.agent.id)}\n />\n </>\n );\n }\n\n return (\n <div className={`rounded-card border px-3 py-2.5 text-xs ${TONE_CLASS[tone]} ${className}`}>\n <div className=\"flex items-start gap-2\">\n {icon}\n <div className=\"min-w-0 flex-1 space-y-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"font-medium\">{title}</span>\n {res.mode === 'specific' && res.agent && res.status && (\n <AgentStatusBadge status={res.status} reason={res.reason} />\n )}\n </div>\n {body && <p className=\"opacity-90\">{body}</p>}\n {links && <div className=\"flex flex-wrap items-center gap-3 pt-0.5\">{links}</div>}\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ActionAgentTargetNotice.js","names":[],"sources":["../../../src/components/common/ActionAgentTargetNotice.tsx"],"sourcesContent":["import { useMemo } from 'react';\nimport { useNavigate } from 'react-router';\nimport { AlertTriangle, ArrowUpRight, ServerCog } from 'lucide-react';\n\nimport {\n useListAgentsQuery,\n useVibecontrolsAgentHealthProbesQuery,\n} from '@/generated/wspace-operations';\nimport type { AgentHealthProbe } from '@/utils/agentStatus';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { AgentStatusBadge } from '../agents/AgentStatusBadge';\nimport {\n resolveAgentTarget,\n type AgentTargetActionLike,\n type AgentTargetAgentLike,\n} from './actionAgentTarget';\n\ninterface ActionAgentTargetNoticeProps {\n /** The action being linked. Needs id + type + agent targeting fields. */\n action: AgentTargetActionLike & { id: string };\n className?: string;\n}\n\ntype Tone = 'info' | 'success' | 'warning' | 'error';\n\nconst TONE_CLASS: Record<Tone, string> = {\n info: 'border-border-subtle bg-bg-sunken text-text-secondary',\n success: 'border-status-success-border bg-status-success-bg-subtle text-status-success-text',\n warning: 'border-status-warning-border bg-status-warning-bg-subtle text-status-warning-text',\n error: 'border-status-error-border bg-status-error-bg-subtle text-status-error-text',\n};\n\n/**\n * Shown when an action is being linked to another surface (vibe-deck button,\n * scheduler, …). It tells the user which agent the action will run on and, when\n * that agent is unhealthy or no longer exists, warns them right in the linking\n * flow and links to the page where they can set the right agent.\n */\nexport function ActionAgentTargetNotice({ action, className = '' }: ActionAgentTargetNoticeProps) {\n const tr = useTr();\n const navigate = useNavigate();\n\n // Only COMMAND/SCRIPT actions run on an agent — skip the agent + health-probe\n // fetches entirely for webhook/workflow actions where the result is unused.\n const needsAgent = action.actionType === 'COMMAND' || action.actionType === 'SCRIPT';\n const { data: agentsData } = useListAgentsQuery({\n variables: { pagination: { limit: 200 } },\n fetchPolicy: 'cache-and-network',\n skip: !needsAgent,\n });\n const probeQuery = useVibecontrolsAgentHealthProbesQuery({\n fetchPolicy: 'cache-and-network',\n nextFetchPolicy: 'cache-first',\n pollInterval: 60_000,\n skip: !needsAgent,\n });\n const probesLoading = probeQuery.data === undefined;\n\n const agents = useMemo<AgentTargetAgentLike[]>(() => {\n const raw = agentsData?.vibecontrolsAgents ?? [];\n return raw.map((a: NonNullable<typeof raw>[number]) => ({\n id: a.id,\n name: a.name,\n isActive: a.isActive,\n platform: a.platform ?? null,\n metadata: (a.metadata as Record<string, unknown> | null) ?? null,\n }));\n }, [agentsData]);\n\n const probesById = useMemo(() => {\n const m = new Map<string, AgentHealthProbe>();\n for (const p of probeQuery.data?.vibecontrolsAgentHealthProbes ?? []) {\n m.set(p.agentId, { agentId: p.agentId, healthy: p.healthy, errorMessage: p.errorMessage });\n }\n return m;\n }, [probeQuery.data]);\n\n const res = useMemo(\n () => resolveAgentTarget(action, agents, probesById, probesLoading),\n [action, agents, probesById, probesLoading]\n );\n\n const goAgents = () => navigate('/vibecontrols/agents');\n const goAgent = (id: string) => navigate(`/vibecontrols/agents/${encodeURIComponent(id)}`);\n const goEditAction = () =>\n navigate(`/vibecontrols/actions?editAction=${encodeURIComponent(action.id)}`);\n\n // Webhook / workflow actions don't run on an agent.\n if (res.mode === 'no-agent') {\n if (action.actionType === 'WEBHOOK') {\n return (\n <div\n className={`flex items-center gap-2 rounded-card border border-border-subtle bg-bg-sunken px-3 py-2 text-xs text-text-secondary ${className}`}\n >\n <ServerCog size={14} className=\"flex-shrink-0\" />\n <span>\n {tr(\n 'vibecontrols.actions.agentTarget.webhook',\n 'Runs as a webhook request — no agent needed.'\n )}\n </span>\n </div>\n );\n }\n return null;\n }\n\n const LinkButton = ({ label, onClick }: { label: string; onClick: () => void }) => (\n <button\n type=\"button\"\n onClick={onClick}\n className=\"inline-flex items-center gap-0.5 font-medium underline-offset-2 hover:underline\"\n >\n {label}\n <ArrowUpRight size={12} />\n </button>\n );\n\n let tone: Tone = 'info';\n let icon = <ServerCog size={14} className=\"mt-0.5 flex-shrink-0\" />;\n let title = '';\n let body: React.ReactNode = null;\n let links: React.ReactNode = null;\n\n if (res.mode === 'any-available') {\n if (res.noHealthyAgent) {\n tone = 'warning';\n icon = <AlertTriangle size={14} className=\"mt-0.5 flex-shrink-0\" />;\n title = tr('vibecontrols.actions.agentTarget.anyTitle', 'Runs on: any available agent');\n body = tr(\n 'vibecontrols.actions.agentTarget.anyNone',\n 'No healthy agent is available right now, so this action will fail until you add or start one.'\n );\n links = (\n <LinkButton\n label={tr('vibecontrols.actions.agentTarget.manageAgents', 'Manage agents')}\n onClick={goAgents}\n />\n );\n } else {\n tone = 'info';\n title = tr('vibecontrols.actions.agentTarget.anyTitle', 'Runs on: any available agent');\n body = probesLoading\n ? tr('vibecontrols.actions.agentTarget.anyChecking', 'Checking agent availability…')\n : tr(\n 'vibecontrols.actions.agentTarget.anyOk',\n 'A healthy agent is picked automatically when this runs.'\n ) + (res.healthyCount > 0 ? ` (${res.healthyCount} available)` : '');\n }\n } else if (res.missing) {\n tone = 'error';\n icon = <AlertTriangle size={14} className=\"mt-0.5 flex-shrink-0\" />;\n title = tr(\n 'vibecontrols.actions.agentTarget.missingTitle',\n 'The agent set for this action no longer exists'\n );\n body = tr(\n 'vibecontrols.actions.agentTarget.missingBody',\n 'Update the action to choose an available agent, otherwise it will fail when it runs.'\n );\n links = (\n <>\n <LinkButton\n label={tr('vibecontrols.actions.agentTarget.setAgent', 'Set agent')}\n onClick={goEditAction}\n />\n <LinkButton\n label={tr('vibecontrols.actions.agentTarget.manageAgents', 'Manage agents')}\n onClick={goAgents}\n />\n </>\n );\n } else if (res.agent) {\n const healthy = res.healthy;\n tone = healthy ? 'success' : 'warning';\n icon = healthy ? (\n <ServerCog size={14} className=\"mt-0.5 flex-shrink-0\" />\n ) : (\n <AlertTriangle size={14} className=\"mt-0.5 flex-shrink-0\" />\n );\n title = `${tr('vibecontrols.actions.agentTarget.runsOn', 'Runs on')}: ${res.agent.name}`;\n body = healthy\n ? null\n : tr(\n 'vibecontrols.actions.agentTarget.unhealthyBody',\n \"This agent isn't healthy, so the action may fail. Pick a different agent or fix this one before linking.\"\n );\n links = healthy ? null : (\n <>\n <LinkButton\n label={tr('vibecontrols.actions.agentTarget.setAgent', 'Set agent')}\n onClick={goEditAction}\n />\n <LinkButton\n label={tr('vibecontrols.actions.agentTarget.viewAgent', 'View agent')}\n onClick={() => res.agent && goAgent(res.agent.id)}\n />\n </>\n );\n }\n\n return (\n <div className={`rounded-card border px-3 py-2.5 text-xs ${TONE_CLASS[tone]} ${className}`}>\n <div className=\"flex items-start gap-2\">\n {icon}\n <div className=\"min-w-0 flex-1 space-y-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"font-medium\">{title}</span>\n {res.mode === 'specific' && res.agent && res.status && (\n <AgentStatusBadge status={res.status} reason={res.reason} />\n )}\n </div>\n {body && <p className=\"opacity-90\">{body}</p>}\n {links && <div className=\"flex flex-wrap items-center gap-3 pt-0.5\">{links}</div>}\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -8,7 +8,7 @@ import { createDocsPreviewHtml as o } from "./previewHtml.js";
|
|
|
8
8
|
import { useCallback as s, useEffect as c, useRef as l, useState as u } from "react";
|
|
9
9
|
import { useI18n as d } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
10
10
|
import { ArrowLeft as f, Edit3 as p, FileText as m, Maximize2 as h, Minimize2 as g, Tag as _ } from "lucide-react";
|
|
11
|
-
import { useNavigate as v } from "react-router
|
|
11
|
+
import { useNavigate as v } from "react-router";
|
|
12
12
|
import { usePermissions as y } from "@burdenoff/fe-libs/shared/providers/shell";
|
|
13
13
|
import { jsx as b, jsxs as x } from "react/jsx-runtime";
|
|
14
14
|
//#region src/components/docs/DocsFullScreenViewer.tsx
|