88 MessageSquare ,
99 PhoneCall ,
1010 RefreshCw ,
11+ Search ,
1112 Send ,
1213 TestTube2 ,
1314 Video ,
@@ -118,9 +119,22 @@ function metadataSource(agent: RegisteredAgent): string {
118119 return typeof source === "string" ? source : "" ;
119120}
120121
122+ function metadataText ( agent : RegisteredAgent ) : string {
123+ try {
124+ return JSON . stringify ( agent . metadata ?? { } ) . toLowerCase ( ) ;
125+ } catch {
126+ return "" ;
127+ }
128+ }
129+
121130function isPaperclipAgent ( agent : RegisteredAgent ) : boolean {
122- const haystack = `${ agent . id } ${ agent . name } ${ agent . role } ${ metadataSource ( agent ) } ` . toLowerCase ( ) ;
123- return haystack . includes ( "paperclip" ) ;
131+ const haystack = `${ agent . id } ${ agent . name } ${ agent . role } ${ metadataSource ( agent ) } ${ metadataText ( agent ) } ` . toLowerCase ( ) ;
132+ return (
133+ haystack . includes ( "paperclip" ) ||
134+ haystack . includes ( "pmo-agent" ) ||
135+ haystack . includes ( "pmo_agents" ) ||
136+ haystack . includes ( "pmo agents" )
137+ ) ;
124138}
125139
126140function isPrimaryAgent ( agent : RegisteredAgent ) : boolean {
@@ -231,20 +245,13 @@ export function AgentEngagementConsole() {
231245 const { data : agentsData , isLoading : agentsLoading } = useAgents ( ) ;
232246 const { data : delegationsData } = useDelegations ( 8 ) ;
233247 const agents = useMemo ( ( ) => ( agentsData ?. agents ?? [ ] ) as RegisteredAgent [ ] , [ agentsData ?. agents ] ) ;
234- const rosterAgents = useMemo ( ( ) => agents . filter ( ( agent ) => ! isPaperclipAgent ( agent ) ) , [ agents ] ) ;
235- const primaryAgents = useMemo ( ( ) => rosterAgents . filter ( ( agent ) => agentGroup ( agent ) === "primary" ) . sort ( sortAgents ) , [ rosterAgents ] ) ;
236- const directoryAgents = useMemo ( ( ) => rosterAgents . filter ( ( agent ) => agentGroup ( agent ) === "directory" ) . sort ( sortAgents ) , [ rosterAgents ] ) ;
237- const activeAgents = useMemo ( ( ) => primaryAgents . filter ( ( agent ) => agent . status === "active" ) , [ primaryAgents ] ) ;
238- const roster = useMemo (
239- ( ) => [ ...primaryAgents , ...directoryAgents ] ,
240- [ directoryAgents , primaryAgents ]
241- ) ;
242- const defaultRoomIds = activeAgents . length > 0 ? activeAgents . map ( ( agent ) => agent . id ) : roster . map ( ( agent ) => agent . id ) ;
243- const defaultAgentId = activeAgents [ 0 ] ?. id ?? roster [ 0 ] ?. id ?? "" ;
244248
245249 const [ mode , setMode ] = useState < Mode > ( "chat" ) ;
246250 const [ selectedAgentId , setSelectedAgentId ] = useState < string | null > ( null ) ;
247251 const [ participants , setParticipants ] = useState < string [ ] | null > ( null ) ;
252+ const [ showSupportAgents , setShowSupportAgents ] = useState ( false ) ;
253+ const [ rosterQuery , setRosterQuery ] = useState ( "" ) ;
254+ const [ statusFilter , setStatusFilter ] = useState < RegisteredAgent [ "status" ] | "all" > ( "all" ) ;
248255 const [ message , setMessage ] = useState ( "" ) ;
249256 const [ standupFocus , setStandupFocus ] = useState ( "" ) ;
250257 const [ standupBlockers , setStandupBlockers ] = useState ( "" ) ;
@@ -260,6 +267,28 @@ export function AgentEngagementConsole() {
260267 const audioRef = useRef < HTMLAudioElement | null > ( null ) ;
261268 const recognitionRef = useRef < SpeechRecognitionLike | null > ( null ) ;
262269
270+ const supportAgents = useMemo ( ( ) => agents . filter ( isPaperclipAgent ) , [ agents ] ) ;
271+ const rosterAgents = useMemo ( ( ) => {
272+ const query = rosterQuery . trim ( ) . toLowerCase ( ) ;
273+ return agents
274+ . filter ( ( agent ) => showSupportAgents || ! isPaperclipAgent ( agent ) )
275+ . filter ( ( agent ) => statusFilter === "all" || agent . status === statusFilter )
276+ . filter ( ( agent ) => {
277+ if ( ! query ) return true ;
278+ return `${ agent . id } ${ agent . name } ${ agent . role } ${ agent . platform } ${ metadataSource ( agent ) } ` . toLowerCase ( ) . includes ( query ) ;
279+ } ) ;
280+ } , [ agents , rosterQuery , showSupportAgents , statusFilter ] ) ;
281+ const primaryAgents = useMemo ( ( ) => rosterAgents . filter ( ( agent ) => agentGroup ( agent ) === "primary" && ! isPaperclipAgent ( agent ) ) . sort ( sortAgents ) , [ rosterAgents ] ) ;
282+ const directoryAgents = useMemo ( ( ) => rosterAgents . filter ( ( agent ) => agentGroup ( agent ) === "directory" && ! isPaperclipAgent ( agent ) ) . sort ( sortAgents ) , [ rosterAgents ] ) ;
283+ const supportRosterAgents = useMemo ( ( ) => rosterAgents . filter ( isPaperclipAgent ) . sort ( sortAgents ) , [ rosterAgents ] ) ;
284+ const activeAgents = useMemo ( ( ) => primaryAgents . filter ( ( agent ) => agent . status === "active" ) , [ primaryAgents ] ) ;
285+ const roster = useMemo (
286+ ( ) => [ ...primaryAgents , ...directoryAgents , ...supportRosterAgents ] ,
287+ [ directoryAgents , primaryAgents , supportRosterAgents ]
288+ ) ;
289+ const defaultRoomIds = activeAgents . length > 0 ? activeAgents . map ( ( agent ) => agent . id ) : roster . map ( ( agent ) => agent . id ) ;
290+ const defaultAgentId = activeAgents [ 0 ] ?. id ?? roster [ 0 ] ?. id ?? "" ;
291+
263292 const selectedAgent = useMemo (
264293 ( ) => roster . find ( ( agent ) => agent . id === ( selectedAgentId || defaultAgentId ) ) ?? roster [ 0 ] ,
265294 [ defaultAgentId , roster , selectedAgentId ]
@@ -617,22 +646,73 @@ export function AgentEngagementConsole() {
617646 < div className = "mb-3 flex items-center justify-between" >
618647 < h2 className = "flex items-center text-sm font-semibold text-slate-950" >
619648 Agent roster
620- < InfoTip text = "Primary working agents are shown first. Paperclip support agents are excluded from this engagement roster and belong in the workflow map." />
649+ < InfoTip text = "Primary working agents are shown first. Paperclip and PMO support agents are hidden by default and belong in the workflow map unless you explicitly show system agents ." />
621650 </ h2 >
622651 < Pill value = { `${ activeAgents . length } active / ${ roster . length } registered` } />
623652 </ div >
653+ < div className = "mb-3 space-y-2" >
654+ < label className = "flex items-center gap-2 rounded-md border border-slate-200 bg-white px-2 py-1.5 text-xs text-stone-600" >
655+ < Search className = "h-3.5 w-3.5 flex-shrink-0 text-stone-400" />
656+ < input
657+ type = "search"
658+ value = { rosterQuery }
659+ onChange = { ( event ) => setRosterQuery ( event . target . value ) }
660+ placeholder = "Filter agents..."
661+ className = "min-w-0 flex-1 bg-transparent text-sm outline-none placeholder:text-stone-400"
662+ aria-label = "Filter agents"
663+ />
664+ </ label >
665+ < div className = "grid grid-cols-[1fr_auto] gap-2" >
666+ < select
667+ value = { statusFilter }
668+ onChange = { ( event ) => setStatusFilter ( event . target . value as RegisteredAgent [ "status" ] | "all" ) }
669+ className = "h-8 rounded-md border border-slate-200 bg-white px-2 text-xs font-semibold text-stone-600 outline-none"
670+ aria-label = "Filter by agent status"
671+ >
672+ < option value = "all" > All statuses</ option >
673+ < option value = "active" > Active</ option >
674+ < option value = "idle" > Idle</ option >
675+ < option value = "dormant" > Dormant</ option >
676+ < option value = "error" > Error</ option >
677+ </ select >
678+ < button
679+ type = "button"
680+ onClick = { ( ) => setShowSupportAgents ( ( value ) => ! value ) }
681+ className = { `h-8 rounded-md border px-2 text-xs font-semibold transition ${
682+ showSupportAgents
683+ ? "border-amber-300 bg-amber-50 text-amber-800"
684+ : "border-slate-200 bg-white text-stone-600 hover:bg-slate-50"
685+ } `}
686+ >
687+ { showSupportAgents ? "Hide system" : `Show system (${ supportAgents . length } )` }
688+ </ button >
689+ </ div >
690+ </ div >
624691 { agentsLoading && < p className = "text-sm text-stone-500" > Loading agents...</ p > }
625692 < div className = "max-h-[34rem] space-y-2 overflow-y-auto pr-1" >
626- < div className = "space-y-2" >
627- < p className = "px-1 text-[11px] font-semibold uppercase tracking-[0.12em] text-stone-500" > Primary agents</ p >
628- { primaryAgents . map ( renderAgentCard ) }
629- </ div >
693+ { primaryAgents . length > 0 && (
694+ < div className = "space-y-2" >
695+ < p className = "px-1 text-[11px] font-semibold uppercase tracking-[0.12em] text-stone-500" > Primary agents</ p >
696+ { primaryAgents . map ( renderAgentCard ) }
697+ </ div >
698+ ) }
630699 { directoryAgents . length > 0 && (
631700 < div className = "space-y-2 pt-2" >
632701 < p className = "px-1 text-[11px] font-semibold uppercase tracking-[0.12em] text-stone-500" > Registered directory</ p >
633702 { directoryAgents . map ( renderAgentCard ) }
634703 </ div >
635704 ) }
705+ { showSupportAgents && supportRosterAgents . length > 0 && (
706+ < div className = "space-y-2 pt-2" >
707+ < p className = "px-1 text-[11px] font-semibold uppercase tracking-[0.12em] text-amber-700" > System / Paperclip</ p >
708+ { supportRosterAgents . map ( renderAgentCard ) }
709+ </ div >
710+ ) }
711+ { ! agentsLoading && roster . length === 0 && (
712+ < p className = "rounded-md border border-slate-200 bg-slate-50 p-3 text-sm text-stone-500" >
713+ No agents match the current filters.
714+ </ p >
715+ ) }
636716 </ div >
637717 </ aside >
638718
0 commit comments