@@ -18,7 +18,7 @@ import {
1818 searchToolConfig ,
1919 updateToolConfig ,
2020} from "@/services/agentConfigService" ;
21- import { Agent , AgentSetupOrchestratorProps } from "@/types/agentConfig" ;
21+ import { Agent , AgentSetupOrchestratorProps , Tool } from "@/types/agentConfig" ;
2222import log from "@/lib/logger" ;
2323
2424import SubAgentPool from "./agent/SubAgentPool" ;
@@ -93,6 +93,11 @@ export default function AgentSetupOrchestrator({
9393 const [ isEmbeddingAutoUnsetOpen , setIsEmbeddingAutoUnsetOpen ] =
9494 useState ( false ) ;
9595 const lastProcessedAgentIdForEmbedding = useRef < number | null > ( null ) ;
96+
97+ // Flag to track if we need to refresh enabledToolIds after tools update
98+ const shouldRefreshEnabledToolIds = useRef ( false ) ;
99+ // Track previous tools prop to detect when it's updated
100+ const previousToolsRef = useRef < Tool [ ] | undefined > ( undefined ) ;
96101
97102 // Edit agent related status
98103 const [ isEditingAgent , setIsEditingAgent ] = useState ( false ) ;
@@ -259,10 +264,15 @@ export default function AgentSetupOrchestrator({
259264
260265 // Listen for changes in the tool status, update the selected tool
261266 useEffect ( ( ) => {
262- if ( ! tools || ! enabledToolIds || isLoadingTools ) return ;
263-
264- const enabledTools = tools . filter ( ( tool ) =>
265- enabledToolIds . includes ( Number ( tool . id ) )
267+ if ( ! tools || isLoadingTools ) return ;
268+ // Allow empty enabledToolIds array (it's valid when no tools are selected)
269+ if ( enabledToolIds === undefined || enabledToolIds === null ) return ;
270+
271+ // Filter out unavailable tools (is_available === false) to prevent deleted MCP tools from showing
272+ const enabledTools = tools . filter (
273+ ( tool ) =>
274+ enabledToolIds . includes ( Number ( tool . id ) ) &&
275+ tool . is_available !== false
266276 ) ;
267277
268278 setSelectedTools ( enabledTools ) ;
@@ -338,6 +348,205 @@ export default function AgentSetupOrchestrator({
338348 } ;
339349 } , [ t ] ) ;
340350
351+ // Listen for tools updated events and refresh enabledToolIds if agent is selected
352+ useEffect ( ( ) => {
353+ const handleToolsUpdated = async ( ) => {
354+ // If there's a selected agent (mainAgentId or editingAgent), refresh enabledToolIds
355+ const currentAgentId = ( isEditingAgent && editingAgent
356+ ? Number ( editingAgent . id )
357+ : mainAgentId
358+ ? Number ( mainAgentId )
359+ : undefined ) as number | undefined ;
360+
361+ if ( currentAgentId ) {
362+ try {
363+ // First, refresh the tools list to ensure it's up to date
364+ // Pass false to prevent showing success message (MCP modal will show its own message)
365+ if ( onToolsRefresh ) {
366+ // First, synchronize the selected tools once using search_info.
367+ await refreshAgentToolSelectionsFromServer ( currentAgentId ) ;
368+ // Then refresh the tool list
369+ await onToolsRefresh ( false ) ;
370+ // Wait for React state to update and tools prop to be updated
371+ // Use setTimeout to ensure tools prop is updated before refreshing enabledToolIds
372+ await new Promise ( ( resolve ) => setTimeout ( resolve , 300 ) ) ;
373+ // Set flag to refresh enabledToolIds after tools prop updates
374+ shouldRefreshEnabledToolIds . current = true ;
375+ }
376+ } catch ( error ) {
377+ log . error ( "Failed to refresh tools after tools update:" , error ) ;
378+ }
379+ }
380+ } ;
381+
382+ window . addEventListener ( "toolsUpdated" , handleToolsUpdated ) ;
383+
384+ return ( ) => {
385+ window . removeEventListener ( "toolsUpdated" , handleToolsUpdated ) ;
386+ } ;
387+ } , [ mainAgentId , isEditingAgent , editingAgent , onToolsRefresh , t ] ) ;
388+
389+ const refreshAgentToolSelectionsFromServer = useCallback (
390+ async ( agentId : number ) => {
391+ try {
392+ const agentInfoResult = await searchAgentInfo ( agentId ) ;
393+ if ( agentInfoResult . success && agentInfoResult . data ) {
394+ const remoteTools = Array . isArray ( agentInfoResult . data . tools )
395+ ? agentInfoResult . data . tools
396+ : [ ] ;
397+ const enabledIdsFromServer = remoteTools
398+ . filter (
399+ ( remoteTool : any ) =>
400+ remoteTool && remoteTool . is_available !== false
401+ )
402+ . map ( ( remoteTool : any ) => Number ( remoteTool . id ) )
403+ . filter ( ( id ) => ! Number . isNaN ( id ) ) ;
404+
405+ const filteredIds = enabledIdsFromServer . filter ( ( toolId ) => {
406+ const toolMeta = tools ?. find (
407+ ( tool ) => Number ( tool . id ) === Number ( toolId )
408+ ) ;
409+ return toolMeta && toolMeta . is_available !== false ;
410+ } ) ;
411+
412+ const dedupedIds = Array . from ( new Set ( filteredIds ) ) ;
413+ setEnabledToolIds ( dedupedIds ) ;
414+ log . info ( "Refreshed agent tool selection from search_info" , {
415+ agentId,
416+ toolIds : dedupedIds ,
417+ } ) ;
418+ } else {
419+ log . error (
420+ "Failed to refresh agent tool selection via search_info" ,
421+ agentInfoResult . message
422+ ) ;
423+ }
424+ } catch ( error ) {
425+ log . error (
426+ "Failed to refresh agent tool selection via search_info:" ,
427+ error
428+ ) ;
429+ }
430+ } ,
431+ [ tools , setEnabledToolIds , setSelectedTools ]
432+ ) ;
433+
434+ // Refresh enabledToolIds when tools prop updates after toolsUpdated event
435+ useEffect ( ( ) => {
436+ const prevTools = previousToolsRef . current ;
437+ const haveTools = tools && tools . length > 0 ;
438+ const prevLen = prevTools ?. length ?? 0 ;
439+ const currLen = tools ?. length ?? 0 ;
440+ const idsChanged =
441+ prevTools === undefined ||
442+ JSON . stringify ( prevTools ?. map ( ( t ) => t . id ) . sort ( ) ) !==
443+ JSON . stringify ( ( tools || [ ] ) . map ( ( t ) => t . id ) . sort ( ) ) ;
444+ const grew = currLen > prevLen ;
445+
446+ // Always update the previous ref for future comparisons
447+ previousToolsRef . current = tools ;
448+
449+ // If there are no tools, nothing to do
450+ if ( ! haveTools ) {
451+ return ;
452+ }
453+
454+ const currentAgentId = ( isEditingAgent && editingAgent
455+ ? Number ( editingAgent . id )
456+ : mainAgentId
457+ ? Number ( mainAgentId )
458+ : undefined ) as number | undefined ;
459+
460+ if ( ! currentAgentId ) {
461+ shouldRefreshEnabledToolIds . current = false ;
462+ return ;
463+ }
464+
465+ const refreshEnabledToolIds = async ( ) => {
466+ try {
467+ // Small delay to allow tools prop to stabilize after updates
468+ await new Promise ( ( resolve ) => setTimeout ( resolve , 50 ) ) ;
469+ await refreshAgentToolSelectionsFromServer ( currentAgentId ) ;
470+ } catch ( error ) {
471+ log . error (
472+ "Failed to refresh enabled tool IDs after tools update:" ,
473+ error
474+ ) ;
475+ }
476+ shouldRefreshEnabledToolIds . current = false ;
477+ } ;
478+
479+ // Trigger when:
480+ // 1) We explicitly flagged a refresh after a toolsUpdated event, OR
481+ // 2) The tool list grew (e.g., an MCP tool was added) or IDs changed,
482+ // which indicates the available tool set has changed and we should re-sync
483+ if ( shouldRefreshEnabledToolIds . current || grew || idsChanged ) {
484+ // Optimistically update selected tools to reduce perceived delay/flicker
485+ if ( haveTools && Array . isArray ( enabledToolIds ) && enabledToolIds . length > 0 ) {
486+ try {
487+ const optimisticSelected = ( tools || [ ] ) . filter ( ( tool ) =>
488+ enabledToolIds . includes ( Number ( tool . id ) )
489+ ) ;
490+ setSelectedTools ( optimisticSelected ) ;
491+ } catch ( e ) {
492+ log . warn ( "Optimistic selection update failed; will rely on refresh" , e ) ;
493+ }
494+ }
495+ refreshEnabledToolIds ( ) ;
496+ }
497+ } , [
498+ tools ,
499+ mainAgentId ,
500+ isEditingAgent ,
501+ editingAgent ,
502+ enabledToolIds ,
503+ refreshAgentToolSelectionsFromServer ,
504+ ] ) ;
505+
506+ // Immediately reflect UI selection from enabledToolIds and latest tools (no server wait)
507+ useEffect ( ( ) => {
508+ const haveTools = Array . isArray ( tools ) && tools . length > 0 ;
509+ if ( ! haveTools ) {
510+ setSelectedTools ( [ ] ) ;
511+ return ;
512+ }
513+ if ( ! Array . isArray ( enabledToolIds ) || enabledToolIds . length === 0 ) {
514+ setSelectedTools ( [ ] ) ;
515+ return ;
516+ }
517+ try {
518+ const nextSelected = ( tools || [ ] ) . filter ( ( tool ) =>
519+ enabledToolIds . includes ( Number ( tool . id ) )
520+ ) ;
521+ setSelectedTools ( nextSelected ) ;
522+ } catch ( e ) {
523+ log . warn ( "Failed to sync selectedTools from enabledToolIds" , e ) ;
524+ }
525+ } , [ enabledToolIds , tools , setSelectedTools ] ) ;
526+
527+ // When tools change, sanitize enabledToolIds against availability to prevent transient flicker
528+ useEffect ( ( ) => {
529+ if ( ! Array . isArray ( tools ) || tools . length === 0 ) {
530+ return ;
531+ }
532+ if ( ! Array . isArray ( enabledToolIds ) ) {
533+ return ;
534+ }
535+ const availableIdSet = new Set (
536+ ( tools || [ ] )
537+ . filter ( ( t ) => t && t . is_available !== false )
538+ . map ( ( t ) => Number ( t . id ) )
539+ . filter ( ( id ) => ! Number . isNaN ( id ) )
540+ ) ;
541+ const sanitized = enabledToolIds . filter ( ( id ) => availableIdSet . has ( Number ( id ) ) ) ;
542+ if (
543+ sanitized . length !== enabledToolIds . length ||
544+ sanitized . some ( ( id , idx ) => Number ( id ) !== Number ( enabledToolIds [ idx ] ) )
545+ ) {
546+ setEnabledToolIds ( sanitized ) ;
547+ }
548+ } , [ tools , enabledToolIds , setEnabledToolIds ] ) ;
549+
341550 // Handle the creation of a new Agent
342551 const handleCreateNewAgent = async ( ) => {
343552 // Set to create mode
@@ -646,10 +855,14 @@ export default function AgentSetupOrchestrator({
646855 setFewShotsContent ?.( agentDetail . few_shots_prompt || "" ) ;
647856
648857 // Load Agent tools
858+ // Filter out unavailable tools (is_available === false) to prevent deleted MCP tools from showing
649859 if ( agentDetail . tools && agentDetail . tools . length > 0 ) {
650- setSelectedTools ( agentDetail . tools ) ;
651- // Set enabled tool IDs
652- const toolIds = agentDetail . tools . map ( ( tool : any ) => Number ( tool . id ) ) ;
860+ const availableTools = agentDetail . tools . filter (
861+ ( tool : any ) => tool . is_available !== false
862+ ) ;
863+ setSelectedTools ( availableTools ) ;
864+ // Set enabled tool IDs only for available tools
865+ const toolIds = availableTools . map ( ( tool : any ) => Number ( tool . id ) ) ;
653866 setEnabledToolIds ( toolIds ) ;
654867 } else {
655868 setSelectedTools ( [ ] ) ;
@@ -883,11 +1096,28 @@ export default function AgentSetupOrchestrator({
8831096 } ;
8841097
8851098 // Refresh tool list
886- const handleToolsRefresh = useCallback ( async ( ) => {
887- if ( onToolsRefresh ) {
888- onToolsRefresh ( ) ;
889- }
890- } , [ onToolsRefresh ] ) ;
1099+ const handleToolsRefresh = useCallback (
1100+ async ( showSuccessMessage = true ) => {
1101+ if ( onToolsRefresh ) {
1102+ // Before refreshing the tool list, synchronize the selected tools using search_info.
1103+ const currentAgentId = ( isEditingAgent && editingAgent
1104+ ? Number ( editingAgent . id )
1105+ : mainAgentId
1106+ ? Number ( mainAgentId )
1107+ : undefined ) as number | undefined ;
1108+ if ( currentAgentId ) {
1109+ await refreshAgentToolSelectionsFromServer ( currentAgentId ) ;
1110+ }
1111+ const refreshedTools = await onToolsRefresh ( showSuccessMessage ) ;
1112+ if ( refreshedTools ) {
1113+ shouldRefreshEnabledToolIds . current = true ;
1114+ }
1115+ return refreshedTools ;
1116+ }
1117+ return undefined ;
1118+ } ,
1119+ [ onToolsRefresh , isEditingAgent , editingAgent , mainAgentId , refreshAgentToolSelectionsFromServer ]
1120+ ) ;
8911121
8921122 // Get button tooltip information
8931123 const getLocalButtonTitle = ( ) => {
0 commit comments