Skip to content

Commit 0859799

Browse files
fix: Fix disabling agent tools (#2546)
1 parent db631f8 commit 0859799

File tree

1 file changed

+133
-48
lines changed

1 file changed

+133
-48
lines changed

platform/frontend/src/components/chat/agent-tools-display.tsx

Lines changed: 133 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
"use client";
22

3+
import { isAgentTool } from "@shared";
34
import { Bot, Wrench } from "lucide-react";
4-
import { type ReactNode, useCallback, useEffect, useState } from "react";
5+
import {
6+
type ReactNode,
7+
useCallback,
8+
useEffect,
9+
useMemo,
10+
useState,
11+
} from "react";
512
import { Button } from "@/components/ui/button";
613
import { Checkbox } from "@/components/ui/checkbox";
714
import { ExpandableText } from "@/components/ui/expandable-text";
@@ -11,7 +18,18 @@ import {
1118
HoverCardTrigger,
1219
} from "@/components/ui/hover-card";
1320
import { useAgentDelegations } from "@/lib/agent-tools.query";
14-
import { useChatProfileMcpTools } from "@/lib/chat.query";
21+
import {
22+
useChatProfileMcpTools,
23+
useConversationEnabledTools,
24+
useProfileToolsWithIds,
25+
useUpdateConversationEnabledTools,
26+
} from "@/lib/chat.query";
27+
import {
28+
addPendingAction,
29+
applyPendingActions,
30+
getPendingActions,
31+
type PendingToolAction,
32+
} from "@/lib/pending-tool-state";
1533
import { cn } from "@/lib/utils";
1634

1735
// Component to display tools for a specific agent
@@ -52,78 +70,145 @@ interface AgentToolsDisplayProps {
5270
addAgentsButton: ReactNode;
5371
}
5472

55-
// Local storage key for disabled delegations
56-
const getStorageKey = (agentId: string, conversationId?: string) =>
57-
`disabled-delegations:${agentId}:${conversationId || "initial"}`;
58-
5973
/**
6074
* Display agent delegations (agents this agent can delegate to).
61-
* Uses the agent_tools/delegations data like the canvas.
62-
* Supports enable/disable toggle with state persisted in localStorage.
75+
* Uses database-backed enabled tools state (same as ChatToolsDisplay for MCP tools).
76+
* Supports enable/disable toggle persisted via conversation_enabled_tools API.
6377
*/
6478
export function AgentToolsDisplay({
6579
agentId,
6680
conversationId,
6781
addAgentsButton,
6882
}: AgentToolsDisplayProps) {
69-
// Fetch delegated agents from agent_tools (like canvas)
70-
const { data: delegatedAgents = [], isLoading } =
83+
// Fetch delegated agents for display info (name, description)
84+
const { data: delegatedAgents = [], isLoading: isLoadingAgents } =
7185
useAgentDelegations(agentId);
7286

73-
// Track disabled delegation agent IDs
74-
const [disabledAgentIds, setDisabledAgentIds] = useState<Set<string>>(
75-
new Set(),
87+
// Fetch all profile tools to get delegation tool IDs
88+
const { data: profileTools = [], isLoading: isLoadingTools } =
89+
useProfileToolsWithIds(agentId);
90+
91+
// Filter for delegation tools only (tools with name starting with agent__)
92+
const delegationTools = useMemo(
93+
() => profileTools.filter((tool) => isAgentTool(tool.name)),
94+
[profileTools],
7695
);
7796

78-
// Load disabled state from localStorage on mount
79-
useEffect(() => {
80-
const storageKey = getStorageKey(agentId, conversationId);
81-
const stored = localStorage.getItem(storageKey);
82-
if (stored) {
83-
try {
84-
const parsed = JSON.parse(stored);
85-
if (Array.isArray(parsed)) {
86-
setDisabledAgentIds(new Set(parsed));
87-
}
88-
} catch {
89-
// Ignore invalid JSON
97+
// Create a map from target agent ID to tool ID for quick lookup
98+
const targetAgentToToolId = useMemo(() => {
99+
const map = new Map<string, string>();
100+
for (const tool of delegationTools) {
101+
if (tool.delegateToAgentId) {
102+
map.set(tool.delegateToAgentId, tool.id);
90103
}
104+
}
105+
return map;
106+
}, [delegationTools]);
107+
108+
// Local pending actions for display (synced with localStorage)
109+
const [localPendingActions, setLocalPendingActions] = useState<
110+
PendingToolAction[]
111+
>([]);
112+
113+
// Load pending actions from localStorage on mount and when context changes
114+
useEffect(() => {
115+
if (!conversationId) {
116+
const actions = getPendingActions(agentId);
117+
setLocalPendingActions(actions);
91118
} else {
92-
setDisabledAgentIds(new Set());
119+
setLocalPendingActions([]);
93120
}
94121
}, [agentId, conversationId]);
95122

96-
// Save disabled state to localStorage
97-
const saveDisabledState = useCallback(
98-
(newDisabled: Set<string>) => {
99-
const storageKey = getStorageKey(agentId, conversationId);
100-
localStorage.setItem(storageKey, JSON.stringify([...newDisabled]));
123+
// Fetch enabled tools for the conversation
124+
const { data: enabledToolsData } =
125+
useConversationEnabledTools(conversationId);
126+
const enabledToolIds = enabledToolsData?.enabledToolIds ?? [];
127+
const hasCustomSelection = enabledToolsData?.hasCustomSelection ?? false;
128+
129+
// Mutation for updating enabled tools
130+
const updateEnabledTools = useUpdateConversationEnabledTools();
131+
132+
// Default enabled tools: all delegation tools are enabled by default
133+
const defaultEnabledToolIds = useMemo(
134+
() => delegationTools.map((t) => t.id),
135+
[delegationTools],
136+
);
137+
138+
// Compute current enabled tools (same pattern as ChatToolsDisplay)
139+
const currentEnabledToolIds = useMemo(() => {
140+
if (conversationId && hasCustomSelection) {
141+
return enabledToolIds;
142+
}
143+
144+
// Start with defaults (all delegation tools enabled)
145+
const baseIds = defaultEnabledToolIds;
146+
147+
// If no conversation, apply pending actions for display
148+
if (!conversationId && localPendingActions.length > 0) {
149+
return applyPendingActions(baseIds, localPendingActions);
150+
}
151+
152+
return baseIds;
153+
}, [
154+
conversationId,
155+
hasCustomSelection,
156+
enabledToolIds,
157+
defaultEnabledToolIds,
158+
localPendingActions,
159+
]);
160+
161+
const enabledToolIdsSet = new Set(currentEnabledToolIds);
162+
163+
// Check if a delegation is enabled (by target agent ID)
164+
const isEnabled = useCallback(
165+
(targetAgentId: string) => {
166+
const toolId = targetAgentToToolId.get(targetAgentId);
167+
if (!toolId) return true; // Default to enabled if tool not found
168+
return enabledToolIdsSet.has(toolId);
101169
},
102-
[agentId, conversationId],
170+
[targetAgentToToolId, enabledToolIdsSet],
103171
);
104172

105-
// Toggle delegation enabled/disabled
173+
// Handle toggling a delegation (by target agent ID)
106174
const handleToggle = useCallback(
107175
(targetAgentId: string) => {
108-
setDisabledAgentIds((prev) => {
109-
const newSet = new Set(prev);
110-
if (newSet.has(targetAgentId)) {
111-
newSet.delete(targetAgentId);
112-
} else {
113-
newSet.add(targetAgentId);
114-
}
115-
saveDisabledState(newSet);
116-
return newSet;
176+
const toolId = targetAgentToToolId.get(targetAgentId);
177+
if (!toolId) return;
178+
179+
const currentlyEnabled = enabledToolIdsSet.has(toolId);
180+
181+
if (!conversationId) {
182+
// Store in localStorage and update local state
183+
const action: PendingToolAction = currentlyEnabled
184+
? { type: "disable", toolId }
185+
: { type: "enable", toolId };
186+
addPendingAction(action, agentId);
187+
setLocalPendingActions((prev) => [...prev, action]);
188+
return;
189+
}
190+
191+
// Update via API
192+
const newEnabledToolIds = currentlyEnabled
193+
? currentEnabledToolIds.filter((id) => id !== toolId)
194+
: [...currentEnabledToolIds, toolId];
195+
196+
updateEnabledTools.mutateAsync({
197+
conversationId,
198+
toolIds: newEnabledToolIds,
117199
});
118200
},
119-
[saveDisabledState],
201+
[
202+
targetAgentToToolId,
203+
enabledToolIdsSet,
204+
conversationId,
205+
agentId,
206+
currentEnabledToolIds,
207+
updateEnabledTools,
208+
],
120209
);
121210

122-
// Check if a delegation is enabled
123-
const isEnabled = useCallback(
124-
(targetAgentId: string) => !disabledAgentIds.has(targetAgentId),
125-
[disabledAgentIds],
126-
);
211+
const isLoading = isLoadingAgents || isLoadingTools;
127212

128213
if (isLoading || delegatedAgents.length === 0) {
129214
return null;

0 commit comments

Comments
 (0)