@burdenoff/microfe-vibecontrols 2026.519.1 → 2026.519.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -134,14 +134,13 @@ function v({ context: v, vibeId: y, agentVibes: b = [] }) {
134
134
  try {
135
135
  let n, r = v === "vibe" ? y : e.vibeId;
136
136
  if (e.id) {
137
- let i;
137
+ let r;
138
138
  try {
139
- i = await Ae({ variables: {
139
+ r = await Ae({ variables: {
140
140
  id: e.id,
141
141
  input: {
142
142
  title: e.title,
143
- content: e.content,
144
- vibeId: r
143
+ content: e.content
145
144
  }
146
145
  } });
147
146
  } catch (e) {
@@ -151,7 +150,7 @@ function v({ context: v, vibeId: y, agentVibes: b = [] }) {
151
150
  }
152
151
  throw e;
153
152
  }
154
- n = i.data?.updateNote?.id;
153
+ n = r.data?.updateNote?.id;
155
154
  } else {
156
155
  let i;
157
156
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"NotesTabPanel.js","names":[],"sources":["../../../src/components/notes/NotesTabPanel.tsx"],"sourcesContent":["import { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useState, useMemo } from 'react';\nimport { Plus, FileText, Grid, List, Pin, RefreshCw } from 'lucide-react';\nimport { AnimatePresence } from 'framer-motion';\nimport { useNavigate } from 'react-router-dom';\nimport { MotionList, MotionItem } from '@burdenoff/fe-libs/shared/motion';\nimport { NoteCard } from './NoteCard';\nimport { NoteList } from './NoteList';\nimport { NoteEditor } from './NoteEditor';\nimport type { Note } from './types';\nimport type { NoteEditorSubmitData } from './NoteEditor';\nimport {\n SearchInput,\n FilterDropdown,\n EmptyState,\n LoadingState,\n ErrorState,\n QuotaExhaustedDialog,\n} from '@/components/shared';\nimport {\n useListNotesQuery,\n usePinNoteMutation,\n useUnpinNoteMutation,\n useDeleteNoteMutation,\n useCreateNoteMutation,\n useUpdateNoteMutation,\n useReplaceNoteTagsMutation,\n useAvailableNoteTagsQuery,\n} from '@/generated/wspace-operations';\nimport { PermissionButton, AccessDenied } from '@burdenoff/fe-libs/shared/components';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { VIBE_PERMISSIONS } from '@/constants/permissions';\nimport { isQuotaExhaustedError } from '@/utils/quotaUtils';\n\ntype ViewMode = 'grid' | 'list';\n\ninterface NotesTabPanelProps {\n /** 'vibe' = single vibe context; 'agent' = multi-vibe agent context */\n context: 'vibe' | 'agent';\n /** For vibe context: the single vibeId to scope notes to */\n vibeId?: string;\n /** For agent context: the vibes belonging to the agent */\n agentVibes?: { id: string; name: string }[];\n}\n\n/**\n * Reusable notes panel for embedding as a tab in detail pages.\n * Supports vibe-scoped and agent-scoped contexts.\n */\nexport function NotesTabPanel({ context, vibeId, agentVibes = [] }: NotesTabPanelProps) {\n const { t } = useI18n();\n const navigate = useNavigate();\n const tr = (key: string, fallback: string) => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n const { hasPermission, isLoading: permissionsLoading, permissions } = usePermissions();\n\n const canListNotes = hasPermission(VIBE_PERMISSIONS.NOTE_LIST);\n const canCreateNote = hasPermission(VIBE_PERMISSIONS.NOTE_CREATE);\n\n const [searchQuery, setSearchQuery] = useState('');\n const [vibeFilter, setVibeFilter] = useState('all');\n const [tagFilter, setTagFilter] = useState('all');\n const [showPinnedOnly, setShowPinnedOnly] = useState(false);\n const [viewMode, setViewMode] = useState<ViewMode>('grid');\n const [showEditor, setShowEditor] = useState(false);\n const [selectedNote, setSelectedNote] = useState<Note | null>(null);\n const [showQuotaExhausted, setShowQuotaExhausted] = useState(false);\n\n const [pinNote] = usePinNoteMutation({ refetchQueries: ['ListNotes'] });\n const [unpinNote] = useUnpinNoteMutation({ refetchQueries: ['ListNotes'] });\n const [deleteNote] = useDeleteNoteMutation({ refetchQueries: ['ListNotes'] });\n const [createNote] = useCreateNoteMutation({ refetchQueries: ['ListNotes'] });\n const [updateNote] = useUpdateNoteMutation({ refetchQueries: ['ListNotes'] });\n const [replaceNoteTags] = useReplaceNoteTagsMutation({ refetchQueries: ['ListNotes'] });\n\n const shouldSkipQueries = permissionsLoading || !canListNotes;\n\n const { data: availableTagsData } = useAvailableNoteTagsQuery({ skip: shouldSkipQueries });\n\n // Build query filter based on context\n const queryFilter = useMemo(() => {\n if (context === 'vibe' && vibeId) {\n return {\n vibeId,\n isPinned: showPinnedOnly ? true : undefined,\n };\n }\n // Agent context: fetch all notes, filter client-side\n return {\n vibeId: vibeFilter === 'none' ? null : vibeFilter !== 'all' ? vibeFilter : undefined,\n isPinned: showPinnedOnly ? true : undefined,\n };\n }, [context, vibeId, vibeFilter, showPinnedOnly]);\n\n const { data, loading, error, refetch } = useListNotesQuery({\n variables: { filter: queryFilter, pagination: { limit: 50 } },\n fetchPolicy: 'cache-and-network',\n skip: shouldSkipQueries,\n });\n\n // Vibe filter options (agent context only)\n const vibeOptions = useMemo(() => {\n if (context !== 'agent') return [];\n return [\n { value: 'all', label: 'All Vibes' },\n { value: 'none', label: 'No Vibe (General)' },\n ...agentVibes.map((v) => ({ value: v.id, label: v.name })),\n ];\n }, [context, agentVibes]);\n\n // Map GraphQL response to Note type\n const notes: Note[] = useMemo(() => {\n const rawNotes = data?.notes?.items ?? [];\n const mapped = rawNotes.map((note: NonNullable<typeof rawNotes>[number]) => ({\n id: note.id,\n workspaceId: note.workspaceId,\n vibeId: note.vibeId || null,\n title: note.title,\n content: note.content,\n tags: note.tags || [],\n tagAssignments:\n note.tagAssignments?.map((a: NonNullable<typeof note.tagAssignments>[number]) => ({\n id: a.id,\n tagId: a.tagId,\n tag: a.tag\n ? { id: a.tag.id, label: a.tag.label, key: a.tag.key, color: a.tag.color }\n : undefined,\n entityType: a.entityType,\n entityId: a.entityId,\n source: a.source,\n assignedBy: a.assignedBy,\n assignedAt: a.assignedAt,\n })) || [],\n isPinned: note.isPinned,\n createdBy: '',\n metadata: (note.metadata as Record<string, unknown>) || {},\n createdAt: note.createdAt,\n updatedAt: note.updatedAt,\n vibe: note.vibe ? { id: note.vibe.id, name: note.vibe.name } : undefined,\n }));\n\n // Agent context: client-side filter to only agent's vibes\n if (context === 'agent' && agentVibes.length > 0) {\n const vibeIdSet = new Set(agentVibes.map((v) => v.id));\n return mapped.filter((n: Note) => n.vibeId && vibeIdSet.has(n.vibeId));\n }\n\n return mapped;\n }, [data?.notes?.items, context, agentVibes]);\n\n // Tag filter options\n const tagOptions = useMemo(() => {\n const tags = availableTagsData?.availableNoteTags ?? [];\n return [\n { value: 'all', label: 'All Tags' },\n ...tags.map((tag: { id: string; label: string }) => ({\n value: tag.id,\n label: tag.label,\n })),\n ];\n }, [availableTagsData?.availableNoteTags]);\n\n // Client-side search and tag filtering\n const filteredNotes = useMemo(() => {\n return notes.filter((note) => {\n if (searchQuery) {\n const query = searchQuery.toLowerCase();\n const matchesSearch =\n note.title.toLowerCase().includes(query) ||\n note.content.toLowerCase().includes(query) ||\n note.tagAssignments?.some((a) => a.tag?.label?.toLowerCase().includes(query));\n if (!matchesSearch) return false;\n }\n if (tagFilter !== 'all') {\n const hasTag = note.tagAssignments?.some((a) => a.tagId === tagFilter);\n if (!hasTag) return false;\n }\n return true;\n });\n }, [notes, searchQuery, tagFilter]);\n\n // Sort: pinned first, then by updatedAt\n const sortedNotes = useMemo(() => {\n return [...filteredNotes].sort((a, b) => {\n if (a.isPinned && !b.isPinned) return -1;\n if (!a.isPinned && b.isPinned) return 1;\n return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime();\n });\n }, [filteredNotes]);\n\n const handleNoteSelect = (note: Note) => {\n setSelectedNote(note);\n setShowEditor(true);\n };\n\n const handleCreateNote = () => {\n setSelectedNote(null);\n setShowEditor(true);\n };\n\n const handlePinNote = async (note: Note) => {\n try {\n if (note.isPinned) {\n await unpinNote({ variables: { id: note.id } });\n } else {\n await pinNote({ variables: { id: note.id } });\n }\n } catch (err) {\n console.error('Failed to toggle pin for note:', err);\n }\n };\n\n const handleDeleteNote = async (note: Note) => {\n try {\n await deleteNote({ variables: { id: note.id } });\n } catch (err) {\n console.error('Failed to delete note:', err);\n }\n };\n\n const handleSaveNote = async (noteData: NoteEditorSubmitData) => {\n const closeEditorForQuota = () => {\n setShowEditor(false);\n setSelectedNote(null);\n setShowQuotaExhausted(true);\n };\n\n try {\n let noteId: string | undefined;\n\n // In vibe context, always force the vibeId\n const effectiveVibeId = context === 'vibe' ? vibeId : noteData.vibeId;\n\n if (noteData.id) {\n let result;\n try {\n result = await updateNote({\n variables: {\n id: noteData.id,\n input: {\n title: noteData.title,\n content: noteData.content,\n vibeId: effectiveVibeId,\n },\n },\n });\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n closeEditorForQuota();\n return;\n }\n\n throw err;\n }\n noteId = result.data?.updateNote?.id;\n } else {\n let result;\n try {\n result = await createNote({\n variables: {\n input: {\n title: noteData.title || '',\n content: noteData.content || '',\n vibeId: effectiveVibeId,\n },\n },\n });\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n closeEditorForQuota();\n return;\n }\n\n throw err;\n }\n noteId = result.data?.createNote?.id;\n }\n\n // Assign tags\n if (noteId && noteData.selectedTagIds && noteData.selectedTagIds.length > 0) {\n await replaceNoteTags({\n variables: { input: { noteId, tagIds: noteData.selectedTagIds } },\n });\n } else if (noteId && noteData.selectedTagIds?.length === 0 && noteData.id) {\n await replaceNoteTags({\n variables: { input: { noteId, tagIds: [] } },\n });\n }\n\n setShowEditor(false);\n } catch (err) {\n console.error('Failed to save note:', err);\n }\n };\n\n // Stats\n const stats = {\n total: notes.length,\n pinned: notes.filter((n) => n.isPinned).length,\n };\n\n // Vibe options for editor\n const vibeOptionsForEditor = useMemo(() => {\n if (context === 'vibe') return [];\n return agentVibes;\n }, [context, agentVibes]);\n\n // Permission guard\n if (!permissionsLoading && permissions && !canListNotes) {\n return (\n <AccessDenied\n title=\"Access Denied\"\n description=\"You don't have permission to view notes.\"\n details=\"Required permission: note:list\"\n />\n );\n }\n\n if (loading && notes.length === 0) {\n return <LoadingState message=\"Loading notes...\" />;\n }\n\n if (error && notes.length === 0) {\n return (\n <ErrorState title=\"Failed to load notes\" message={error.message} onRetry={() => refetch()} />\n );\n }\n\n const isFiltered =\n searchQuery ||\n (context === 'agent' && vibeFilter !== 'all') ||\n tagFilter !== 'all' ||\n showPinnedOnly;\n\n return (\n <div className=\"space-y-4\">\n {/* Header */}\n <div className=\"flex items-center justify-between\">\n <div>\n <h3 className=\"text-lg font-semibold text-text-primary\">Notes</h3>\n <p className=\"text-sm text-text-secondary\">\n {stats.total} {stats.total === 1 ? 'note' : 'notes'}, {stats.pinned} pinned\n </p>\n </div>\n <div className=\"flex items-center gap-2\">\n <button\n onClick={() => refetch()}\n disabled={loading}\n className=\"flex items-center gap-2 px-3 py-2 text-text-secondary hover:bg-bg-sunken rounded-md transition-colors disabled:opacity-50\"\n title=\"Refresh notes\"\n >\n <RefreshCw className={`w-4 h-4 ${loading ? 'animate-spin' : ''}`} />\n </button>\n <PermissionButton permission={VIBE_PERMISSIONS.NOTE_CREATE}>\n <button\n onClick={handleCreateNote}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n <Plus className=\"w-4 h-4\" />\n New Note\n </button>\n </PermissionButton>\n </div>\n </div>\n\n {/* Filters */}\n <div className=\"space-y-3\">\n <div className=\"flex items-center gap-2\">\n <SearchInput\n value={searchQuery}\n onChange={setSearchQuery}\n placeholder=\"Search notes...\"\n className=\"flex-1 sm:w-64 sm:flex-none\"\n />\n <div className=\"flex items-center gap-1 bg-bg-sunken rounded-md p-1 flex-shrink-0\">\n <button\n onClick={() => setViewMode('grid')}\n className={`p-1.5 sm:p-2 rounded ${viewMode === 'grid' ? 'bg-bg-surface shadow-sm' : 'hover:bg-bg-surface/50'}`}\n title=\"Grid view\"\n >\n <Grid className=\"w-4 h-4 text-text-secondary\" />\n </button>\n <button\n onClick={() => setViewMode('list')}\n className={`p-1.5 sm:p-2 rounded ${viewMode === 'list' ? 'bg-bg-surface shadow-sm' : 'hover:bg-bg-surface/50'}`}\n title=\"List view\"\n >\n <List className=\"w-4 h-4 text-text-secondary\" />\n </button>\n </div>\n </div>\n\n <div className=\"flex items-center gap-2 sm:gap-3 flex-wrap\">\n {context === 'agent' && (\n <FilterDropdown\n label=\"Vibe\"\n value={vibeFilter}\n options={vibeOptions}\n onChange={setVibeFilter}\n />\n )}\n <FilterDropdown\n label=\"Tag\"\n value={tagFilter}\n options={tagOptions}\n onChange={setTagFilter}\n />\n </div>\n </div>\n\n {/* Pinned toggle */}\n <div className=\"flex items-center gap-2\">\n <button\n onClick={() => setShowPinnedOnly(!showPinnedOnly)}\n className={`flex items-center gap-2 px-3 py-1.5 text-sm rounded-full transition-colors ${\n showPinnedOnly\n ? 'bg-status-warning-bg text-status-warning-text'\n : 'bg-bg-sunken text-text-secondary hover:bg-bg-sunken/80'\n }`}\n >\n <Pin className=\"w-3 h-3\" />\n Pinned ({stats.pinned})\n </button>\n </div>\n\n {/* Results count */}\n {isFiltered ? (\n <p className=\"text-sm text-text-secondary\">\n Showing {sortedNotes.length} of {notes.length} notes\n {loading && <span className=\"ml-2 text-text-muted\">(refreshing...)</span>}\n </p>\n ) : null}\n\n {/* Notes display */}\n <AnimatePresence mode=\"wait\">\n {sortedNotes.length > 0 ? (\n viewMode === 'grid' ? (\n <MotionList\n key=\"grid\"\n stagger={0.04}\n className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\"\n >\n {sortedNotes.map((note) => (\n <MotionItem key={note.id}>\n <NoteCard\n note={note}\n onOpen={handleNoteSelect}\n onEdit={handleNoteSelect}\n onPin={handlePinNote}\n onUnpin={handlePinNote}\n onDelete={handleDeleteNote}\n />\n </MotionItem>\n ))}\n </MotionList>\n ) : (\n <MotionList key=\"list\">\n {sortedNotes.map((note) => (\n <MotionItem key={note.id} variant=\"fadeRight\">\n <NoteList\n notes={[note]}\n onOpen={handleNoteSelect}\n onEdit={handleNoteSelect}\n onPin={handlePinNote}\n onUnpin={handlePinNote}\n onDelete={handleDeleteNote}\n />\n </MotionItem>\n ))}\n </MotionList>\n )\n ) : (\n <EmptyState\n icon={<FileText className=\"w-8 h-8 text-text-secondary\" />}\n title={isFiltered ? 'No notes found' : 'No notes yet'}\n description={\n isFiltered\n ? 'Try adjusting your search or filters'\n : 'Create your first note to start documenting your work'\n }\n action={\n !isFiltered && canCreateNote ? (\n <button\n onClick={handleCreateNote}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n <Plus className=\"w-4 h-4\" />\n New Note\n </button>\n ) : undefined\n }\n />\n )}\n </AnimatePresence>\n\n {/* Note Editor Modal */}\n {showEditor && (\n <NoteEditor\n note={selectedNote || undefined}\n vibeOptions={vibeOptionsForEditor}\n defaultVibeId={context === 'vibe' ? vibeId : undefined}\n hideVibeSelect={context === 'vibe'}\n onSubmit={handleSaveNote}\n onCancel={() => setShowEditor(false)}\n />\n )}\n\n <QuotaExhaustedDialog\n isOpen={showQuotaExhausted}\n onClose={() => setShowQuotaExhausted(false)}\n onConfirm={() => {\n setShowQuotaExhausted(false);\n navigate('/billing');\n }}\n title=\"Note Quota Reached\"\n message=\"You've used all available note quota on your current plan. Upgrade your plan to create or update more notes.\"\n />\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"NotesTabPanel.js","names":[],"sources":["../../../src/components/notes/NotesTabPanel.tsx"],"sourcesContent":["import { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useState, useMemo } from 'react';\nimport { Plus, FileText, Grid, List, Pin, RefreshCw } from 'lucide-react';\nimport { AnimatePresence } from 'framer-motion';\nimport { useNavigate } from 'react-router-dom';\nimport { MotionList, MotionItem } from '@burdenoff/fe-libs/shared/motion';\nimport { NoteCard } from './NoteCard';\nimport { NoteList } from './NoteList';\nimport { NoteEditor } from './NoteEditor';\nimport type { Note } from './types';\nimport type { NoteEditorSubmitData } from './NoteEditor';\nimport {\n SearchInput,\n FilterDropdown,\n EmptyState,\n LoadingState,\n ErrorState,\n QuotaExhaustedDialog,\n} from '@/components/shared';\nimport {\n useListNotesQuery,\n usePinNoteMutation,\n useUnpinNoteMutation,\n useDeleteNoteMutation,\n useCreateNoteMutation,\n useUpdateNoteMutation,\n useReplaceNoteTagsMutation,\n useAvailableNoteTagsQuery,\n} from '@/generated/wspace-operations';\nimport { PermissionButton, AccessDenied } from '@burdenoff/fe-libs/shared/components';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { VIBE_PERMISSIONS } from '@/constants/permissions';\nimport { isQuotaExhaustedError } from '@/utils/quotaUtils';\n\ntype ViewMode = 'grid' | 'list';\n\ninterface NotesTabPanelProps {\n /** 'vibe' = single vibe context; 'agent' = multi-vibe agent context */\n context: 'vibe' | 'agent';\n /** For vibe context: the single vibeId to scope notes to */\n vibeId?: string;\n /** For agent context: the vibes belonging to the agent */\n agentVibes?: { id: string; name: string }[];\n}\n\n/**\n * Reusable notes panel for embedding as a tab in detail pages.\n * Supports vibe-scoped and agent-scoped contexts.\n */\nexport function NotesTabPanel({ context, vibeId, agentVibes = [] }: NotesTabPanelProps) {\n const { t } = useI18n();\n const navigate = useNavigate();\n const tr = (key: string, fallback: string) => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n const { hasPermission, isLoading: permissionsLoading, permissions } = usePermissions();\n\n const canListNotes = hasPermission(VIBE_PERMISSIONS.NOTE_LIST);\n const canCreateNote = hasPermission(VIBE_PERMISSIONS.NOTE_CREATE);\n\n const [searchQuery, setSearchQuery] = useState('');\n const [vibeFilter, setVibeFilter] = useState('all');\n const [tagFilter, setTagFilter] = useState('all');\n const [showPinnedOnly, setShowPinnedOnly] = useState(false);\n const [viewMode, setViewMode] = useState<ViewMode>('grid');\n const [showEditor, setShowEditor] = useState(false);\n const [selectedNote, setSelectedNote] = useState<Note | null>(null);\n const [showQuotaExhausted, setShowQuotaExhausted] = useState(false);\n\n const [pinNote] = usePinNoteMutation({ refetchQueries: ['ListNotes'] });\n const [unpinNote] = useUnpinNoteMutation({ refetchQueries: ['ListNotes'] });\n const [deleteNote] = useDeleteNoteMutation({ refetchQueries: ['ListNotes'] });\n const [createNote] = useCreateNoteMutation({ refetchQueries: ['ListNotes'] });\n const [updateNote] = useUpdateNoteMutation({ refetchQueries: ['ListNotes'] });\n const [replaceNoteTags] = useReplaceNoteTagsMutation({ refetchQueries: ['ListNotes'] });\n\n const shouldSkipQueries = permissionsLoading || !canListNotes;\n\n const { data: availableTagsData } = useAvailableNoteTagsQuery({ skip: shouldSkipQueries });\n\n // Build query filter based on context\n const queryFilter = useMemo(() => {\n if (context === 'vibe' && vibeId) {\n return {\n vibeId,\n isPinned: showPinnedOnly ? true : undefined,\n };\n }\n // Agent context: fetch all notes, filter client-side\n return {\n vibeId: vibeFilter === 'none' ? null : vibeFilter !== 'all' ? vibeFilter : undefined,\n isPinned: showPinnedOnly ? true : undefined,\n };\n }, [context, vibeId, vibeFilter, showPinnedOnly]);\n\n const { data, loading, error, refetch } = useListNotesQuery({\n variables: { filter: queryFilter, pagination: { limit: 50 } },\n fetchPolicy: 'cache-and-network',\n skip: shouldSkipQueries,\n });\n\n // Vibe filter options (agent context only)\n const vibeOptions = useMemo(() => {\n if (context !== 'agent') return [];\n return [\n { value: 'all', label: 'All Vibes' },\n { value: 'none', label: 'No Vibe (General)' },\n ...agentVibes.map((v) => ({ value: v.id, label: v.name })),\n ];\n }, [context, agentVibes]);\n\n // Map GraphQL response to Note type\n const notes: Note[] = useMemo(() => {\n const rawNotes = data?.notes?.items ?? [];\n const mapped = rawNotes.map((note: NonNullable<typeof rawNotes>[number]) => ({\n id: note.id,\n workspaceId: note.workspaceId,\n vibeId: note.vibeId || null,\n title: note.title,\n content: note.content,\n tags: note.tags || [],\n tagAssignments:\n note.tagAssignments?.map((a: NonNullable<typeof note.tagAssignments>[number]) => ({\n id: a.id,\n tagId: a.tagId,\n tag: a.tag\n ? { id: a.tag.id, label: a.tag.label, key: a.tag.key, color: a.tag.color }\n : undefined,\n entityType: a.entityType,\n entityId: a.entityId,\n source: a.source,\n assignedBy: a.assignedBy,\n assignedAt: a.assignedAt,\n })) || [],\n isPinned: note.isPinned,\n createdBy: '',\n metadata: (note.metadata as Record<string, unknown>) || {},\n createdAt: note.createdAt,\n updatedAt: note.updatedAt,\n vibe: note.vibe ? { id: note.vibe.id, name: note.vibe.name } : undefined,\n }));\n\n // Agent context: client-side filter to only agent's vibes\n if (context === 'agent' && agentVibes.length > 0) {\n const vibeIdSet = new Set(agentVibes.map((v) => v.id));\n return mapped.filter((n: Note) => n.vibeId && vibeIdSet.has(n.vibeId));\n }\n\n return mapped;\n }, [data?.notes?.items, context, agentVibes]);\n\n // Tag filter options\n const tagOptions = useMemo(() => {\n const tags = availableTagsData?.availableNoteTags ?? [];\n return [\n { value: 'all', label: 'All Tags' },\n ...tags.map((tag: { id: string; label: string }) => ({\n value: tag.id,\n label: tag.label,\n })),\n ];\n }, [availableTagsData?.availableNoteTags]);\n\n // Client-side search and tag filtering\n const filteredNotes = useMemo(() => {\n return notes.filter((note) => {\n if (searchQuery) {\n const query = searchQuery.toLowerCase();\n const matchesSearch =\n note.title.toLowerCase().includes(query) ||\n note.content.toLowerCase().includes(query) ||\n note.tagAssignments?.some((a) => a.tag?.label?.toLowerCase().includes(query));\n if (!matchesSearch) return false;\n }\n if (tagFilter !== 'all') {\n const hasTag = note.tagAssignments?.some((a) => a.tagId === tagFilter);\n if (!hasTag) return false;\n }\n return true;\n });\n }, [notes, searchQuery, tagFilter]);\n\n // Sort: pinned first, then by updatedAt\n const sortedNotes = useMemo(() => {\n return [...filteredNotes].sort((a, b) => {\n if (a.isPinned && !b.isPinned) return -1;\n if (!a.isPinned && b.isPinned) return 1;\n return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime();\n });\n }, [filteredNotes]);\n\n const handleNoteSelect = (note: Note) => {\n setSelectedNote(note);\n setShowEditor(true);\n };\n\n const handleCreateNote = () => {\n setSelectedNote(null);\n setShowEditor(true);\n };\n\n const handlePinNote = async (note: Note) => {\n try {\n if (note.isPinned) {\n await unpinNote({ variables: { id: note.id } });\n } else {\n await pinNote({ variables: { id: note.id } });\n }\n } catch (err) {\n console.error('Failed to toggle pin for note:', err);\n }\n };\n\n const handleDeleteNote = async (note: Note) => {\n try {\n await deleteNote({ variables: { id: note.id } });\n } catch (err) {\n console.error('Failed to delete note:', err);\n }\n };\n\n const handleSaveNote = async (noteData: NoteEditorSubmitData) => {\n const closeEditorForQuota = () => {\n setShowEditor(false);\n setSelectedNote(null);\n setShowQuotaExhausted(true);\n };\n\n try {\n let noteId: string | undefined;\n\n // In vibe context, always force the vibeId\n const effectiveVibeId = context === 'vibe' ? vibeId : noteData.vibeId;\n\n if (noteData.id) {\n let result;\n try {\n result = await updateNote({\n variables: {\n id: noteData.id,\n input: {\n title: noteData.title,\n content: noteData.content,\n },\n },\n });\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n closeEditorForQuota();\n return;\n }\n\n throw err;\n }\n noteId = result.data?.updateNote?.id;\n } else {\n let result;\n try {\n result = await createNote({\n variables: {\n input: {\n title: noteData.title || '',\n content: noteData.content || '',\n vibeId: effectiveVibeId,\n },\n },\n });\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n closeEditorForQuota();\n return;\n }\n\n throw err;\n }\n noteId = result.data?.createNote?.id;\n }\n\n // Assign tags\n if (noteId && noteData.selectedTagIds && noteData.selectedTagIds.length > 0) {\n await replaceNoteTags({\n variables: { input: { noteId, tagIds: noteData.selectedTagIds } },\n });\n } else if (noteId && noteData.selectedTagIds?.length === 0 && noteData.id) {\n await replaceNoteTags({\n variables: { input: { noteId, tagIds: [] } },\n });\n }\n\n setShowEditor(false);\n } catch (err) {\n console.error('Failed to save note:', err);\n }\n };\n\n // Stats\n const stats = {\n total: notes.length,\n pinned: notes.filter((n) => n.isPinned).length,\n };\n\n // Vibe options for editor\n const vibeOptionsForEditor = useMemo(() => {\n if (context === 'vibe') return [];\n return agentVibes;\n }, [context, agentVibes]);\n\n // Permission guard\n if (!permissionsLoading && permissions && !canListNotes) {\n return (\n <AccessDenied\n title=\"Access Denied\"\n description=\"You don't have permission to view notes.\"\n details=\"Required permission: note:list\"\n />\n );\n }\n\n if (loading && notes.length === 0) {\n return <LoadingState message=\"Loading notes...\" />;\n }\n\n if (error && notes.length === 0) {\n return (\n <ErrorState title=\"Failed to load notes\" message={error.message} onRetry={() => refetch()} />\n );\n }\n\n const isFiltered =\n searchQuery ||\n (context === 'agent' && vibeFilter !== 'all') ||\n tagFilter !== 'all' ||\n showPinnedOnly;\n\n return (\n <div className=\"space-y-4\">\n {/* Header */}\n <div className=\"flex items-center justify-between\">\n <div>\n <h3 className=\"text-lg font-semibold text-text-primary\">Notes</h3>\n <p className=\"text-sm text-text-secondary\">\n {stats.total} {stats.total === 1 ? 'note' : 'notes'}, {stats.pinned} pinned\n </p>\n </div>\n <div className=\"flex items-center gap-2\">\n <button\n onClick={() => refetch()}\n disabled={loading}\n className=\"flex items-center gap-2 px-3 py-2 text-text-secondary hover:bg-bg-sunken rounded-md transition-colors disabled:opacity-50\"\n title=\"Refresh notes\"\n >\n <RefreshCw className={`w-4 h-4 ${loading ? 'animate-spin' : ''}`} />\n </button>\n <PermissionButton permission={VIBE_PERMISSIONS.NOTE_CREATE}>\n <button\n onClick={handleCreateNote}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n <Plus className=\"w-4 h-4\" />\n New Note\n </button>\n </PermissionButton>\n </div>\n </div>\n\n {/* Filters */}\n <div className=\"space-y-3\">\n <div className=\"flex items-center gap-2\">\n <SearchInput\n value={searchQuery}\n onChange={setSearchQuery}\n placeholder=\"Search notes...\"\n className=\"flex-1 sm:w-64 sm:flex-none\"\n />\n <div className=\"flex items-center gap-1 bg-bg-sunken rounded-md p-1 flex-shrink-0\">\n <button\n onClick={() => setViewMode('grid')}\n className={`p-1.5 sm:p-2 rounded ${viewMode === 'grid' ? 'bg-bg-surface shadow-sm' : 'hover:bg-bg-surface/50'}`}\n title=\"Grid view\"\n >\n <Grid className=\"w-4 h-4 text-text-secondary\" />\n </button>\n <button\n onClick={() => setViewMode('list')}\n className={`p-1.5 sm:p-2 rounded ${viewMode === 'list' ? 'bg-bg-surface shadow-sm' : 'hover:bg-bg-surface/50'}`}\n title=\"List view\"\n >\n <List className=\"w-4 h-4 text-text-secondary\" />\n </button>\n </div>\n </div>\n\n <div className=\"flex items-center gap-2 sm:gap-3 flex-wrap\">\n {context === 'agent' && (\n <FilterDropdown\n label=\"Vibe\"\n value={vibeFilter}\n options={vibeOptions}\n onChange={setVibeFilter}\n />\n )}\n <FilterDropdown\n label=\"Tag\"\n value={tagFilter}\n options={tagOptions}\n onChange={setTagFilter}\n />\n </div>\n </div>\n\n {/* Pinned toggle */}\n <div className=\"flex items-center gap-2\">\n <button\n onClick={() => setShowPinnedOnly(!showPinnedOnly)}\n className={`flex items-center gap-2 px-3 py-1.5 text-sm rounded-full transition-colors ${\n showPinnedOnly\n ? 'bg-status-warning-bg text-status-warning-text'\n : 'bg-bg-sunken text-text-secondary hover:bg-bg-sunken/80'\n }`}\n >\n <Pin className=\"w-3 h-3\" />\n Pinned ({stats.pinned})\n </button>\n </div>\n\n {/* Results count */}\n {isFiltered ? (\n <p className=\"text-sm text-text-secondary\">\n Showing {sortedNotes.length} of {notes.length} notes\n {loading && <span className=\"ml-2 text-text-muted\">(refreshing...)</span>}\n </p>\n ) : null}\n\n {/* Notes display */}\n <AnimatePresence mode=\"wait\">\n {sortedNotes.length > 0 ? (\n viewMode === 'grid' ? (\n <MotionList\n key=\"grid\"\n stagger={0.04}\n className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\"\n >\n {sortedNotes.map((note) => (\n <MotionItem key={note.id}>\n <NoteCard\n note={note}\n onOpen={handleNoteSelect}\n onEdit={handleNoteSelect}\n onPin={handlePinNote}\n onUnpin={handlePinNote}\n onDelete={handleDeleteNote}\n />\n </MotionItem>\n ))}\n </MotionList>\n ) : (\n <MotionList key=\"list\">\n {sortedNotes.map((note) => (\n <MotionItem key={note.id} variant=\"fadeRight\">\n <NoteList\n notes={[note]}\n onOpen={handleNoteSelect}\n onEdit={handleNoteSelect}\n onPin={handlePinNote}\n onUnpin={handlePinNote}\n onDelete={handleDeleteNote}\n />\n </MotionItem>\n ))}\n </MotionList>\n )\n ) : (\n <EmptyState\n icon={<FileText className=\"w-8 h-8 text-text-secondary\" />}\n title={isFiltered ? 'No notes found' : 'No notes yet'}\n description={\n isFiltered\n ? 'Try adjusting your search or filters'\n : 'Create your first note to start documenting your work'\n }\n action={\n !isFiltered && canCreateNote ? (\n <button\n onClick={handleCreateNote}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n <Plus className=\"w-4 h-4\" />\n New Note\n </button>\n ) : undefined\n }\n />\n )}\n </AnimatePresence>\n\n {/* Note Editor Modal */}\n {showEditor && (\n <NoteEditor\n note={selectedNote || undefined}\n vibeOptions={vibeOptionsForEditor}\n defaultVibeId={context === 'vibe' ? vibeId : undefined}\n hideVibeSelect={context === 'vibe'}\n onSubmit={handleSaveNote}\n onCancel={() => setShowEditor(false)}\n />\n )}\n\n <QuotaExhaustedDialog\n isOpen={showQuotaExhausted}\n onClose={() => setShowQuotaExhausted(false)}\n onConfirm={() => {\n setShowQuotaExhausted(false);\n navigate('/billing');\n }}\n title=\"Note Quota Reached\"\n message=\"You've used all available note quota on your current plan. Upgrade your plan to create or update more notes.\"\n />\n </div>\n );\n}\n"],"mappings":"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"}
@@ -133,7 +133,8 @@ function y() {
133
133
  fetchPolicy: "cache-and-network",
134
134
  nextFetchPolicy: "cache-first",
135
135
  notifyOnNetworkStatusChange: !0,
136
- skip: !b
136
+ skip: !b,
137
+ pollInterval: 3e4
137
138
  }), { data: J } = ae({ skip: !b }), kt = q.data === void 0 && J === void 0, Y = l(() => {
138
139
  let e = /* @__PURE__ */ new Map(), t = J?.vibecontrolsAgentHealthProbeStream ?? q.data?.vibecontrolsAgentHealthProbes ?? [];
139
140
  for (let n of t) e.set(n.agentId, {
@@ -409,11 +410,13 @@ function y() {
409
410
  actions: /* @__PURE__ */ h("div", {
410
411
  className: "flex w-full flex-col items-end gap-2 sm:w-auto sm:flex-row sm:items-center",
411
412
  children: [/* @__PURE__ */ m("button", {
412
- onClick: () => K(),
413
- disabled: G,
413
+ onClick: () => {
414
+ K(), q.refetch();
415
+ },
416
+ disabled: G || q.loading,
414
417
  className: "flex items-center justify-center gap-2 px-3 py-2 text-text-secondary hover:bg-bg-sunken rounded-md transition-colors disabled:opacity-50",
415
418
  title: S("agentsPage.refreshTitle", "Refresh agents"),
416
- children: /* @__PURE__ */ m(Ae, { className: `w-4 h-4 ${G ? "animate-spin" : ""}` })
419
+ children: /* @__PURE__ */ m(Ae, { className: `w-4 h-4 ${G || q.loading ? "animate-spin" : ""}` })
417
420
  }), St ? /* @__PURE__ */ m(Le, {
418
421
  permission: be.AGENT_CREATE,
419
422
  deniedBehavior: "disable",
@@ -1 +1 @@
1
- {"version":3,"file":"AgentsPage.js","names":[],"sources":["../../src/pages/AgentsPage.tsx"],"sourcesContent":["import { useState, useMemo, useRef, useCallback, useEffect } from 'react';\nimport { Plus, Bot, Grid, List, RefreshCw, Trash2, CheckSquare, XSquare } from 'lucide-react';\nimport { MotionPage, MotionList, MotionItem } from '@burdenoff/fe-libs/shared/motion';\nimport { AnimatePresence, motion } from 'framer-motion';\nimport { AgentCard, AgentList, AgentForm } from '@/components/agents';\nimport type { Agent } from '@/components/agents';\nimport {\n PageHeader,\n SearchInput,\n FilterDropdown,\n TagFilter,\n EmptyState,\n LoadingState,\n ErrorState,\n AgentSetupBanner,\n QuotaExhaustedDialog,\n GetHelpButton,\n} from '@/components/shared';\nimport { useVibeControls, useVibeNavigate } from '@/providers/VibeControlsProvider';\nimport {\n useListAgentsQuery,\n useVibecontrolsAgentHealthProbesQuery,\n useVibecontrolsAgentHealthProbeStreamSubscription,\n useCreateAgentMutation,\n useUpdateAgentMutation,\n useDeleteAgentMutation,\n useAgentHeartbeatMutation,\n useDeactivateAgentMutation,\n useCreateTargetMutation,\n useAddAgentFromTunnelMutation,\n useStartVibecontrolsAgentMutation,\n useStopVibecontrolsAgentMutation,\n} from '@/generated/wspace-operations';\nimport { PermissionButton, AccessDenied } from '@burdenoff/fe-libs/shared/components';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { VIBE_PERMISSIONS } from '@/constants/permissions';\nimport { deriveAgentStatus, type AgentHealthProbe } from '@/utils/agentStatus';\nimport { isQuotaExhaustedError } from '@/utils/quotaUtils';\nimport { useCachedInitialData } from '@/hooks/useCachedInitialData';\nimport { useVibeControlsStore } from '@/store/vibeControlsStore';\n\ntype ViewMode = 'grid' | 'list';\n\n/**\n * Agents management page with filtering, search, and CRUD operations\n */\nexport function AgentsPage() {\n const { onAgentSelect, workspaceId } = useVibeControls();\n const handleNavigate = useVibeNavigate();\n const { hasPermission, isLoading: permissionsLoading, permissions } = usePermissions();\n const { t } = useI18n();\n\n const tr = useCallback(\n (key: string, fallback: string, params?: Record<string, string | number>): string => {\n const translated = t(key, params);\n const template = translated === key ? fallback : translated;\n\n if (!params) return template;\n\n return Object.entries(params).reduce(\n (acc, [paramKey, paramValue]) => acc.replace(`{{${paramKey}}}`, String(paramValue)),\n template\n );\n },\n [t]\n );\n\n const statusOptions = [\n { value: 'all', label: tr('agentsPage.statusAll', 'All Statuses') },\n { value: 'ACTIVE', label: tr('agentsPage.statusActive', 'Active') },\n { value: 'STOPPED', label: tr('agentsPage.statusStopped', 'Stopped') },\n { value: 'OFFLINE', label: tr('agentsPage.statusOffline', 'Offline') },\n ];\n\n const platformOptions = [\n { value: 'all', label: tr('agentsPage.platformAll', 'All Platforms') },\n { value: 'darwin', label: tr('agentsPage.platformMac', 'macOS') },\n { value: 'linux', label: tr('agentsPage.platformLinux', 'Linux') },\n { value: 'windows', label: tr('agentsPage.platformWindows', 'Windows') },\n ];\n\n // Check permissions\n const canListAgents = hasPermission(VIBE_PERMISSIONS.AGENT_LIST);\n\n const [searchQuery, setSearchQuery] = useState('');\n const [statusFilter, setStatusFilter] = useState('all');\n const [platformFilter, setPlatformFilter] = useState('all');\n const [tagFilter, setTagFilter] = useState('all');\n const [viewMode, setViewMode] = useState<ViewMode>('grid');\n const [showCreateForm, setShowCreateForm] = useState(false);\n const [isCreating, setIsCreating] = useState(false);\n const [editTunnelAgent, setEditTunnelAgent] = useState<Agent | null>(null);\n const [isSavingTunnel, setIsSavingTunnel] = useState(false);\n // Per-agent \"connecting\" overlay shown after Edit Tunnel save while the\n // backend pushes OAuth creds → agent finalize() → /health/ready flips to\n // ok. State lives in the global store (vibeControlsStore) so the overlay\n // survives navigation to AgentDetailsPage and back — the documented\n // recovery path navigates into the details page so pushAgentGatewayAuth\n // auto-recovery can fire, and storing the set in local React state used\n // to discard it on unmount, causing a CONNECTING → DEGRADED → ACTIVE\n // flicker on return.\n const connectingAgents = useVibeControlsStore((s) => s.connectingAgents);\n const markAgentConnecting = useVibeControlsStore((s) => s.markAgentConnecting);\n const clearAgentConnecting = useVibeControlsStore((s) => s.clearAgentConnecting);\n const connectingAgentIds = useMemo(\n () => new Set(Object.keys(connectingAgents)),\n [connectingAgents]\n );\n\n // 30s safety net — if a probe never reports healthy, drop the overlay\n // so the user eventually sees the real DEGRADED state.\n const connectingTimersRef = useRef<Map<string, ReturnType<typeof setTimeout>>>(new Map());\n useEffect(() => {\n const timers = connectingTimersRef.current;\n for (const agentId of Object.keys(connectingAgents)) {\n if (timers.has(agentId)) continue;\n const timer = setTimeout(() => {\n clearAgentConnecting(agentId);\n timers.delete(agentId);\n }, 30_000);\n timers.set(agentId, timer);\n }\n // Drop timers for agents no longer marked connecting.\n for (const [agentId, timer] of timers.entries()) {\n if (!(agentId in connectingAgents)) {\n clearTimeout(timer);\n timers.delete(agentId);\n }\n }\n }, [connectingAgents, clearAgentConnecting]);\n\n useEffect(() => {\n const timers = connectingTimersRef.current;\n return () => {\n for (const timer of timers.values()) clearTimeout(timer);\n timers.clear();\n };\n }, []);\n const [disconnectConfirmAgent, setDisconnectConfirmAgent] = useState<Agent | null>(null);\n const [isDisconnecting, setIsDisconnecting] = useState(false);\n const tunnelUrlRef = useRef<HTMLInputElement>(null);\n const tunnelApiKeyRef = useRef<HTMLInputElement>(null);\n\n // Bulk selection state\n const [selectedAgentIds, setSelectedAgentIds] = useState<Set<string>>(new Set());\n const [bulkLoading, setBulkLoading] = useState(false);\n const [successMessage, setSuccessMessage] = useState<string | null>(null);\n const [errorMessage, setErrorMessage] = useState<string | null>(null);\n const [showQuotaExhausted, setShowQuotaExhausted] = useState(false);\n const toastTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n useEffect(() => {\n return () => {\n if (toastTimerRef.current) clearTimeout(toastTimerRef.current);\n };\n }, []);\n\n const showSuccess = useCallback((msg: string) => {\n if (toastTimerRef.current) clearTimeout(toastTimerRef.current);\n setErrorMessage(null);\n setSuccessMessage(msg);\n toastTimerRef.current = setTimeout(() => setSuccessMessage(null), 3000);\n }, []);\n const showError = useCallback((msg: string) => {\n if (toastTimerRef.current) clearTimeout(toastTimerRef.current);\n setSuccessMessage(null);\n setErrorMessage(msg);\n toastTimerRef.current = setTimeout(() => setErrorMessage(null), 5000);\n }, []);\n\n const [createAgent] = useCreateAgentMutation({ refetchQueries: ['ListAgents'] });\n const [addAgentFromTunnel] = useAddAgentFromTunnelMutation({ refetchQueries: ['ListAgents'] });\n const [agentHeartbeat] = useAgentHeartbeatMutation({ refetchQueries: ['ListAgents'] });\n const [deactivateAgent] = useDeactivateAgentMutation({ refetchQueries: ['ListAgents'] });\n const [startAgentMutation] = useStartVibecontrolsAgentMutation({\n refetchQueries: ['ListAgents'],\n });\n const [stopAgentMutation] = useStopVibecontrolsAgentMutation({ refetchQueries: ['ListAgents'] });\n\n const {\n targets: cachedTargets,\n loading: initialDataLoading,\n refetch: refetchInitialData,\n } = useCachedInitialData();\n const targetsList = cachedTargets;\n const targetsLoading = initialDataLoading;\n const hasTargets = targetsList.length > 0;\n const [updateAgent] = useUpdateAgentMutation({ refetchQueries: ['ListAgents'] });\n const [deleteAgent] = useDeleteAgentMutation({ refetchQueries: ['ListAgents'] });\n // refetchQueries + awaitRefetchQueries: the inline create flow used to fire\n // refetchInitialData() without awaiting it, so AgentForm closed and\n // re-rendered against stale `cachedTargets` — the new target only appeared\n // on full page reload. Awaiting the underlying queries here makes the\n // mutation resolve only after the initial-data + targets caches are fresh.\n const [createTargetMutation] = useCreateTargetMutation({\n refetchQueries: ['VibeControlsInitialData', 'ListTargets'],\n awaitRefetchQueries: true,\n });\n\n /**\n * Inline DIRECT target creation callback for the AgentForm. Returns\n * the newly created target so the form can auto-select it.\n */\n const handleInlineCreateTarget = useCallback(\n async (targetName: string) => {\n if (!workspaceId || !targetName.trim()) return null;\n try {\n const { data: result } = await createTargetMutation({\n variables: {\n workspaceId,\n input: {\n type: 'DIRECT',\n name: targetName.trim(),\n },\n },\n });\n const created = result?.createTarget;\n if (!created) return null;\n refetchInitialData();\n return {\n id: created.id,\n name: created.name,\n type: created.type ?? 'DIRECT',\n status: created.status ?? 'unknown',\n };\n } catch {\n return null;\n }\n },\n [createTargetMutation, refetchInitialData, workspaceId]\n );\n\n // Fetch agents from GraphQL API\n // Skip until a workspace is selected: the resolver requires\n // x-workspace-id and throws \"Workspace context required\" when fired\n // before the auth provider has resolved a workspace (URL with ?org but\n // no &workspace, post-context-switcher self-heal, etc.). Without this\n // skip, the page lands on \"Failed to load agents\" and never recovers\n // because Apollo's errorPolicy: 'all' returns the error and doesn't\n // auto-refetch when workspaceId becomes available.\n const { data, loading, error, refetch } = useListAgentsQuery({\n variables: {\n filter: {\n isActive: statusFilter === 'ACTIVE' ? true : statusFilter === 'OFFLINE' ? false : undefined,\n platform: platformFilter !== 'all' ? platformFilter : undefined,\n },\n pagination: { limit: 100 },\n },\n fetchPolicy: 'cache-first',\n skip: !workspaceId,\n });\n\n // Live deep-health probe — backend hits /health/ready on every agent and\n // returns per-agent { healthy, errorMessage }. Polled every 30s so stale\n // tunnels (cloudflared dead, agent crashed but `isActive` still true)\n // flip the badge to DEGRADED before users hit \"create session\" against\n // an unreachable agent.\n // Initial query primes the cache; the subscription below replaces the\n // prior 30s pollInterval so badges flip to/from DEGRADED in <1s.\n const probeQuery = useVibecontrolsAgentHealthProbesQuery({\n fetchPolicy: 'cache-and-network',\n nextFetchPolicy: 'cache-first',\n notifyOnNetworkStatusChange: true,\n skip: !workspaceId,\n });\n const { data: probeStreamData } = useVibecontrolsAgentHealthProbeStreamSubscription({\n skip: !workspaceId,\n });\n // True when no probe data has been delivered for this mount cycle yet.\n // Used by deriveAgentStatus to show REGISTERING for agents we have not\n // yet probed (instead of optimistically ACTIVE).\n const probesLoading = probeQuery.data === undefined && probeStreamData === undefined;\n const probesById = useMemo(() => {\n const m = new Map<string, AgentHealthProbe>();\n // Subscription stream wins over the initial query — strictly newer.\n const probes =\n probeStreamData?.vibecontrolsAgentHealthProbeStream ??\n probeQuery.data?.vibecontrolsAgentHealthProbes ??\n [];\n for (const p of probes) {\n m.set(p.agentId, {\n agentId: p.agentId,\n healthy: p.healthy,\n errorMessage: p.errorMessage,\n });\n }\n return m;\n }, [probeQuery.data, probeStreamData]);\n\n // Map GraphQL response to Agent type\n const agents: Agent[] = useMemo(() => {\n const rawAgents = data?.vibecontrolsAgents ?? [];\n return rawAgents.map((agent: NonNullable<typeof rawAgents>[number]) => ({\n id: agent.id,\n workspaceId: agent.workspaceId,\n name: agent.name,\n hostname: agent.hostname,\n platform: agent.platform,\n architecture: agent.architecture,\n version: agent.version,\n isActive: agent.isActive,\n status: connectingAgentIds.has(agent.id)\n ? ('CONNECTING' as const)\n : deriveAgentStatus(\n {\n id: agent.id,\n isActive: agent.isActive,\n metadata: agent.metadata as Record<string, unknown> | null,\n },\n probesById,\n { probesLoading }\n ),\n // Prefer the live health probe error. When the live probe didn't run\n // (or returned ok) but the agent's most-recent gateway-auth push\n // failed, surface that instead — otherwise the only signal of a\n // failed onboarding is a yellow Degraded badge with no hover text.\n // `pending` is hidden: it just means the async finalize is still\n // running and the badge already shows the right transient state.\n healthError:\n probesById.get(agent.id)?.errorMessage ??\n (agent.lastGatewayAuthPushStatus === 'failed'\n ? (agent.lastGatewayAuthPushError ?? 'Gateway-auth push failed')\n : null),\n lastHeartbeat: agent.lastHeartbeat || null,\n tunnelUrl: agent.tunnelUrl ?? null,\n tunnelStatus: agent.tunnelStatus ?? null,\n agentApiKey: agent.agentApiKey ?? null,\n configuration: (agent.configuration as Record<string, unknown>) || {},\n capabilities: Array.isArray(agent.capabilities)\n ? (agent.capabilities as string[])\n : typeof agent.capabilities === 'object' && agent.capabilities !== null\n ? (() => {\n const obj = agent.capabilities as Record<string, unknown>;\n const keys = Object.keys(obj);\n // When capabilities was serialized from an array, keys are numeric (\"0\",\"1\",\"2\")\n // In that case, extract values (the actual capability names) instead of keys\n const hasNumericKeys = keys.length > 0 && keys.every((k) => /^\\d+$/.test(k));\n return hasNumericKeys ? (Object.values(obj) as string[]) : (keys as string[]);\n })()\n : [],\n metadata: (agent.metadata as Record<string, unknown>) || {},\n createdAt: agent.createdAt,\n updatedAt: agent.updatedAt,\n tagAssignments: (agent.tagAssignments ?? []).map(\n (a: NonNullable<typeof agent.tagAssignments>[number]) => ({\n id: a.id,\n tagId: a.tagId,\n tag: a.tagInfo\n ? {\n id: a.tagInfo.id,\n key: a.tagInfo.key ?? null,\n label: a.tagInfo.label ?? null,\n color: a.tagInfo.color ?? null,\n }\n : null,\n })\n ),\n }));\n }, [data?.vibecontrolsAgents, probesById, probesLoading, connectingAgentIds]);\n\n // Clear CONNECTING overlay only when ALL of these hold:\n // 1. The live probe reports `healthy: true` (deep-health probe, which\n // already gates on session.ok + tunnel.ok + ttyd.ok server-side —\n // see probeAgentHealth in wspace-vibecontrols-svc).\n // 2. The grace-window timestamp stored at markAgentConnecting time has\n // elapsed. This defends against a STALE `healthy:true` probe\n // delivered from the Apollo cache during the in-flight refetch\n // that follows updateAgent — without the grace window, that stale\n // cache hit cleared CONNECTING immediately and the next (fresh,\n // transient) probe landing during cloudflared restart flipped the\n // badge to DEGRADED, producing the CONNECTING → DEGRADED → ACTIVE\n // flicker reported by users.\n //\n // Re-runs on `probesById` updates AND on a one-shot timer per pending\n // agent so the clear fires automatically once the grace window elapses\n // even if no new probe arrives.\n useEffect(() => {\n const pending = Object.keys(connectingAgents);\n if (pending.length === 0) return;\n const now = Date.now();\n let earliestPending = Infinity;\n for (const agentId of pending) {\n const minClearAt = connectingAgents[agentId];\n if (minClearAt === undefined) continue;\n const probe = probesById.get(agentId);\n if (probe?.healthy === true && now >= minClearAt) {\n clearAgentConnecting(agentId);\n } else if (minClearAt < earliestPending) {\n earliestPending = minClearAt;\n }\n }\n if (earliestPending === Infinity) return;\n const wait = Math.max(0, earliestPending - now) + 50;\n const tick = setTimeout(() => {\n // No-op state poke — the dependency on `connectingAgents` causes\n // this effect to re-run when state changes, but a healthy probe\n // arriving BEFORE the grace window elapses needs a re-evaluation\n // when the window finally closes. Re-marking with graceMs=0 is a\n // no-op when the entry is already past its window; if cleared in\n // the meantime, the early `pending.length === 0` short-circuit\n // makes this safe.\n const fresh = useVibeControlsStore.getState().connectingAgents;\n for (const agentId of Object.keys(fresh)) {\n const minClearAt = fresh[agentId];\n if (minClearAt === undefined) continue;\n const probe = probesById.get(agentId);\n if (probe?.healthy === true && Date.now() >= minClearAt) {\n clearAgentConnecting(agentId);\n }\n }\n }, wait);\n return () => clearTimeout(tick);\n }, [probesById, connectingAgents, clearAgentConnecting]);\n\n // Filter agents based on search and tag (client-side filtering).\n // Status / platform filters are applied server-side via the GraphQL filter\n // input above; these client-side filters compose on top of that result set.\n const filteredAgents = useMemo(() => {\n return agents.filter((agent) => {\n // Search filter\n if (searchQuery) {\n const query = searchQuery.toLowerCase();\n const matchesSearch =\n agent.name.toLowerCase().includes(query) ||\n agent.hostname?.toLowerCase().includes(query) ||\n agent.id.toLowerCase().includes(query);\n if (!matchesSearch) return false;\n }\n\n // Tag filter — match against structured tagAssignments (UUIDs).\n // Never falls back to the legacy free-text agent.tags array because\n // those values are not the same as tagFilterIds and would silently\n // produce empty results (the failure mode that got the previous\n // TagFilter implementation removed in commit 4e101ca).\n if (tagFilter !== 'all') {\n const hasTag = (agent.tagAssignments ?? []).some(\n (a: { tagId: string }) => a.tagId === tagFilter\n );\n if (!hasTag) return false;\n }\n\n return true;\n });\n }, [agents, searchQuery, tagFilter]);\n\n useEffect(() => {\n return () => {\n if (toastTimerRef.current) clearTimeout(toastTimerRef.current);\n };\n }, []);\n\n const handleAgentSelect = (agent: Agent) => {\n // Navigate to agent details page\n handleNavigate(`/agents/${agent.id}`);\n // Also call the shell callback if provided\n onAgentSelect?.({\n id: agent.id,\n name: agent.name,\n type: agent.platform || 'unknown',\n status: agent.status || (agent.isActive ? 'ACTIVE' : 'OFFLINE'),\n });\n };\n\n const handleCreateAgent = () => {\n setShowCreateForm(true);\n };\n\n const handleCreateAgentSubmit = async (\n formData: Record<string, unknown>\n ): Promise<{ id: string } | void> => {\n try {\n setIsCreating(true);\n\n const tunnelUrl = (formData.tunnelUrl as string | undefined)?.trim();\n const agentApiKey = (formData.agentApiKey as string | undefined)?.trim();\n\n // Zero-config path: operator pasted the agent's tunnel URL + API key\n // shown in its startup banner. Backend probes the agent, registers it,\n // mints an OAuth app, and pushes credentials — the agent promotes\n // itself to `ready` inside that final POST /api/agent/gateway-auth.\n if (tunnelUrl && agentApiKey) {\n const result = await addAgentFromTunnel({\n variables: {\n input: {\n tunnelUrl,\n apiKey: agentApiKey,\n ...(formData.name ? { name: formData.name as string } : {}),\n ...(formData.targetId ? { targetId: formData.targetId as string } : {}),\n },\n },\n });\n if (result.error) throw result.error;\n const payload = result.data?.addVibecontrolsAgentFromTunnel;\n if (!payload?.success) {\n throw new Error(\n payload?.error || 'Failed to onboard agent — probe or OAuth setup failed.'\n );\n }\n if (payload.agent?.id) return { id: payload.agent.id };\n throw new Error('Agent onboarding returned no agent record.');\n }\n\n // Legacy manual path — operator typed hostname/platform/etc directly.\n const result = await createAgent({\n variables: {\n input: {\n name: formData.name as string,\n hostname: formData.hostname as string,\n platform: formData.platform as string,\n architecture: formData.architecture as string,\n version: formData.version as string,\n ...(formData.targetId ? { targetId: formData.targetId as string } : {}),\n ...(formData.tunnelUrl ? { tunnelUrl: formData.tunnelUrl as string } : {}),\n ...(formData.tunnelStatus ? { tunnelStatus: formData.tunnelStatus as string } : {}),\n },\n },\n });\n\n const createdAgent = result.data?.createVibecontrolsAgent;\n if (createdAgent?.id) return { id: createdAgent.id };\n\n if (result.error) {\n throw result.error;\n }\n\n throw new Error('Agent creation failed — no data returned from the server.');\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n setShowQuotaExhausted(true);\n setShowCreateForm(false);\n return;\n }\n\n console.error('Failed to create agent:', err);\n throw err;\n } finally {\n setIsCreating(false);\n }\n };\n\n const handleStartAgent = async (agent: Agent) => {\n try {\n const result = await startAgentMutation({ variables: { agentId: agent.id } });\n const payload = result.data?.startVibecontrolsAgent;\n\n if (!payload?.success) {\n throw new Error(payload?.error || 'Failed to start agent');\n }\n\n await agentHeartbeat({ variables: { id: agent.id } });\n showSuccess(`Agent \"${agent.name}\" started`);\n } catch (err) {\n console.error('Failed to start agent:', err);\n showError(err instanceof Error ? err.message : 'Failed to start agent');\n }\n };\n\n // NOTE: Stop and Disconnect both call deactivateVibecontrolsAgent and produce the same\n // server-side list state (isActive: false). The distinction is that Disconnect additionally\n // clears tunnel fields (tunnelUrl, tunnelStatus). When a dedicated stopVibecontrolsAgent\n // mutation succeeds, we still deactivate in workspace state so the list reflects the agent as offline.\n const handleStopAgent = async (agent: Agent) => {\n try {\n const result = await stopAgentMutation({ variables: { agentId: agent.id } });\n const payload = result.data?.stopVibecontrolsAgent;\n\n if (!payload?.success) {\n throw new Error(payload?.error || 'Failed to stop agent');\n }\n\n await deactivateAgent({ variables: { id: agent.id } });\n showSuccess(`Agent \"${agent.name}\" stopped`);\n } catch (err) {\n console.error('Failed to stop agent:', err);\n showError(err instanceof Error ? err.message : 'Failed to stop agent');\n }\n };\n\n const handleDisconnectAgent = (agent: Agent) => {\n setDisconnectConfirmAgent(agent);\n };\n\n const handleConfirmDisconnect = async () => {\n if (!disconnectConfirmAgent) return;\n const agent = disconnectConfirmAgent;\n setIsDisconnecting(true);\n // Deactivate + clear tunnel info so the agent appears fully disconnected.\n // Two sequential mutations with refetchQueries suppressed — a single refetch()\n // is called after both complete so the list doesn't flash with stale tunnel fields.\n // A single atomic disconnectVibecontrolsAgent backend mutation that handles\n // both deactivation and tunnel field clearing is tracked in vibecontrols-svc\n // backlog; until then the two-step approach below is the supported flow.\n try {\n await deactivateAgent({ variables: { id: agent.id }, refetchQueries: [] });\n } catch (err) {\n console.error('Failed to deactivate agent:', err);\n showError(err instanceof Error ? err.message : 'Failed to disconnect agent');\n setIsDisconnecting(false);\n setDisconnectConfirmAgent(null);\n // Early-return path: refetch here so the list reflects the deactivated state,\n // then return so the bottom refetch below does not also fire.\n await refetch();\n return;\n }\n try {\n await updateAgent({\n variables: {\n id: agent.id,\n input: { tunnelUrl: null, tunnelStatus: null, agentApiKey: null },\n },\n refetchQueries: [],\n });\n showSuccess(`Agent \"${agent.name}\" disconnected`);\n } catch (err) {\n console.error('Failed to clear tunnel fields:', err);\n showError(\n `Agent marked offline, but failed to clear connection fields — clear them manually in Settings.`\n );\n }\n setIsDisconnecting(false);\n setDisconnectConfirmAgent(null);\n // Both updateAgent success and updateAgent failure reach here — single refetch covers both.\n await refetch();\n };\n\n const handleConfigureAgent = (agent: Agent) => {\n // Navigate to agent details page (settings tab)\n handleNavigate(`/agents/${agent.id}`);\n };\n\n const handleEditTunnelUrl = (agent: Agent) => {\n setEditTunnelAgent(agent);\n };\n\n const handleSaveTunnelSettings = async () => {\n if (!editTunnelAgent) return;\n const agentId = editTunnelAgent.id;\n const newUrl = tunnelUrlRef.current?.value?.trim() || '';\n const newApiKey = tunnelApiKeyRef.current?.value?.trim() || '';\n try {\n setIsSavingTunnel(true);\n await updateAgent({\n variables: {\n id: agentId,\n input: {\n tunnelUrl: newUrl || null,\n tunnelStatus: newUrl ? 'ACTIVE' : null,\n agentApiKey: newApiKey || null,\n },\n },\n // Force a fresh probe immediately so the badge resolves to the\n // real state (instead of relying on the 30s subscription cadence).\n refetchQueries: ['ListAgents', 'VibecontrolsAgentHealthProbes'],\n });\n // Begin the \"Connecting…\" overlay BEFORE clearing the dialog so the\n // operator sees an unbroken progress signal: dialog spinner →\n // CONNECTING badge with spinner → ACTIVE / DEGRADED.\n if (newUrl) markAgentConnecting(agentId);\n setEditTunnelAgent(null);\n } catch (err) {\n console.error('Failed to update tunnel settings:', err);\n } finally {\n setIsSavingTunnel(false);\n }\n };\n\n // ── Bulk Selection ─────────────────────────────────────────────────\n const toggleAgentSelection = useCallback((agentId: string) => {\n setSelectedAgentIds((prev) => {\n const next = new Set(prev);\n if (next.has(agentId)) {\n next.delete(agentId);\n } else {\n next.add(agentId);\n }\n return next;\n });\n }, []);\n\n const selectAllAgents = useCallback(() => {\n setSelectedAgentIds(new Set(filteredAgents.map((a) => a.id)));\n }, [filteredAgents]);\n\n const deselectAllAgents = useCallback(() => {\n setSelectedAgentIds(new Set());\n }, []);\n\n const isAllSelected =\n filteredAgents.length > 0 && selectedAgentIds.size === filteredAgents.length;\n const hasSelection = selectedAgentIds.size > 0;\n\n // ── Bulk Delete ──────────────────────────────────────────────────\n const handleBulkDelete = useCallback(async () => {\n if (selectedAgentIds.size === 0) return;\n if (\n !window.confirm(\n tr(\n 'agentsPage.bulkDeleteConfirm',\n 'Are you sure you want to remove {{count}} agent(s)? This cannot be undone.',\n { count: selectedAgentIds.size }\n )\n )\n )\n return;\n\n setBulkLoading(true);\n try {\n const results = await Promise.allSettled(\n Array.from(selectedAgentIds).map((id) => deleteAgent({ variables: { id } }))\n );\n const succeeded = results.filter((r) => r.status === 'fulfilled').length;\n const failed = results.filter((r) => r.status === 'rejected').length;\n if (failed > 0) {\n showError(\n `Removed ${succeeded}, failed to remove ${failed} agent${failed !== 1 ? 's' : ''}`\n );\n } else {\n showSuccess(`Removed ${succeeded} agent${succeeded !== 1 ? 's' : ''}`);\n }\n setSelectedAgentIds(new Set());\n } catch (err) {\n showError(err instanceof Error ? err.message : 'Failed to remove agents');\n } finally {\n setBulkLoading(false);\n }\n }, [selectedAgentIds, deleteAgent, showError, showSuccess]);\n\n // Stats\n const stats = {\n total: agents.length,\n active: agents.filter((a) => a.isActive).length,\n offline: agents.filter((a) => !a.isActive).length,\n };\n\n // Guard states rendered inside MotionPage to prevent unmount/remount animation replay\n if (permissionsLoading) {\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <LoadingState message={tr('agentsPage.loadingPermissions', 'Loading permissions...')} />\n </MotionPage>\n );\n }\n\n if (permissions && permissions.permissionStrings?.length > 0 && !canListAgents) {\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <AccessDenied\n title={tr('agentsPage.accessDeniedTitle', 'Access Denied')}\n description={tr(\n 'agentsPage.accessDeniedDescription',\n \"You don't have permission to view agents. Contact your workspace administrator for access.\"\n )}\n />\n </MotionPage>\n );\n }\n\n if (loading && agents.length === 0) {\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <LoadingState message={tr('agentsPage.loadingAgents', 'Loading agents...')} />\n </MotionPage>\n );\n }\n\n if (error && agents.length === 0) {\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <ErrorState\n title={tr('agentsPage.failedLoad', 'Failed to load agents')}\n message={error.message}\n onRetry={() => refetch()}\n />\n </MotionPage>\n );\n }\n\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n {/* Toasts */}\n {successMessage && (\n <div className=\"fixed top-4 left-4 right-4 sm:left-auto z-50 bg-status-success-bg text-status-success-text px-4 py-3 rounded-md shadow-lg text-sm\">\n {successMessage}\n </div>\n )}\n {errorMessage && (\n <div className=\"fixed top-4 right-4 z-50 bg-status-error-bg text-status-error-text px-4 py-3 rounded-md shadow-lg text-sm\">\n {errorMessage}\n </div>\n )}\n\n <PageHeader\n title={tr('agentsPage.title', 'Agents')}\n description={tr('agentsPage.descriptionOnline', '{{active}} of {{total}} agents online', {\n active: stats.active,\n total: stats.total,\n })}\n actions={\n <div className=\"flex w-full flex-col items-end gap-2 sm:w-auto sm:flex-row sm:items-center\">\n <button\n onClick={() => refetch()}\n disabled={loading}\n className=\"flex items-center justify-center gap-2 px-3 py-2 text-text-secondary hover:bg-bg-sunken rounded-md transition-colors disabled:opacity-50\"\n title={tr('agentsPage.refreshTitle', 'Refresh agents')}\n >\n <RefreshCw className={`w-4 h-4 ${loading ? 'animate-spin' : ''}`} />\n </button>\n {hasTargets ? (\n <PermissionButton\n permission={VIBE_PERMISSIONS.AGENT_CREATE}\n deniedBehavior=\"disable\"\n deniedTooltip={tr(\n 'agentsPage.deniedCreateTooltip',\n \"You don't have permission to create agents\"\n )}\n >\n <button\n onClick={handleCreateAgent}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n <Plus className=\"w-4 h-4\" />\n {tr('agentsPage.addAgent', 'Add Agent')}\n </button>\n </PermissionButton>\n ) : (\n /* No targets yet — still open Add Agent; the form now\n supports inline target creation. */\n <button\n onClick={handleCreateAgent}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n <Plus className=\"w-4 h-4\" />\n {tr('agentsPage.addAgent', 'Add Agent')}\n </button>\n )}\n </div>\n }\n />\n\n {/* Agent CLI Setup Banner — shown when no agents exist */}\n {agents.length === 0 && !loading && (\n <AgentSetupBanner variant=\"full\" dismissKey=\"vibecontrols-agents-setup-banner\" />\n )}\n\n {/* Compact reminder when agents exist but shown once */}\n {agents.length > 0 && (\n <AgentSetupBanner variant=\"compact\" dismissKey=\"vibecontrols-agents-compact-banner\" />\n )}\n\n {/* Filters Bar */}\n <div className=\"space-y-3\">\n {/* Row 1: Search + View Toggle */}\n <div className=\"flex items-center gap-2\">\n <SearchInput\n value={searchQuery}\n onChange={setSearchQuery}\n placeholder={tr('agentsPage.searchPlaceholder', 'Search agents...')}\n className=\"flex-1 sm:w-64 sm:flex-none\"\n />\n {/* View Toggle */}\n <div className=\"flex items-center gap-1 bg-bg-sunken rounded-md p-1 flex-shrink-0\">\n <button\n onClick={() => {\n setViewMode('grid');\n setSelectedAgentIds(new Set());\n }}\n className={`p-1.5 sm:p-2 rounded ${viewMode === 'grid' ? 'bg-bg-surface shadow-sm' : 'hover:bg-bg-surface/50'}`}\n title={tr('agentsPage.gridViewTitle', 'Grid view')}\n >\n <Grid className=\"w-4 h-4 text-text-secondary\" />\n </button>\n <button\n onClick={() => {\n setViewMode('list');\n setSelectedAgentIds(new Set());\n }}\n className={`p-1.5 sm:p-2 rounded ${viewMode === 'list' ? 'bg-bg-surface shadow-sm' : 'hover:bg-bg-surface/50'}`}\n title={tr('agentsPage.listViewTitle', 'List view')}\n >\n <List className=\"w-4 h-4 text-text-secondary\" />\n </button>\n </div>\n </div>\n\n {/* Row 2: Filters - wrap on all screens */}\n <div className=\"flex items-center gap-2 sm:gap-3 flex-wrap\">\n <FilterDropdown\n label={tr('agentsPage.filterStatus', 'Status')}\n value={statusFilter}\n options={statusOptions}\n onChange={setStatusFilter}\n />\n <FilterDropdown\n label={tr('agentsPage.filterPlatform', 'Platform')}\n value={platformFilter}\n options={platformOptions}\n onChange={setPlatformFilter}\n />\n <TagFilter\n label={tr('agentsPage.filterTag', 'Tag')}\n value={tagFilter}\n onChange={setTagFilter}\n />\n </div>\n </div>\n\n {/* Select All & Bulk Actions */}\n {filteredAgents.length > 0 && (\n <div className=\"flex items-center gap-2 flex-wrap\">\n <div className=\"ml-auto flex items-center gap-2\">\n <button\n onClick={isAllSelected ? deselectAllAgents : selectAllAgents}\n className=\"flex items-center gap-1.5 px-3 py-1.5 text-xs text-text-secondary hover:text-text-primary transition-colors\"\n >\n <CheckSquare className=\"w-3.5 h-3.5\" />\n {isAllSelected\n ? tr('agentsPage.deselectAll', 'Deselect All')\n : tr('agentsPage.selectAll', 'Select All')}\n </button>\n </div>\n </div>\n )}\n\n {/* Bulk Action Bar */}\n {hasSelection && (\n <div className=\"flex flex-col sm:flex-row items-start sm:items-center gap-3 px-4 py-2 bg-action-primary-bg/10 border border-action-primary-bg/20 rounded-lg\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('agentsPage.selectedCount', '{{count}} agents selected', {\n count: selectedAgentIds.size,\n })}\n </span>\n <div className=\"flex items-center gap-1.5 sm:ml-auto\">\n <button\n onClick={handleBulkDelete}\n disabled={bulkLoading}\n className=\"flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-status-error-text bg-status-error-bg border border-status-error-text/20 rounded-md hover:bg-status-error-bg/80 transition-colors disabled:opacity-50\"\n >\n <Trash2 className=\"w-3 h-3\" /> {tr('agentsPage.remove', 'Remove')}\n </button>\n <button\n onClick={deselectAllAgents}\n className=\"flex items-center gap-1.5 px-3 py-1.5 text-xs text-text-secondary hover:text-text-primary transition-colors\"\n >\n <XSquare className=\"w-3 h-3\" /> {tr('agentsPage.deselect', 'Deselect')}\n </button>\n </div>\n </div>\n )}\n\n {/* Results Count */}\n {searchQuery || statusFilter !== 'all' || platformFilter !== 'all' ? (\n <p className=\"text-sm text-text-secondary\">\n {tr('agentsPage.showingResults', 'Showing {{filtered}} of {{total}} agents', {\n filtered: filteredAgents.length,\n total: agents.length,\n })}\n {loading && (\n <span className=\"ml-2 text-text-muted\">\n {tr('agentsPage.refreshing', '(refreshing...)')}\n </span>\n )}\n </p>\n ) : null}\n\n {/* Agents Display */}\n {filteredAgents.length > 0 ? (\n <AnimatePresence mode=\"wait\">\n {viewMode === 'grid' ? (\n <MotionList\n key=\"grid-view\"\n stagger={0.04}\n className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\"\n >\n {filteredAgents.map((agent) => (\n <MotionItem key={agent.id}>\n <AgentCard\n agent={agent}\n onSelect={handleAgentSelect}\n onStart={handleStartAgent}\n onStop={handleStopAgent}\n onDisconnect={handleDisconnectAgent}\n onConfigure={handleConfigureAgent}\n onEditTunnelUrl={handleEditTunnelUrl}\n selected={selectedAgentIds.has(agent.id)}\n onToggleSelect={() => toggleAgentSelection(agent.id)}\n healthErrorMessage={agent.healthError ?? null}\n />\n </MotionItem>\n ))}\n </MotionList>\n ) : (\n <MotionList key=\"list-view\" stagger={0.04}>\n <MotionItem variant=\"fadeRight\">\n {/* List view intentionally omits bulk-select props — checkboxes are\n only available in grid view. The bulk action toolbar is hidden\n when selectedAgentIds is empty, so this is safe. */}\n <AgentList\n agents={filteredAgents}\n onSelect={handleAgentSelect}\n onStart={handleStartAgent}\n onStop={handleStopAgent}\n onDisconnect={handleDisconnectAgent}\n onSettings={handleConfigureAgent}\n />\n </MotionItem>\n </MotionList>\n )}\n </AnimatePresence>\n ) : (\n <EmptyState\n icon={<Bot className=\"w-8 h-8 text-text-secondary\" />}\n title={\n searchQuery || statusFilter !== 'all' || platformFilter !== 'all'\n ? tr('agentsPage.emptyNoAgentsFound', 'No agents found')\n : tr('agentsPage.emptyNoAgentsConfigured', 'No agents configured')\n }\n description={\n searchQuery || statusFilter !== 'all' || platformFilter !== 'all'\n ? tr('agentsPage.emptyAdjustSearch', 'Try adjusting your search or filters')\n : tr(\n 'agentsPage.emptyNoAgentsDescription',\n 'Install the CLI agent on your machine, then add it here. You can create a target inline while adding the agent.'\n )\n }\n action={\n !searchQuery && statusFilter === 'all' && platformFilter === 'all' ? (\n <div className=\"flex flex-wrap items-center gap-3\">\n {/* Always open Add Agent — the form handles the \"no\n targets yet\" case via inline DIRECT target creation. */}\n <button\n onClick={handleCreateAgent}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n <Plus className=\"w-4 h-4\" />\n {tr('agentsPage.addAgent', 'Add Agent')}\n </button>\n <button\n onClick={() => handleNavigate('/getting-started')}\n className=\"flex items-center gap-2 px-4 py-2 border border-border-default text-text-primary rounded-md hover:bg-bg-sunken transition-colors text-sm\"\n >\n {tr('agentsPage.setupGuide', 'Setup guide')}\n </button>\n <GetHelpButton prompt=\"What is an Agent in VibeControls and how do I add one?\" />\n </div>\n ) : undefined\n }\n />\n )}\n\n {/* Create Agent Modal */}\n {showCreateForm && (\n <AgentForm\n onSubmit={handleCreateAgentSubmit}\n onCancel={() => setShowCreateForm(false)}\n isLoading={isCreating}\n onNavigate={(path) => {\n setShowCreateForm(false);\n handleNavigate(path);\n }}\n targets={targetsList.map(\n (t: { id: string; name: string; type?: string; status: string }) => ({\n id: t.id,\n name: t.name,\n type: t.type || 'DIRECT',\n status: t.status,\n })\n )}\n targetsLoading={targetsLoading}\n onCreateTarget={handleInlineCreateTarget}\n />\n )}\n\n {/* Disconnect Confirmation Modal */}\n <AnimatePresence>\n {disconnectConfirmAgent && (\n <div\n className=\"fixed inset-0 bg-bg-overlay flex items-center justify-center z-50 p-4 sm:p-6\"\n onClick={() => !isDisconnecting && setDisconnectConfirmAgent(null)}\n >\n <motion.div\n initial={{ opacity: 0, scale: 0.95 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.95 }}\n transition={{ duration: 0.15 }}\n className=\"bg-bg-surface border border-border-default rounded-lg shadow-xl w-full max-w-sm p-4 sm:p-6\"\n onClick={(e) => e.stopPropagation()}\n >\n <h3 className=\"text-lg font-semibold text-text-primary mb-1\">Disconnect Agent</h3>\n <p className=\"text-sm text-text-secondary mb-4\">\n {deriveAgentStatus(disconnectConfirmAgent) === 'OFFLINE' ? (\n <>\n Clear the stored tunnel connection for{' '}\n <strong>{disconnectConfirmAgent.name}</strong>? This will remove the tunnel URL\n and API key from this offline agent.\n </>\n ) : (\n <>\n Mark <strong>{disconnectConfirmAgent.name}</strong> as offline and clear its\n tunnel connection? The agent process will not be stopped automatically — you may\n need to stop it manually on the host machine.\n </>\n )}\n </p>\n <div className=\"flex justify-end gap-2\">\n <button\n onClick={() => setDisconnectConfirmAgent(null)}\n disabled={isDisconnecting}\n className=\"px-4 py-2 text-sm text-text-secondary hover:bg-bg-sunken rounded-md transition-colors disabled:opacity-50\"\n >\n Cancel\n </button>\n <button\n onClick={handleConfirmDisconnect}\n disabled={isDisconnecting}\n className=\"px-4 py-2 text-sm bg-action-danger-bg text-action-danger-text rounded-md hover:bg-action-danger-bg-hover transition-colors disabled:opacity-50\"\n >\n {isDisconnecting ? 'Disconnecting...' : 'Disconnect'}\n </button>\n </div>\n </motion.div>\n </div>\n )}\n </AnimatePresence>\n\n {/* Edit Tunnel Modal */}\n <AnimatePresence>\n {editTunnelAgent && (\n <div\n className=\"fixed inset-0 bg-bg-overlay flex items-center justify-center z-50 p-4 sm:p-6\"\n onClick={() => setEditTunnelAgent(null)}\n >\n <motion.div\n initial={{ opacity: 0, scale: 0.95 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.95 }}\n transition={{ duration: 0.15 }}\n className=\"bg-bg-surface border border-border-default rounded-lg shadow-xl w-full max-w-md p-4 sm:p-6\"\n onClick={(e) => e.stopPropagation()}\n >\n <h3 className=\"text-lg font-semibold text-text-primary mb-1\">\n {tr('agentsPage.editTunnelTitle', 'Edit Tunnel')}\n </h3>\n <p className=\"text-sm text-text-secondary mb-4\">\n {tr(\n 'agentsPage.editTunnelDescription',\n 'Configure the cloudflared tunnel URL and API key for {{name}}.',\n {\n name: editTunnelAgent.name,\n }\n )}\n </p>\n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('agentsPage.tunnelUrlLabel', 'Tunnel URL')}\n </label>\n <input\n ref={tunnelUrlRef}\n type=\"url\"\n defaultValue={editTunnelAgent.tunnelUrl || ''}\n placeholder={tr(\n 'agentsPage.tunnelUrlPlaceholder',\n 'https://example.trycloudflare.com'\n )}\n className=\"w-full px-3 py-2 border border-border-default rounded-md bg-bg-sunken text-text-primary placeholder:text-text-placeholder focus:outline-none focus:border-border-strong\"\n autoFocus\n />\n <p className=\"text-xs text-text-muted mt-1\">\n {tr(\n 'agentsPage.tunnelUrlHint',\n 'Leave empty to clear. Agents with auto-tunnel report this automatically on startup.'\n )}\n </p>\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('agentsPage.agentApiKeyLabel', 'Agent API Key')}\n </label>\n <input\n ref={tunnelApiKeyRef}\n type=\"text\"\n defaultValue={editTunnelAgent.agentApiKey || ''}\n placeholder={tr('agentsPage.agentApiKeyPlaceholder', 'sk-agent-...')}\n className=\"w-full px-3 py-2 border border-border-default rounded-md bg-bg-sunken text-text-primary font-mono text-sm placeholder:text-text-placeholder focus:outline-none focus:border-border-strong\"\n />\n <p className=\"text-xs text-text-muted mt-1\">\n {tr(\n 'agentsPage.agentApiKeyHint',\n 'Authenticates requests to the agent. Typically changes on each agent restart.'\n )}\n </p>\n </div>\n </div>\n <div className=\"flex justify-end gap-2 mt-5\">\n <button\n onClick={() => setEditTunnelAgent(null)}\n className=\"px-4 py-2 text-sm text-text-secondary hover:bg-bg-sunken rounded-md transition-colors\"\n >\n {tr('agentsPage.cancel', 'Cancel')}\n </button>\n <button\n onClick={handleSaveTunnelSettings}\n disabled={isSavingTunnel}\n className=\"px-4 py-2 text-sm bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50\"\n >\n {isSavingTunnel\n ? tr('agentsPage.saving', 'Saving...')\n : tr('agentsPage.save', 'Save')}\n </button>\n </div>\n </motion.div>\n </div>\n )}\n </AnimatePresence>\n\n {/* Quota Exhausted Dialog */}\n <QuotaExhaustedDialog\n isOpen={showQuotaExhausted}\n onClose={() => setShowQuotaExhausted(false)}\n onConfirm={() => {\n setShowQuotaExhausted(false);\n handleNavigate('/billing');\n }}\n title=\"Agent Quota Reached\"\n message=\"You've used all available agent slots on your current plan. Upgrade your plan to register more agents.\"\n />\n </MotionPage>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"AgentsPage.js","names":[],"sources":["../../src/pages/AgentsPage.tsx"],"sourcesContent":["import { useState, useMemo, useRef, useCallback, useEffect } from 'react';\nimport { Plus, Bot, Grid, List, RefreshCw, Trash2, CheckSquare, XSquare } from 'lucide-react';\nimport { MotionPage, MotionList, MotionItem } from '@burdenoff/fe-libs/shared/motion';\nimport { AnimatePresence, motion } from 'framer-motion';\nimport { AgentCard, AgentList, AgentForm } from '@/components/agents';\nimport type { Agent } from '@/components/agents';\nimport {\n PageHeader,\n SearchInput,\n FilterDropdown,\n TagFilter,\n EmptyState,\n LoadingState,\n ErrorState,\n AgentSetupBanner,\n QuotaExhaustedDialog,\n GetHelpButton,\n} from '@/components/shared';\nimport { useVibeControls, useVibeNavigate } from '@/providers/VibeControlsProvider';\nimport {\n useListAgentsQuery,\n useVibecontrolsAgentHealthProbesQuery,\n useVibecontrolsAgentHealthProbeStreamSubscription,\n useCreateAgentMutation,\n useUpdateAgentMutation,\n useDeleteAgentMutation,\n useAgentHeartbeatMutation,\n useDeactivateAgentMutation,\n useCreateTargetMutation,\n useAddAgentFromTunnelMutation,\n useStartVibecontrolsAgentMutation,\n useStopVibecontrolsAgentMutation,\n} from '@/generated/wspace-operations';\nimport { PermissionButton, AccessDenied } from '@burdenoff/fe-libs/shared/components';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { VIBE_PERMISSIONS } from '@/constants/permissions';\nimport { deriveAgentStatus, type AgentHealthProbe } from '@/utils/agentStatus';\nimport { isQuotaExhaustedError } from '@/utils/quotaUtils';\nimport { useCachedInitialData } from '@/hooks/useCachedInitialData';\nimport { useVibeControlsStore } from '@/store/vibeControlsStore';\n\ntype ViewMode = 'grid' | 'list';\n\n/**\n * Agents management page with filtering, search, and CRUD operations\n */\nexport function AgentsPage() {\n const { onAgentSelect, workspaceId } = useVibeControls();\n const handleNavigate = useVibeNavigate();\n const { hasPermission, isLoading: permissionsLoading, permissions } = usePermissions();\n const { t } = useI18n();\n\n const tr = useCallback(\n (key: string, fallback: string, params?: Record<string, string | number>): string => {\n const translated = t(key, params);\n const template = translated === key ? fallback : translated;\n\n if (!params) return template;\n\n return Object.entries(params).reduce(\n (acc, [paramKey, paramValue]) => acc.replace(`{{${paramKey}}}`, String(paramValue)),\n template\n );\n },\n [t]\n );\n\n const statusOptions = [\n { value: 'all', label: tr('agentsPage.statusAll', 'All Statuses') },\n { value: 'ACTIVE', label: tr('agentsPage.statusActive', 'Active') },\n { value: 'STOPPED', label: tr('agentsPage.statusStopped', 'Stopped') },\n { value: 'OFFLINE', label: tr('agentsPage.statusOffline', 'Offline') },\n ];\n\n const platformOptions = [\n { value: 'all', label: tr('agentsPage.platformAll', 'All Platforms') },\n { value: 'darwin', label: tr('agentsPage.platformMac', 'macOS') },\n { value: 'linux', label: tr('agentsPage.platformLinux', 'Linux') },\n { value: 'windows', label: tr('agentsPage.platformWindows', 'Windows') },\n ];\n\n // Check permissions\n const canListAgents = hasPermission(VIBE_PERMISSIONS.AGENT_LIST);\n\n const [searchQuery, setSearchQuery] = useState('');\n const [statusFilter, setStatusFilter] = useState('all');\n const [platformFilter, setPlatformFilter] = useState('all');\n const [tagFilter, setTagFilter] = useState('all');\n const [viewMode, setViewMode] = useState<ViewMode>('grid');\n const [showCreateForm, setShowCreateForm] = useState(false);\n const [isCreating, setIsCreating] = useState(false);\n const [editTunnelAgent, setEditTunnelAgent] = useState<Agent | null>(null);\n const [isSavingTunnel, setIsSavingTunnel] = useState(false);\n // Per-agent \"connecting\" overlay shown after Edit Tunnel save while the\n // backend pushes OAuth creds → agent finalize() → /health/ready flips to\n // ok. State lives in the global store (vibeControlsStore) so the overlay\n // survives navigation to AgentDetailsPage and back — the documented\n // recovery path navigates into the details page so pushAgentGatewayAuth\n // auto-recovery can fire, and storing the set in local React state used\n // to discard it on unmount, causing a CONNECTING → DEGRADED → ACTIVE\n // flicker on return.\n const connectingAgents = useVibeControlsStore((s) => s.connectingAgents);\n const markAgentConnecting = useVibeControlsStore((s) => s.markAgentConnecting);\n const clearAgentConnecting = useVibeControlsStore((s) => s.clearAgentConnecting);\n const connectingAgentIds = useMemo(\n () => new Set(Object.keys(connectingAgents)),\n [connectingAgents]\n );\n\n // 30s safety net — if a probe never reports healthy, drop the overlay\n // so the user eventually sees the real DEGRADED state.\n const connectingTimersRef = useRef<Map<string, ReturnType<typeof setTimeout>>>(new Map());\n useEffect(() => {\n const timers = connectingTimersRef.current;\n for (const agentId of Object.keys(connectingAgents)) {\n if (timers.has(agentId)) continue;\n const timer = setTimeout(() => {\n clearAgentConnecting(agentId);\n timers.delete(agentId);\n }, 30_000);\n timers.set(agentId, timer);\n }\n // Drop timers for agents no longer marked connecting.\n for (const [agentId, timer] of timers.entries()) {\n if (!(agentId in connectingAgents)) {\n clearTimeout(timer);\n timers.delete(agentId);\n }\n }\n }, [connectingAgents, clearAgentConnecting]);\n\n useEffect(() => {\n const timers = connectingTimersRef.current;\n return () => {\n for (const timer of timers.values()) clearTimeout(timer);\n timers.clear();\n };\n }, []);\n const [disconnectConfirmAgent, setDisconnectConfirmAgent] = useState<Agent | null>(null);\n const [isDisconnecting, setIsDisconnecting] = useState(false);\n const tunnelUrlRef = useRef<HTMLInputElement>(null);\n const tunnelApiKeyRef = useRef<HTMLInputElement>(null);\n\n // Bulk selection state\n const [selectedAgentIds, setSelectedAgentIds] = useState<Set<string>>(new Set());\n const [bulkLoading, setBulkLoading] = useState(false);\n const [successMessage, setSuccessMessage] = useState<string | null>(null);\n const [errorMessage, setErrorMessage] = useState<string | null>(null);\n const [showQuotaExhausted, setShowQuotaExhausted] = useState(false);\n const toastTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n useEffect(() => {\n return () => {\n if (toastTimerRef.current) clearTimeout(toastTimerRef.current);\n };\n }, []);\n\n const showSuccess = useCallback((msg: string) => {\n if (toastTimerRef.current) clearTimeout(toastTimerRef.current);\n setErrorMessage(null);\n setSuccessMessage(msg);\n toastTimerRef.current = setTimeout(() => setSuccessMessage(null), 3000);\n }, []);\n const showError = useCallback((msg: string) => {\n if (toastTimerRef.current) clearTimeout(toastTimerRef.current);\n setSuccessMessage(null);\n setErrorMessage(msg);\n toastTimerRef.current = setTimeout(() => setErrorMessage(null), 5000);\n }, []);\n\n const [createAgent] = useCreateAgentMutation({ refetchQueries: ['ListAgents'] });\n const [addAgentFromTunnel] = useAddAgentFromTunnelMutation({ refetchQueries: ['ListAgents'] });\n const [agentHeartbeat] = useAgentHeartbeatMutation({ refetchQueries: ['ListAgents'] });\n const [deactivateAgent] = useDeactivateAgentMutation({ refetchQueries: ['ListAgents'] });\n const [startAgentMutation] = useStartVibecontrolsAgentMutation({\n refetchQueries: ['ListAgents'],\n });\n const [stopAgentMutation] = useStopVibecontrolsAgentMutation({ refetchQueries: ['ListAgents'] });\n\n const {\n targets: cachedTargets,\n loading: initialDataLoading,\n refetch: refetchInitialData,\n } = useCachedInitialData();\n const targetsList = cachedTargets;\n const targetsLoading = initialDataLoading;\n const hasTargets = targetsList.length > 0;\n const [updateAgent] = useUpdateAgentMutation({ refetchQueries: ['ListAgents'] });\n const [deleteAgent] = useDeleteAgentMutation({ refetchQueries: ['ListAgents'] });\n // refetchQueries + awaitRefetchQueries: the inline create flow used to fire\n // refetchInitialData() without awaiting it, so AgentForm closed and\n // re-rendered against stale `cachedTargets` — the new target only appeared\n // on full page reload. Awaiting the underlying queries here makes the\n // mutation resolve only after the initial-data + targets caches are fresh.\n const [createTargetMutation] = useCreateTargetMutation({\n refetchQueries: ['VibeControlsInitialData', 'ListTargets'],\n awaitRefetchQueries: true,\n });\n\n /**\n * Inline DIRECT target creation callback for the AgentForm. Returns\n * the newly created target so the form can auto-select it.\n */\n const handleInlineCreateTarget = useCallback(\n async (targetName: string) => {\n if (!workspaceId || !targetName.trim()) return null;\n try {\n const { data: result } = await createTargetMutation({\n variables: {\n workspaceId,\n input: {\n type: 'DIRECT',\n name: targetName.trim(),\n },\n },\n });\n const created = result?.createTarget;\n if (!created) return null;\n refetchInitialData();\n return {\n id: created.id,\n name: created.name,\n type: created.type ?? 'DIRECT',\n status: created.status ?? 'unknown',\n };\n } catch {\n return null;\n }\n },\n [createTargetMutation, refetchInitialData, workspaceId]\n );\n\n // Fetch agents from GraphQL API\n // Skip until a workspace is selected: the resolver requires\n // x-workspace-id and throws \"Workspace context required\" when fired\n // before the auth provider has resolved a workspace (URL with ?org but\n // no &workspace, post-context-switcher self-heal, etc.). Without this\n // skip, the page lands on \"Failed to load agents\" and never recovers\n // because Apollo's errorPolicy: 'all' returns the error and doesn't\n // auto-refetch when workspaceId becomes available.\n const { data, loading, error, refetch } = useListAgentsQuery({\n variables: {\n filter: {\n isActive: statusFilter === 'ACTIVE' ? true : statusFilter === 'OFFLINE' ? false : undefined,\n platform: platformFilter !== 'all' ? platformFilter : undefined,\n },\n pagination: { limit: 100 },\n },\n fetchPolicy: 'cache-first',\n skip: !workspaceId,\n });\n\n // Live deep-health probe — backend hits /health/ready on every agent and\n // returns per-agent { healthy, errorMessage }. Polled every 30s so stale\n // tunnels (cloudflared dead, agent crashed but `isActive` still true)\n // flip the badge to DEGRADED before users hit \"create session\" against\n // an unreachable agent.\n // Initial query primes the cache; the subscription below replaces the\n // prior 30s pollInterval so badges flip to/from DEGRADED in <1s.\n const probeQuery = useVibecontrolsAgentHealthProbesQuery({\n fetchPolicy: 'cache-and-network',\n nextFetchPolicy: 'cache-first',\n notifyOnNetworkStatusChange: true,\n skip: !workspaceId,\n // Fallback polling: the stream subscription is the primary push channel\n // (sub-second updates) but it doesn't always reach the browser in local\n // dev — graphql-ws upgrade can be blocked by an SSE-only proxy, the\n // svc may have just restarted and dropped subscribers, etc. Without a\n // fallback the badge sticks at whatever the *initial* probe captured\n // (often DEGRADED while the agent's session-tmux plugin was still\n // loading) and only flips on a full page reload. 30s matches the\n // server-side periodic probe interval so we don't probe more often\n // than the backend re-evaluates.\n pollInterval: 30_000,\n });\n const { data: probeStreamData } = useVibecontrolsAgentHealthProbeStreamSubscription({\n skip: !workspaceId,\n });\n // True when no probe data has been delivered for this mount cycle yet.\n // Used by deriveAgentStatus to show REGISTERING for agents we have not\n // yet probed (instead of optimistically ACTIVE).\n const probesLoading = probeQuery.data === undefined && probeStreamData === undefined;\n const probesById = useMemo(() => {\n const m = new Map<string, AgentHealthProbe>();\n // Subscription stream wins over the initial query — strictly newer.\n const probes =\n probeStreamData?.vibecontrolsAgentHealthProbeStream ??\n probeQuery.data?.vibecontrolsAgentHealthProbes ??\n [];\n for (const p of probes) {\n m.set(p.agentId, {\n agentId: p.agentId,\n healthy: p.healthy,\n errorMessage: p.errorMessage,\n });\n }\n return m;\n }, [probeQuery.data, probeStreamData]);\n\n // Map GraphQL response to Agent type\n const agents: Agent[] = useMemo(() => {\n const rawAgents = data?.vibecontrolsAgents ?? [];\n return rawAgents.map((agent: NonNullable<typeof rawAgents>[number]) => ({\n id: agent.id,\n workspaceId: agent.workspaceId,\n name: agent.name,\n hostname: agent.hostname,\n platform: agent.platform,\n architecture: agent.architecture,\n version: agent.version,\n isActive: agent.isActive,\n status: connectingAgentIds.has(agent.id)\n ? ('CONNECTING' as const)\n : deriveAgentStatus(\n {\n id: agent.id,\n isActive: agent.isActive,\n metadata: agent.metadata as Record<string, unknown> | null,\n },\n probesById,\n { probesLoading }\n ),\n // Prefer the live health probe error. When the live probe didn't run\n // (or returned ok) but the agent's most-recent gateway-auth push\n // failed, surface that instead — otherwise the only signal of a\n // failed onboarding is a yellow Degraded badge with no hover text.\n // `pending` is hidden: it just means the async finalize is still\n // running and the badge already shows the right transient state.\n healthError:\n probesById.get(agent.id)?.errorMessage ??\n (agent.lastGatewayAuthPushStatus === 'failed'\n ? (agent.lastGatewayAuthPushError ?? 'Gateway-auth push failed')\n : null),\n lastHeartbeat: agent.lastHeartbeat || null,\n tunnelUrl: agent.tunnelUrl ?? null,\n tunnelStatus: agent.tunnelStatus ?? null,\n agentApiKey: agent.agentApiKey ?? null,\n configuration: (agent.configuration as Record<string, unknown>) || {},\n capabilities: Array.isArray(agent.capabilities)\n ? (agent.capabilities as string[])\n : typeof agent.capabilities === 'object' && agent.capabilities !== null\n ? (() => {\n const obj = agent.capabilities as Record<string, unknown>;\n const keys = Object.keys(obj);\n // When capabilities was serialized from an array, keys are numeric (\"0\",\"1\",\"2\")\n // In that case, extract values (the actual capability names) instead of keys\n const hasNumericKeys = keys.length > 0 && keys.every((k) => /^\\d+$/.test(k));\n return hasNumericKeys ? (Object.values(obj) as string[]) : (keys as string[]);\n })()\n : [],\n metadata: (agent.metadata as Record<string, unknown>) || {},\n createdAt: agent.createdAt,\n updatedAt: agent.updatedAt,\n tagAssignments: (agent.tagAssignments ?? []).map(\n (a: NonNullable<typeof agent.tagAssignments>[number]) => ({\n id: a.id,\n tagId: a.tagId,\n tag: a.tagInfo\n ? {\n id: a.tagInfo.id,\n key: a.tagInfo.key ?? null,\n label: a.tagInfo.label ?? null,\n color: a.tagInfo.color ?? null,\n }\n : null,\n })\n ),\n }));\n }, [data?.vibecontrolsAgents, probesById, probesLoading, connectingAgentIds]);\n\n // Clear CONNECTING overlay only when ALL of these hold:\n // 1. The live probe reports `healthy: true` (deep-health probe, which\n // already gates on session.ok + tunnel.ok + ttyd.ok server-side —\n // see probeAgentHealth in wspace-vibecontrols-svc).\n // 2. The grace-window timestamp stored at markAgentConnecting time has\n // elapsed. This defends against a STALE `healthy:true` probe\n // delivered from the Apollo cache during the in-flight refetch\n // that follows updateAgent — without the grace window, that stale\n // cache hit cleared CONNECTING immediately and the next (fresh,\n // transient) probe landing during cloudflared restart flipped the\n // badge to DEGRADED, producing the CONNECTING → DEGRADED → ACTIVE\n // flicker reported by users.\n //\n // Re-runs on `probesById` updates AND on a one-shot timer per pending\n // agent so the clear fires automatically once the grace window elapses\n // even if no new probe arrives.\n useEffect(() => {\n const pending = Object.keys(connectingAgents);\n if (pending.length === 0) return;\n const now = Date.now();\n let earliestPending = Infinity;\n for (const agentId of pending) {\n const minClearAt = connectingAgents[agentId];\n if (minClearAt === undefined) continue;\n const probe = probesById.get(agentId);\n if (probe?.healthy === true && now >= minClearAt) {\n clearAgentConnecting(agentId);\n } else if (minClearAt < earliestPending) {\n earliestPending = minClearAt;\n }\n }\n if (earliestPending === Infinity) return;\n const wait = Math.max(0, earliestPending - now) + 50;\n const tick = setTimeout(() => {\n // No-op state poke — the dependency on `connectingAgents` causes\n // this effect to re-run when state changes, but a healthy probe\n // arriving BEFORE the grace window elapses needs a re-evaluation\n // when the window finally closes. Re-marking with graceMs=0 is a\n // no-op when the entry is already past its window; if cleared in\n // the meantime, the early `pending.length === 0` short-circuit\n // makes this safe.\n const fresh = useVibeControlsStore.getState().connectingAgents;\n for (const agentId of Object.keys(fresh)) {\n const minClearAt = fresh[agentId];\n if (minClearAt === undefined) continue;\n const probe = probesById.get(agentId);\n if (probe?.healthy === true && Date.now() >= minClearAt) {\n clearAgentConnecting(agentId);\n }\n }\n }, wait);\n return () => clearTimeout(tick);\n }, [probesById, connectingAgents, clearAgentConnecting]);\n\n // Filter agents based on search and tag (client-side filtering).\n // Status / platform filters are applied server-side via the GraphQL filter\n // input above; these client-side filters compose on top of that result set.\n const filteredAgents = useMemo(() => {\n return agents.filter((agent) => {\n // Search filter\n if (searchQuery) {\n const query = searchQuery.toLowerCase();\n const matchesSearch =\n agent.name.toLowerCase().includes(query) ||\n agent.hostname?.toLowerCase().includes(query) ||\n agent.id.toLowerCase().includes(query);\n if (!matchesSearch) return false;\n }\n\n // Tag filter — match against structured tagAssignments (UUIDs).\n // Never falls back to the legacy free-text agent.tags array because\n // those values are not the same as tagFilterIds and would silently\n // produce empty results (the failure mode that got the previous\n // TagFilter implementation removed in commit 4e101ca).\n if (tagFilter !== 'all') {\n const hasTag = (agent.tagAssignments ?? []).some(\n (a: { tagId: string }) => a.tagId === tagFilter\n );\n if (!hasTag) return false;\n }\n\n return true;\n });\n }, [agents, searchQuery, tagFilter]);\n\n useEffect(() => {\n return () => {\n if (toastTimerRef.current) clearTimeout(toastTimerRef.current);\n };\n }, []);\n\n const handleAgentSelect = (agent: Agent) => {\n // Navigate to agent details page\n handleNavigate(`/agents/${agent.id}`);\n // Also call the shell callback if provided\n onAgentSelect?.({\n id: agent.id,\n name: agent.name,\n type: agent.platform || 'unknown',\n status: agent.status || (agent.isActive ? 'ACTIVE' : 'OFFLINE'),\n });\n };\n\n const handleCreateAgent = () => {\n setShowCreateForm(true);\n };\n\n const handleCreateAgentSubmit = async (\n formData: Record<string, unknown>\n ): Promise<{ id: string } | void> => {\n try {\n setIsCreating(true);\n\n const tunnelUrl = (formData.tunnelUrl as string | undefined)?.trim();\n const agentApiKey = (formData.agentApiKey as string | undefined)?.trim();\n\n // Zero-config path: operator pasted the agent's tunnel URL + API key\n // shown in its startup banner. Backend probes the agent, registers it,\n // mints an OAuth app, and pushes credentials — the agent promotes\n // itself to `ready` inside that final POST /api/agent/gateway-auth.\n if (tunnelUrl && agentApiKey) {\n const result = await addAgentFromTunnel({\n variables: {\n input: {\n tunnelUrl,\n apiKey: agentApiKey,\n ...(formData.name ? { name: formData.name as string } : {}),\n ...(formData.targetId ? { targetId: formData.targetId as string } : {}),\n },\n },\n });\n if (result.error) throw result.error;\n const payload = result.data?.addVibecontrolsAgentFromTunnel;\n if (!payload?.success) {\n throw new Error(\n payload?.error || 'Failed to onboard agent — probe or OAuth setup failed.'\n );\n }\n if (payload.agent?.id) return { id: payload.agent.id };\n throw new Error('Agent onboarding returned no agent record.');\n }\n\n // Legacy manual path — operator typed hostname/platform/etc directly.\n const result = await createAgent({\n variables: {\n input: {\n name: formData.name as string,\n hostname: formData.hostname as string,\n platform: formData.platform as string,\n architecture: formData.architecture as string,\n version: formData.version as string,\n ...(formData.targetId ? { targetId: formData.targetId as string } : {}),\n ...(formData.tunnelUrl ? { tunnelUrl: formData.tunnelUrl as string } : {}),\n ...(formData.tunnelStatus ? { tunnelStatus: formData.tunnelStatus as string } : {}),\n },\n },\n });\n\n const createdAgent = result.data?.createVibecontrolsAgent;\n if (createdAgent?.id) return { id: createdAgent.id };\n\n if (result.error) {\n throw result.error;\n }\n\n throw new Error('Agent creation failed — no data returned from the server.');\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n setShowQuotaExhausted(true);\n setShowCreateForm(false);\n return;\n }\n\n console.error('Failed to create agent:', err);\n throw err;\n } finally {\n setIsCreating(false);\n }\n };\n\n const handleStartAgent = async (agent: Agent) => {\n try {\n const result = await startAgentMutation({ variables: { agentId: agent.id } });\n const payload = result.data?.startVibecontrolsAgent;\n\n if (!payload?.success) {\n throw new Error(payload?.error || 'Failed to start agent');\n }\n\n await agentHeartbeat({ variables: { id: agent.id } });\n showSuccess(`Agent \"${agent.name}\" started`);\n } catch (err) {\n console.error('Failed to start agent:', err);\n showError(err instanceof Error ? err.message : 'Failed to start agent');\n }\n };\n\n // NOTE: Stop and Disconnect both call deactivateVibecontrolsAgent and produce the same\n // server-side list state (isActive: false). The distinction is that Disconnect additionally\n // clears tunnel fields (tunnelUrl, tunnelStatus). When a dedicated stopVibecontrolsAgent\n // mutation succeeds, we still deactivate in workspace state so the list reflects the agent as offline.\n const handleStopAgent = async (agent: Agent) => {\n try {\n const result = await stopAgentMutation({ variables: { agentId: agent.id } });\n const payload = result.data?.stopVibecontrolsAgent;\n\n if (!payload?.success) {\n throw new Error(payload?.error || 'Failed to stop agent');\n }\n\n await deactivateAgent({ variables: { id: agent.id } });\n showSuccess(`Agent \"${agent.name}\" stopped`);\n } catch (err) {\n console.error('Failed to stop agent:', err);\n showError(err instanceof Error ? err.message : 'Failed to stop agent');\n }\n };\n\n const handleDisconnectAgent = (agent: Agent) => {\n setDisconnectConfirmAgent(agent);\n };\n\n const handleConfirmDisconnect = async () => {\n if (!disconnectConfirmAgent) return;\n const agent = disconnectConfirmAgent;\n setIsDisconnecting(true);\n // Deactivate + clear tunnel info so the agent appears fully disconnected.\n // Two sequential mutations with refetchQueries suppressed — a single refetch()\n // is called after both complete so the list doesn't flash with stale tunnel fields.\n // A single atomic disconnectVibecontrolsAgent backend mutation that handles\n // both deactivation and tunnel field clearing is tracked in vibecontrols-svc\n // backlog; until then the two-step approach below is the supported flow.\n try {\n await deactivateAgent({ variables: { id: agent.id }, refetchQueries: [] });\n } catch (err) {\n console.error('Failed to deactivate agent:', err);\n showError(err instanceof Error ? err.message : 'Failed to disconnect agent');\n setIsDisconnecting(false);\n setDisconnectConfirmAgent(null);\n // Early-return path: refetch here so the list reflects the deactivated state,\n // then return so the bottom refetch below does not also fire.\n await refetch();\n return;\n }\n try {\n await updateAgent({\n variables: {\n id: agent.id,\n input: { tunnelUrl: null, tunnelStatus: null, agentApiKey: null },\n },\n refetchQueries: [],\n });\n showSuccess(`Agent \"${agent.name}\" disconnected`);\n } catch (err) {\n console.error('Failed to clear tunnel fields:', err);\n showError(\n `Agent marked offline, but failed to clear connection fields — clear them manually in Settings.`\n );\n }\n setIsDisconnecting(false);\n setDisconnectConfirmAgent(null);\n // Both updateAgent success and updateAgent failure reach here — single refetch covers both.\n await refetch();\n };\n\n const handleConfigureAgent = (agent: Agent) => {\n // Navigate to agent details page (settings tab)\n handleNavigate(`/agents/${agent.id}`);\n };\n\n const handleEditTunnelUrl = (agent: Agent) => {\n setEditTunnelAgent(agent);\n };\n\n const handleSaveTunnelSettings = async () => {\n if (!editTunnelAgent) return;\n const agentId = editTunnelAgent.id;\n const newUrl = tunnelUrlRef.current?.value?.trim() || '';\n const newApiKey = tunnelApiKeyRef.current?.value?.trim() || '';\n try {\n setIsSavingTunnel(true);\n await updateAgent({\n variables: {\n id: agentId,\n input: {\n tunnelUrl: newUrl || null,\n tunnelStatus: newUrl ? 'ACTIVE' : null,\n agentApiKey: newApiKey || null,\n },\n },\n // Force a fresh probe immediately so the badge resolves to the\n // real state (instead of relying on the 30s subscription cadence).\n refetchQueries: ['ListAgents', 'VibecontrolsAgentHealthProbes'],\n });\n // Begin the \"Connecting…\" overlay BEFORE clearing the dialog so the\n // operator sees an unbroken progress signal: dialog spinner →\n // CONNECTING badge with spinner → ACTIVE / DEGRADED.\n if (newUrl) markAgentConnecting(agentId);\n setEditTunnelAgent(null);\n } catch (err) {\n console.error('Failed to update tunnel settings:', err);\n } finally {\n setIsSavingTunnel(false);\n }\n };\n\n // ── Bulk Selection ─────────────────────────────────────────────────\n const toggleAgentSelection = useCallback((agentId: string) => {\n setSelectedAgentIds((prev) => {\n const next = new Set(prev);\n if (next.has(agentId)) {\n next.delete(agentId);\n } else {\n next.add(agentId);\n }\n return next;\n });\n }, []);\n\n const selectAllAgents = useCallback(() => {\n setSelectedAgentIds(new Set(filteredAgents.map((a) => a.id)));\n }, [filteredAgents]);\n\n const deselectAllAgents = useCallback(() => {\n setSelectedAgentIds(new Set());\n }, []);\n\n const isAllSelected =\n filteredAgents.length > 0 && selectedAgentIds.size === filteredAgents.length;\n const hasSelection = selectedAgentIds.size > 0;\n\n // ── Bulk Delete ──────────────────────────────────────────────────\n const handleBulkDelete = useCallback(async () => {\n if (selectedAgentIds.size === 0) return;\n if (\n !window.confirm(\n tr(\n 'agentsPage.bulkDeleteConfirm',\n 'Are you sure you want to remove {{count}} agent(s)? This cannot be undone.',\n { count: selectedAgentIds.size }\n )\n )\n )\n return;\n\n setBulkLoading(true);\n try {\n const results = await Promise.allSettled(\n Array.from(selectedAgentIds).map((id) => deleteAgent({ variables: { id } }))\n );\n const succeeded = results.filter((r) => r.status === 'fulfilled').length;\n const failed = results.filter((r) => r.status === 'rejected').length;\n if (failed > 0) {\n showError(\n `Removed ${succeeded}, failed to remove ${failed} agent${failed !== 1 ? 's' : ''}`\n );\n } else {\n showSuccess(`Removed ${succeeded} agent${succeeded !== 1 ? 's' : ''}`);\n }\n setSelectedAgentIds(new Set());\n } catch (err) {\n showError(err instanceof Error ? err.message : 'Failed to remove agents');\n } finally {\n setBulkLoading(false);\n }\n }, [selectedAgentIds, deleteAgent, showError, showSuccess]);\n\n // Stats\n const stats = {\n total: agents.length,\n active: agents.filter((a) => a.isActive).length,\n offline: agents.filter((a) => !a.isActive).length,\n };\n\n // Guard states rendered inside MotionPage to prevent unmount/remount animation replay\n if (permissionsLoading) {\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <LoadingState message={tr('agentsPage.loadingPermissions', 'Loading permissions...')} />\n </MotionPage>\n );\n }\n\n if (permissions && permissions.permissionStrings?.length > 0 && !canListAgents) {\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <AccessDenied\n title={tr('agentsPage.accessDeniedTitle', 'Access Denied')}\n description={tr(\n 'agentsPage.accessDeniedDescription',\n \"You don't have permission to view agents. Contact your workspace administrator for access.\"\n )}\n />\n </MotionPage>\n );\n }\n\n if (loading && agents.length === 0) {\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <LoadingState message={tr('agentsPage.loadingAgents', 'Loading agents...')} />\n </MotionPage>\n );\n }\n\n if (error && agents.length === 0) {\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <ErrorState\n title={tr('agentsPage.failedLoad', 'Failed to load agents')}\n message={error.message}\n onRetry={() => refetch()}\n />\n </MotionPage>\n );\n }\n\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n {/* Toasts */}\n {successMessage && (\n <div className=\"fixed top-4 left-4 right-4 sm:left-auto z-50 bg-status-success-bg text-status-success-text px-4 py-3 rounded-md shadow-lg text-sm\">\n {successMessage}\n </div>\n )}\n {errorMessage && (\n <div className=\"fixed top-4 right-4 z-50 bg-status-error-bg text-status-error-text px-4 py-3 rounded-md shadow-lg text-sm\">\n {errorMessage}\n </div>\n )}\n\n <PageHeader\n title={tr('agentsPage.title', 'Agents')}\n description={tr('agentsPage.descriptionOnline', '{{active}} of {{total}} agents online', {\n active: stats.active,\n total: stats.total,\n })}\n actions={\n <div className=\"flex w-full flex-col items-end gap-2 sm:w-auto sm:flex-row sm:items-center\">\n <button\n onClick={() => {\n // Refresh both the agent list AND the live deep-health\n // probe so the badge reflects current state, not a stale\n // initial probe captured while the agent was still\n // booting. Without re-probing here the user clicks the\n // refresh icon and the row's name/tunnel reload but the\n // DEGRADED badge stays stuck until the next 30s tick.\n refetch();\n probeQuery.refetch();\n }}\n disabled={loading || probeQuery.loading}\n className=\"flex items-center justify-center gap-2 px-3 py-2 text-text-secondary hover:bg-bg-sunken rounded-md transition-colors disabled:opacity-50\"\n title={tr('agentsPage.refreshTitle', 'Refresh agents')}\n >\n <RefreshCw\n className={`w-4 h-4 ${loading || probeQuery.loading ? 'animate-spin' : ''}`}\n />\n </button>\n {hasTargets ? (\n <PermissionButton\n permission={VIBE_PERMISSIONS.AGENT_CREATE}\n deniedBehavior=\"disable\"\n deniedTooltip={tr(\n 'agentsPage.deniedCreateTooltip',\n \"You don't have permission to create agents\"\n )}\n >\n <button\n onClick={handleCreateAgent}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n <Plus className=\"w-4 h-4\" />\n {tr('agentsPage.addAgent', 'Add Agent')}\n </button>\n </PermissionButton>\n ) : (\n /* No targets yet — still open Add Agent; the form now\n supports inline target creation. */\n <button\n onClick={handleCreateAgent}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n <Plus className=\"w-4 h-4\" />\n {tr('agentsPage.addAgent', 'Add Agent')}\n </button>\n )}\n </div>\n }\n />\n\n {/* Agent CLI Setup Banner — shown when no agents exist */}\n {agents.length === 0 && !loading && (\n <AgentSetupBanner variant=\"full\" dismissKey=\"vibecontrols-agents-setup-banner\" />\n )}\n\n {/* Compact reminder when agents exist but shown once */}\n {agents.length > 0 && (\n <AgentSetupBanner variant=\"compact\" dismissKey=\"vibecontrols-agents-compact-banner\" />\n )}\n\n {/* Filters Bar */}\n <div className=\"space-y-3\">\n {/* Row 1: Search + View Toggle */}\n <div className=\"flex items-center gap-2\">\n <SearchInput\n value={searchQuery}\n onChange={setSearchQuery}\n placeholder={tr('agentsPage.searchPlaceholder', 'Search agents...')}\n className=\"flex-1 sm:w-64 sm:flex-none\"\n />\n {/* View Toggle */}\n <div className=\"flex items-center gap-1 bg-bg-sunken rounded-md p-1 flex-shrink-0\">\n <button\n onClick={() => {\n setViewMode('grid');\n setSelectedAgentIds(new Set());\n }}\n className={`p-1.5 sm:p-2 rounded ${viewMode === 'grid' ? 'bg-bg-surface shadow-sm' : 'hover:bg-bg-surface/50'}`}\n title={tr('agentsPage.gridViewTitle', 'Grid view')}\n >\n <Grid className=\"w-4 h-4 text-text-secondary\" />\n </button>\n <button\n onClick={() => {\n setViewMode('list');\n setSelectedAgentIds(new Set());\n }}\n className={`p-1.5 sm:p-2 rounded ${viewMode === 'list' ? 'bg-bg-surface shadow-sm' : 'hover:bg-bg-surface/50'}`}\n title={tr('agentsPage.listViewTitle', 'List view')}\n >\n <List className=\"w-4 h-4 text-text-secondary\" />\n </button>\n </div>\n </div>\n\n {/* Row 2: Filters - wrap on all screens */}\n <div className=\"flex items-center gap-2 sm:gap-3 flex-wrap\">\n <FilterDropdown\n label={tr('agentsPage.filterStatus', 'Status')}\n value={statusFilter}\n options={statusOptions}\n onChange={setStatusFilter}\n />\n <FilterDropdown\n label={tr('agentsPage.filterPlatform', 'Platform')}\n value={platformFilter}\n options={platformOptions}\n onChange={setPlatformFilter}\n />\n <TagFilter\n label={tr('agentsPage.filterTag', 'Tag')}\n value={tagFilter}\n onChange={setTagFilter}\n />\n </div>\n </div>\n\n {/* Select All & Bulk Actions */}\n {filteredAgents.length > 0 && (\n <div className=\"flex items-center gap-2 flex-wrap\">\n <div className=\"ml-auto flex items-center gap-2\">\n <button\n onClick={isAllSelected ? deselectAllAgents : selectAllAgents}\n className=\"flex items-center gap-1.5 px-3 py-1.5 text-xs text-text-secondary hover:text-text-primary transition-colors\"\n >\n <CheckSquare className=\"w-3.5 h-3.5\" />\n {isAllSelected\n ? tr('agentsPage.deselectAll', 'Deselect All')\n : tr('agentsPage.selectAll', 'Select All')}\n </button>\n </div>\n </div>\n )}\n\n {/* Bulk Action Bar */}\n {hasSelection && (\n <div className=\"flex flex-col sm:flex-row items-start sm:items-center gap-3 px-4 py-2 bg-action-primary-bg/10 border border-action-primary-bg/20 rounded-lg\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('agentsPage.selectedCount', '{{count}} agents selected', {\n count: selectedAgentIds.size,\n })}\n </span>\n <div className=\"flex items-center gap-1.5 sm:ml-auto\">\n <button\n onClick={handleBulkDelete}\n disabled={bulkLoading}\n className=\"flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-status-error-text bg-status-error-bg border border-status-error-text/20 rounded-md hover:bg-status-error-bg/80 transition-colors disabled:opacity-50\"\n >\n <Trash2 className=\"w-3 h-3\" /> {tr('agentsPage.remove', 'Remove')}\n </button>\n <button\n onClick={deselectAllAgents}\n className=\"flex items-center gap-1.5 px-3 py-1.5 text-xs text-text-secondary hover:text-text-primary transition-colors\"\n >\n <XSquare className=\"w-3 h-3\" /> {tr('agentsPage.deselect', 'Deselect')}\n </button>\n </div>\n </div>\n )}\n\n {/* Results Count */}\n {searchQuery || statusFilter !== 'all' || platformFilter !== 'all' ? (\n <p className=\"text-sm text-text-secondary\">\n {tr('agentsPage.showingResults', 'Showing {{filtered}} of {{total}} agents', {\n filtered: filteredAgents.length,\n total: agents.length,\n })}\n {loading && (\n <span className=\"ml-2 text-text-muted\">\n {tr('agentsPage.refreshing', '(refreshing...)')}\n </span>\n )}\n </p>\n ) : null}\n\n {/* Agents Display */}\n {filteredAgents.length > 0 ? (\n <AnimatePresence mode=\"wait\">\n {viewMode === 'grid' ? (\n <MotionList\n key=\"grid-view\"\n stagger={0.04}\n className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\"\n >\n {filteredAgents.map((agent) => (\n <MotionItem key={agent.id}>\n <AgentCard\n agent={agent}\n onSelect={handleAgentSelect}\n onStart={handleStartAgent}\n onStop={handleStopAgent}\n onDisconnect={handleDisconnectAgent}\n onConfigure={handleConfigureAgent}\n onEditTunnelUrl={handleEditTunnelUrl}\n selected={selectedAgentIds.has(agent.id)}\n onToggleSelect={() => toggleAgentSelection(agent.id)}\n healthErrorMessage={agent.healthError ?? null}\n />\n </MotionItem>\n ))}\n </MotionList>\n ) : (\n <MotionList key=\"list-view\" stagger={0.04}>\n <MotionItem variant=\"fadeRight\">\n {/* List view intentionally omits bulk-select props — checkboxes are\n only available in grid view. The bulk action toolbar is hidden\n when selectedAgentIds is empty, so this is safe. */}\n <AgentList\n agents={filteredAgents}\n onSelect={handleAgentSelect}\n onStart={handleStartAgent}\n onStop={handleStopAgent}\n onDisconnect={handleDisconnectAgent}\n onSettings={handleConfigureAgent}\n />\n </MotionItem>\n </MotionList>\n )}\n </AnimatePresence>\n ) : (\n <EmptyState\n icon={<Bot className=\"w-8 h-8 text-text-secondary\" />}\n title={\n searchQuery || statusFilter !== 'all' || platformFilter !== 'all'\n ? tr('agentsPage.emptyNoAgentsFound', 'No agents found')\n : tr('agentsPage.emptyNoAgentsConfigured', 'No agents configured')\n }\n description={\n searchQuery || statusFilter !== 'all' || platformFilter !== 'all'\n ? tr('agentsPage.emptyAdjustSearch', 'Try adjusting your search or filters')\n : tr(\n 'agentsPage.emptyNoAgentsDescription',\n 'Install the CLI agent on your machine, then add it here. You can create a target inline while adding the agent.'\n )\n }\n action={\n !searchQuery && statusFilter === 'all' && platformFilter === 'all' ? (\n <div className=\"flex flex-wrap items-center gap-3\">\n {/* Always open Add Agent — the form handles the \"no\n targets yet\" case via inline DIRECT target creation. */}\n <button\n onClick={handleCreateAgent}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n <Plus className=\"w-4 h-4\" />\n {tr('agentsPage.addAgent', 'Add Agent')}\n </button>\n <button\n onClick={() => handleNavigate('/getting-started')}\n className=\"flex items-center gap-2 px-4 py-2 border border-border-default text-text-primary rounded-md hover:bg-bg-sunken transition-colors text-sm\"\n >\n {tr('agentsPage.setupGuide', 'Setup guide')}\n </button>\n <GetHelpButton prompt=\"What is an Agent in VibeControls and how do I add one?\" />\n </div>\n ) : undefined\n }\n />\n )}\n\n {/* Create Agent Modal */}\n {showCreateForm && (\n <AgentForm\n onSubmit={handleCreateAgentSubmit}\n onCancel={() => setShowCreateForm(false)}\n isLoading={isCreating}\n onNavigate={(path) => {\n setShowCreateForm(false);\n handleNavigate(path);\n }}\n targets={targetsList.map(\n (t: { id: string; name: string; type?: string; status: string }) => ({\n id: t.id,\n name: t.name,\n type: t.type || 'DIRECT',\n status: t.status,\n })\n )}\n targetsLoading={targetsLoading}\n onCreateTarget={handleInlineCreateTarget}\n />\n )}\n\n {/* Disconnect Confirmation Modal */}\n <AnimatePresence>\n {disconnectConfirmAgent && (\n <div\n className=\"fixed inset-0 bg-bg-overlay flex items-center justify-center z-50 p-4 sm:p-6\"\n onClick={() => !isDisconnecting && setDisconnectConfirmAgent(null)}\n >\n <motion.div\n initial={{ opacity: 0, scale: 0.95 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.95 }}\n transition={{ duration: 0.15 }}\n className=\"bg-bg-surface border border-border-default rounded-lg shadow-xl w-full max-w-sm p-4 sm:p-6\"\n onClick={(e) => e.stopPropagation()}\n >\n <h3 className=\"text-lg font-semibold text-text-primary mb-1\">Disconnect Agent</h3>\n <p className=\"text-sm text-text-secondary mb-4\">\n {deriveAgentStatus(disconnectConfirmAgent) === 'OFFLINE' ? (\n <>\n Clear the stored tunnel connection for{' '}\n <strong>{disconnectConfirmAgent.name}</strong>? This will remove the tunnel URL\n and API key from this offline agent.\n </>\n ) : (\n <>\n Mark <strong>{disconnectConfirmAgent.name}</strong> as offline and clear its\n tunnel connection? The agent process will not be stopped automatically — you may\n need to stop it manually on the host machine.\n </>\n )}\n </p>\n <div className=\"flex justify-end gap-2\">\n <button\n onClick={() => setDisconnectConfirmAgent(null)}\n disabled={isDisconnecting}\n className=\"px-4 py-2 text-sm text-text-secondary hover:bg-bg-sunken rounded-md transition-colors disabled:opacity-50\"\n >\n Cancel\n </button>\n <button\n onClick={handleConfirmDisconnect}\n disabled={isDisconnecting}\n className=\"px-4 py-2 text-sm bg-action-danger-bg text-action-danger-text rounded-md hover:bg-action-danger-bg-hover transition-colors disabled:opacity-50\"\n >\n {isDisconnecting ? 'Disconnecting...' : 'Disconnect'}\n </button>\n </div>\n </motion.div>\n </div>\n )}\n </AnimatePresence>\n\n {/* Edit Tunnel Modal */}\n <AnimatePresence>\n {editTunnelAgent && (\n <div\n className=\"fixed inset-0 bg-bg-overlay flex items-center justify-center z-50 p-4 sm:p-6\"\n onClick={() => setEditTunnelAgent(null)}\n >\n <motion.div\n initial={{ opacity: 0, scale: 0.95 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.95 }}\n transition={{ duration: 0.15 }}\n className=\"bg-bg-surface border border-border-default rounded-lg shadow-xl w-full max-w-md p-4 sm:p-6\"\n onClick={(e) => e.stopPropagation()}\n >\n <h3 className=\"text-lg font-semibold text-text-primary mb-1\">\n {tr('agentsPage.editTunnelTitle', 'Edit Tunnel')}\n </h3>\n <p className=\"text-sm text-text-secondary mb-4\">\n {tr(\n 'agentsPage.editTunnelDescription',\n 'Configure the cloudflared tunnel URL and API key for {{name}}.',\n {\n name: editTunnelAgent.name,\n }\n )}\n </p>\n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('agentsPage.tunnelUrlLabel', 'Tunnel URL')}\n </label>\n <input\n ref={tunnelUrlRef}\n type=\"url\"\n defaultValue={editTunnelAgent.tunnelUrl || ''}\n placeholder={tr(\n 'agentsPage.tunnelUrlPlaceholder',\n 'https://example.trycloudflare.com'\n )}\n className=\"w-full px-3 py-2 border border-border-default rounded-md bg-bg-sunken text-text-primary placeholder:text-text-placeholder focus:outline-none focus:border-border-strong\"\n autoFocus\n />\n <p className=\"text-xs text-text-muted mt-1\">\n {tr(\n 'agentsPage.tunnelUrlHint',\n 'Leave empty to clear. Agents with auto-tunnel report this automatically on startup.'\n )}\n </p>\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('agentsPage.agentApiKeyLabel', 'Agent API Key')}\n </label>\n <input\n ref={tunnelApiKeyRef}\n type=\"text\"\n defaultValue={editTunnelAgent.agentApiKey || ''}\n placeholder={tr('agentsPage.agentApiKeyPlaceholder', 'sk-agent-...')}\n className=\"w-full px-3 py-2 border border-border-default rounded-md bg-bg-sunken text-text-primary font-mono text-sm placeholder:text-text-placeholder focus:outline-none focus:border-border-strong\"\n />\n <p className=\"text-xs text-text-muted mt-1\">\n {tr(\n 'agentsPage.agentApiKeyHint',\n 'Authenticates requests to the agent. Typically changes on each agent restart.'\n )}\n </p>\n </div>\n </div>\n <div className=\"flex justify-end gap-2 mt-5\">\n <button\n onClick={() => setEditTunnelAgent(null)}\n className=\"px-4 py-2 text-sm text-text-secondary hover:bg-bg-sunken rounded-md transition-colors\"\n >\n {tr('agentsPage.cancel', 'Cancel')}\n </button>\n <button\n onClick={handleSaveTunnelSettings}\n disabled={isSavingTunnel}\n className=\"px-4 py-2 text-sm bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50\"\n >\n {isSavingTunnel\n ? tr('agentsPage.saving', 'Saving...')\n : tr('agentsPage.save', 'Save')}\n </button>\n </div>\n </motion.div>\n </div>\n )}\n </AnimatePresence>\n\n {/* Quota Exhausted Dialog */}\n <QuotaExhaustedDialog\n isOpen={showQuotaExhausted}\n onClose={() => setShowQuotaExhausted(false)}\n onConfirm={() => {\n setShowQuotaExhausted(false);\n handleNavigate('/billing');\n }}\n title=\"Agent Quota Reached\"\n message=\"You've used all available agent slots on your current plan. Upgrade your plan to register more agents.\"\n />\n </MotionPage>\n );\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-vibecontrols",
3
- "version": "2026.519.1",
3
+ "version": "2026.519.3",
4
4
  "description": "VibeControls microfrontend for Burdenoff products",
5
5
  "type": "module",
6
6
  "files": [