Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 29 additions & 17 deletions src/web-ui/src/app/scenes/agents/AgentsScene.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { useAgentsList } from './hooks/useAgentsList';
import { AGENT_ICON_MAP } from './agentsIcons';
import { CAPABILITY_ACCENT, CORE_AGENT_ACCENTS, DEFAULT_CORE_AGENT_ACCENT } from './agentTheme';
import { getCardGradient } from '@/shared/utils/cardGradients';
import { isUserSelectableToolName } from '@/shared/utils/toolVisibility';
import { getAgentBadge, getAgentDescription, getCapabilityLabel } from './utils';
import './AgentsView.scss';
import './AgentsScene.scss';
Expand Down Expand Up @@ -310,16 +311,24 @@ const AgentsHomeView: React.FC = () => {
() => (selectedAgent?.agentKind === 'mode' ? getModeManageableSubagents(selectedAgent.id) : []),
[getModeManageableSubagents, selectedAgent],
);
const selectedAgentTools = useMemo(() => (
const selectedAgentConfiguredTools = useMemo(() => (
selectedAgent?.agentKind === 'mode'
? (selectedAgentModeConfig?.enabled_tools ?? selectedAgent.defaultTools ?? [])
: (selectedAgent?.defaultTools ?? [])
), [selectedAgent, selectedAgentModeConfig]);
const selectedAgentTools = useMemo(
() => selectedAgentConfiguredTools.filter(isUserSelectableToolName),
[selectedAgentConfiguredTools],
);
const userSelectableAvailableTools = useMemo(
() => availableTools.filter((tool) => isUserSelectableToolName(tool.name)),
[availableTools],
);
const selectedAgentHasSkillTool = selectedAgent?.agentKind === 'mode'
? modeHasSkillTool(selectedAgentTools)
? modeHasSkillTool(selectedAgentConfiguredTools)
: false;
const selectedAgentHasTaskTool = selectedAgent?.agentKind === 'mode'
? modeHasTaskTool(selectedAgentTools)
? modeHasTaskTool(selectedAgentConfiguredTools)
: false;
const selectedAgentEnabledSubagents = useMemo(
() => selectedAgentManageableSubagents.filter((subagent) => subagent.effectiveEnabled),
Expand Down Expand Up @@ -366,13 +375,13 @@ const AgentsHomeView: React.FC = () => {
}, [allAgents, selectedAgentModeProfile]);
const selectedAgentUsesSharedProfile = (selectedAgentModeProfile?.memberModeIds.length ?? 0) > 1;
const getDisplayedToolCount = useCallback((agent: AgentWithCapabilities): number => {
if (agent.agentKind === 'mode') {
return getModeConfig(agent.id)?.enabled_tools?.length
?? agent.defaultTools?.length
?? agent.toolCount
?? 0;
const configuredTools = agent.agentKind === 'mode'
? (getModeConfig(agent.id)?.enabled_tools ?? agent.defaultTools)
: agent.defaultTools;
if (configuredTools) {
return configuredTools.filter(isUserSelectableToolName).length;
}
return agent.toolCount ?? agent.defaultTools?.length ?? 0;
return agent.toolCount ?? 0;
}, [getModeConfig]);
const selectedAgentToolCount = selectedAgent ? getDisplayedToolCount(selectedAgent) : 0;
const selectedAgentCapabilityTabs = useMemo(() => {
Expand All @@ -385,10 +394,12 @@ const AgentsHomeView: React.FC = () => {

if (selectedAgentTools.length > 0) {
const currentToolCount = selectedAgent?.agentKind === 'mode'
? (toolsEditing ? (pendingTools ?? selectedAgentTools).length : selectedAgentTools.length)
? (toolsEditing
? (pendingTools ?? selectedAgentConfiguredTools).filter(isUserSelectableToolName).length
: selectedAgentTools.length)
: selectedAgentTools.length;
const totalToolCount = selectedAgent?.agentKind === 'mode'
? availableTools.length
? userSelectableAvailableTools.length
: selectedAgentTools.length;

tabs.push({
Expand Down Expand Up @@ -424,11 +435,12 @@ const AgentsHomeView: React.FC = () => {

return tabs;
}, [
availableTools.length,
userSelectableAvailableTools.length,
pendingSkills,
pendingSubagentIds,
pendingTools,
selectedAgent,
selectedAgentConfiguredTools,
selectedAgentEnabledSubagentIds,
selectedAgentHasSkillTool,
selectedAgentHasTaskTool,
Expand Down Expand Up @@ -1008,7 +1020,7 @@ const AgentsHomeView: React.FC = () => {
size="small"
onClick={() => {
if (currentCapabilityTab === 'tools') {
setPendingTools([...selectedAgentTools]);
setPendingTools([...selectedAgentConfiguredTools]);
setToolsEditing(true);
return;
}
Expand All @@ -1031,17 +1043,17 @@ const AgentsHomeView: React.FC = () => {
{currentCapabilityTab === 'tools' ? (
selectedAgent.agentKind === 'mode' && toolsEditing ? (
<div className="agent-card__token-grid">
{[...availableTools]
{[...userSelectableAvailableTools]
.sort((a, b) => {
const draft = pendingTools ?? selectedAgentTools;
const draft = pendingTools ?? selectedAgentConfiguredTools;
const aOn = draft.includes(a.name);
const bOn = draft.includes(b.name);
if (aOn && !bOn) return -1;
if (!aOn && bOn) return 1;
return 0;
})
.map((tool) => {
const draft = pendingTools ?? selectedAgentTools;
const draft = pendingTools ?? selectedAgentConfiguredTools;
const isOn = draft.includes(tool.name);
return (
<button
Expand All @@ -1051,7 +1063,7 @@ const AgentsHomeView: React.FC = () => {
title={tool.description || tool.name}
onClick={() => {
setPendingTools((prev) => {
const current = prev ?? selectedAgentTools;
const current = prev ?? selectedAgentConfiguredTools;
return isOn
? current.filter((n) => n !== tool.name)
: [...current, tool.name];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import { toolAPI } from '@/infrastructure/api/service-api/ToolAPI';
import { useCurrentWorkspace } from '@/infrastructure/contexts/WorkspaceContext';
import { useNotification } from '@/shared/notification-system';
import { isUserSelectableToolName } from '@/shared/utils/toolVisibility';
import { useAgentsStore } from '../agentsStore';
import {
filterToolsForReviewMode,
Expand Down Expand Up @@ -116,7 +117,7 @@ const CreateAgentPage: React.FC = () => {
const normalizedTools = tools
.map((tool): SubagentEditorToolInfo | null => {
const toolName = typeof tool?.name === 'string' ? tool.name : '';
if (!toolName) {
if (!toolName || !isUserSelectableToolName(toolName)) {
return null;
}
return {
Expand Down Expand Up @@ -236,6 +237,7 @@ const CreateAgentPage: React.FC = () => {
);

const toggleTool = useCallback((toolName: string) => {
if (!isUserSelectableToolName(toolName)) return;
setSelectedTools((prev) => {
const next = new Set(prev);
if (next.has(toolName)) {
Expand Down
24 changes: 16 additions & 8 deletions src/web-ui/src/app/scenes/profile/views/AssistantDefaultsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { MCPAPI, type MCPServerInfo } from '@/infrastructure/api/service-api/MCP
import { notificationService } from '@/shared/notification-system';
import type { DynamicToolInfo } from '@/shared/types/agent-api';
import { createLogger } from '@/shared/utils/logger';
import { isUserSelectableToolName } from '@/shared/utils/toolVisibility';
import { ModelSelector } from '@/flow_chat/components/ModelSelector';
import { useNurseryStore } from '../nurseryStore';

Expand Down Expand Up @@ -100,10 +101,15 @@ const AssistantDefaultsPage: React.FC = () => {
[modeSkills],
);

const userSelectableTools = useMemo(
() => availableTools.filter((tool) => isUserSelectableToolName(tool.name)),
[availableTools],
);

// Split tools into built-in vs MCP
const builtinTools = useMemo(
() => availableTools.filter((tool) => !isMcpTool(tool)),
[availableTools],
() => userSelectableTools.filter((tool) => !isMcpTool(tool)),
[userSelectableTools],
);

const builtinToolsEnabled = useMemo(
Expand All @@ -119,14 +125,14 @@ const AssistantDefaultsPage: React.FC = () => {
// MCP tools grouped by server id
const mcpToolsByServer = useMemo(() => {
const map = new Map<string, ToolInfo[]>();
for (const tool of availableTools) {
for (const tool of userSelectableTools) {
if (!isMcpTool(tool)) continue;
const server = getMcpServerName(tool);
if (!map.has(server)) map.set(server, []);
map.get(server)!.push(tool);
}
return map;
}, [availableTools]);
}, [userSelectableTools]);

// All known MCP server ids — union of detected tool servers + registered servers
const mcpServerIds = useMemo(() => {
Expand Down Expand Up @@ -168,7 +174,7 @@ const AssistantDefaultsPage: React.FC = () => {
}, [detail]);

const handleToolToggle = useCallback(async (toolName: string) => {
if (!assistantModeConfig) return;
if (!assistantModeConfig || !isUserSelectableToolName(toolName)) return;
setToolsLoading((prev) => ({ ...prev, [toolName]: true }));
const current = assistantModeConfig.enabled_tools ?? [];
const isEnabled = current.includes(toolName);
Expand Down Expand Up @@ -208,11 +214,13 @@ const AssistantDefaultsPage: React.FC = () => {

const handleGroupToggleAll = useCallback(async (toolNames: string[]) => {
if (!assistantModeConfig) return;
const selectableToolNames = toolNames.filter(isUserSelectableToolName);
if (selectableToolNames.length === 0) return;
const current = assistantModeConfig.enabled_tools ?? [];
const allEnabled = toolNames.every((n) => current.includes(n));
const allEnabled = selectableToolNames.every((n) => current.includes(n));
const newTools = allEnabled
? current.filter((n) => !toolNames.includes(n))
: [...new Set([...current, ...toolNames])];
? current.filter((n) => !selectableToolNames.includes(n))
: [...new Set([...current, ...selectableToolNames])];
const newConfig = { ...assistantModeConfig, enabled_tools: newTools };
setAssistantModeConfig(newConfig);
try {
Expand Down
15 changes: 15 additions & 0 deletions src/web-ui/src/shared/utils/toolVisibility.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { describe, expect, it } from 'vitest';
import { isUserSelectableToolName } from './toolVisibility';

describe('isUserSelectableToolName', () => {
it.each(['GetToolSpec', 'CallDeferredTool'])(
'hides the internal gateway tool %s',
(toolName) => {
expect(isUserSelectableToolName(toolName)).toBe(false);
},
);

it('keeps regular tools selectable', () => {
expect(isUserSelectableToolName('Read')).toBe(true);
});
});
8 changes: 8 additions & 0 deletions src/web-ui/src/shared/utils/toolVisibility.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const INTERNAL_GATEWAY_TOOL_NAMES: ReadonlySet<string> = new Set([
'GetToolSpec',
'CallDeferredTool',
]);

export function isUserSelectableToolName(toolName: string): boolean {
return !INTERNAL_GATEWAY_TOOL_NAMES.has(toolName);
}
Loading