feat: update the agent profiles tools check & agentbuilder tools & publish to market button (#11501)

* feat: add lobehubskill into profiles tools

* feat: agent builder support the lobehub skill in profiles

* fix: slove the agent builder controll agent tools not work problem

* feat: add the publish button to profiles main page
This commit is contained in:
Shinji-Li
2026-01-14 19:08:37 +08:00
committed by GitHub
parent 27ec25ca10
commit 85277fa5c4
19 changed files with 410 additions and 59 deletions

View File

@@ -265,6 +265,7 @@
"plugin.settings.title": "{{id}} Skill Configuration",
"plugin.settings.tooltip": "Skill Configuration",
"plugin.store": "Skill Store",
"publishToCommunity": "Publish to Community",
"settingAgent.avatar.sizeExceeded": "Image size exceeds 1MB limit, please choose a smaller image",
"settingAgent.avatar.title": "Avatar",
"settingAgent.backgroundColor.title": "Background Color",

View File

@@ -265,6 +265,7 @@
"plugin.settings.title": "{{id}} 技能配置",
"plugin.settings.tooltip": "技能配置",
"plugin.store": "技能商店",
"publishToCommunity": "发布到社区",
"settingAgent.avatar.sizeExceeded": "图片大小超过 1MB 限制,请选择更小的图片",
"settingAgent.avatar.title": "助理头像",
"settingAgent.backgroundColor.title": "头像背景色",

View File

@@ -1,4 +1,4 @@
import { KLAVIS_SERVER_TYPES } from '@lobechat/const';
import { KLAVIS_SERVER_TYPES, LOBEHUB_SKILL_PROVIDERS } from '@lobechat/const';
import { marketToolsResultsPrompt, modelsResultsPrompt } from '@lobechat/prompts';
import { BuiltinServerRuntimeOutput } from '@lobechat/types';
@@ -10,9 +10,11 @@ import { getToolStoreState } from '@/store/tool';
import {
builtinToolSelectors,
klavisStoreSelectors,
lobehubSkillStoreSelectors,
pluginSelectors,
} from '@/store/tool/selectors';
import { KlavisServerStatus } from '@/store/tool/slices/klavisStore/types';
import { LobehubSkillStatus } from '@/store/tool/slices/lobehubSkillStore/types';
import { getUserStoreState } from '@/store/user';
import { userProfileSelectors } from '@/store/user/selectors';
@@ -732,7 +734,82 @@ export class AgentBuilderExecutionRuntime {
}
}
// Not a Klavis tool, check if it's a builtin tool
// Check if it's a LobehubSkill provider
const isLobehubSkillEnabled =
typeof window !== 'undefined' &&
window.global_serverConfigStore?.getState()?.serverConfig?.enableLobehubSkill;
if (isLobehubSkillEnabled) {
// Check if this is a LobehubSkill provider
const lobehubSkillServer = lobehubSkillStoreSelectors
.getServers(toolState)
.find((s) => s.identifier === identifier);
// Find LobehubSkill provider info from LOBEHUB_SKILL_PROVIDERS
const lobehubSkillProviderInfo = LOBEHUB_SKILL_PROVIDERS.find((p) => p.id === identifier);
if (lobehubSkillProviderInfo) {
// This is a LobehubSkill provider
if (lobehubSkillServer) {
// Server exists
if (lobehubSkillServer.status === LobehubSkillStatus.CONNECTED) {
// Already connected, just enable the plugin
const agentState = getAgentStoreState();
const currentPlugins =
agentSelectors.getAgentConfigById(agentId)(agentState).plugins || [];
if (!currentPlugins.includes(identifier)) {
await getAgentStoreState().optimisticUpdateAgentConfig(agentId, {
plugins: [...currentPlugins, identifier],
});
}
return {
content: `Successfully enabled LobehubSkill provider: ${lobehubSkillProviderInfo.label}`,
state: {
installed: true,
isLobehubSkill: true,
pluginId: identifier,
pluginName: lobehubSkillProviderInfo.label,
serverStatus: 'connected',
success: true,
} as InstallPluginState,
success: true,
};
} else {
// Server exists but not connected - need to reconnect
return {
content: `LobehubSkill provider "${lobehubSkillProviderInfo.label}" is not connected. Please reconnect it from the tools settings.`,
state: {
installed: false,
isLobehubSkill: true,
pluginId: identifier,
pluginName: lobehubSkillProviderInfo.label,
serverStatus: lobehubSkillServer.status,
success: false,
} as InstallPluginState,
success: false,
};
}
} else {
// Server doesn't exist - need to connect first
return {
content: `LobehubSkill provider "${lobehubSkillProviderInfo.label}" is not connected. Please connect it from the tools settings first.`,
state: {
installed: false,
isLobehubSkill: true,
pluginId: identifier,
pluginName: lobehubSkillProviderInfo.label,
serverStatus: 'not_connected',
success: false,
} as InstallPluginState,
success: false,
};
}
}
}
// Not a Klavis or LobehubSkill tool, check if it's a builtin tool
const builtinTools = builtinToolSelectors.metaList(toolState);
const builtinTool = builtinTools.find((t) => t.identifier === identifier);

View File

@@ -1,16 +1,21 @@
'use client';
import { KLAVIS_SERVER_TYPES } from '@lobechat/const';
import { KLAVIS_SERVER_TYPES, LOBEHUB_SKILL_PROVIDERS } from '@lobechat/const';
import { BuiltinInterventionProps } from '@lobechat/types';
import { Avatar , Flexbox } from '@lobehub/ui';
import { Avatar, Flexbox } from '@lobehub/ui';
import { CheckCircle } from 'lucide-react';
import Image from 'next/image';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { useToolStore } from '@/store/tool';
import { klavisStoreSelectors, pluginSelectors } from '@/store/tool/selectors';
import {
klavisStoreSelectors,
lobehubSkillStoreSelectors,
pluginSelectors,
} from '@/store/tool/selectors';
import { KlavisServerStatus } from '@/store/tool/slices/klavisStore/types';
import { LobehubSkillStatus } from '@/store/tool/slices/lobehubSkillStore/types';
import type { InstallPluginParams } from '../../types';
@@ -34,10 +39,19 @@ const InstallPluginIntervention = memo<BuiltinInterventionProps<InstallPluginPar
klavisStoreSelectors.getServers(s).find((srv) => srv.identifier === identifier),
);
// Get LobehubSkill server state
const lobehubSkillServer = useToolStore((s) =>
lobehubSkillStoreSelectors.getServers(s).find((srv) => srv.identifier === identifier),
);
// Check if it's a Klavis tool
const klavisTypeInfo = KLAVIS_SERVER_TYPES.find((t) => t.identifier === identifier);
const isKlavis = source === 'official' && !!klavisTypeInfo;
// Check if it's a LobehubSkill provider
const lobehubSkillProviderInfo = LOBEHUB_SKILL_PROVIDERS.find((p) => p.id === identifier);
const isLobehubSkill = source === 'official' && !!lobehubSkillProviderInfo;
// Render success state (already installed)
if (isPluginInstalled) {
return (
@@ -54,12 +68,12 @@ const InstallPluginIntervention = memo<BuiltinInterventionProps<InstallPluginPar
<CheckCircle size={20} style={{ color: 'var(--lobe-success-6)' }} />
<Flexbox gap={4}>
<span style={{ fontWeight: 600 }}>
{isKlavis
{isKlavis || isLobehubSkill
? t('agentBuilder.installPlugin.connectedAndEnabled')
: t('agentBuilder.installPlugin.installedAndEnabled')}
</span>
<span style={{ color: 'var(--lobe-text-secondary)', fontSize: 12 }}>
{klavisTypeInfo?.label || identifier}
{klavisTypeInfo?.label || lobehubSkillProviderInfo?.label || identifier}
</span>
</Flexbox>
</Flexbox>
@@ -105,6 +119,53 @@ const InstallPluginIntervention = memo<BuiltinInterventionProps<InstallPluginPar
);
}
// Render LobehubSkill provider
if (isLobehubSkill) {
const icon =
typeof lobehubSkillProviderInfo?.icon === 'string'
? lobehubSkillProviderInfo.icon
: undefined;
const isNotConnected =
!lobehubSkillServer || lobehubSkillServer.status !== LobehubSkillStatus.CONNECTED;
return (
<Flexbox
gap={12}
style={{ background: 'var(--lobe-fill-tertiary)', borderRadius: 8, padding: 16 }}
>
<Flexbox align="center" gap={12} horizontal>
{icon ? (
<Image
alt={lobehubSkillProviderInfo?.label || identifier}
height={40}
src={icon}
style={{ borderRadius: 8 }}
unoptimized
width={40}
/>
) : (
<Avatar avatar="🔗" size={40} style={{ borderRadius: 8 }} />
)}
<Flexbox flex={1} gap={4}>
<Flexbox align="center" gap={8} horizontal>
<span style={{ fontWeight: 600 }}>
{lobehubSkillProviderInfo?.label || identifier}
</span>
<span style={{ color: 'var(--lobe-text-tertiary)', fontSize: 12 }}>
LobeHub Skill
</span>
</Flexbox>
<span style={{ color: 'var(--lobe-text-secondary)', fontSize: 12 }}>
{isNotConnected
? t('agentBuilder.installPlugin.requiresAuth')
: t('agentBuilder.installPlugin.clickApproveToConnect')}
</span>
</Flexbox>
</Flexbox>
</Flexbox>
);
}
// Render MCP marketplace plugin
// Note: The actual installation happens in ExecutionRuntime after user approves
return (

View File

@@ -9,8 +9,16 @@ import type { InstallPluginParams, InstallPluginState } from '../../types';
const InstallPlugin = memo<BuiltinRenderProps<InstallPluginParams, InstallPluginState>>(
({ pluginState }) => {
const { pluginId, pluginName, installed, awaitingApproval, isKlavis, serverStatus, error } =
pluginState || {};
const {
pluginId,
pluginName,
installed,
awaitingApproval,
isKlavis,
isLobehubSkill,
serverStatus,
error,
} = pluginState || {};
if (!pluginId) return null;
@@ -43,7 +51,7 @@ const InstallPlugin = memo<BuiltinRenderProps<InstallPluginParams, InstallPlugin
<Flexbox align={'center'} gap={8} horizontal style={{ fontSize: 13 }}>
<Clock size={14} style={{ color: 'var(--lobe-warning-6)' }} />
<span style={{ fontWeight: 500 }}>
{isKlavis ? (
{isKlavis || isLobehubSkill ? (
<>
Waiting for authorization:{' '}
<code
@@ -90,7 +98,7 @@ const InstallPlugin = memo<BuiltinRenderProps<InstallPluginParams, InstallPlugin
<Flexbox align={'center'} gap={8} horizontal style={{ fontSize: 13 }}>
<CheckCircle size={14} style={{ color: 'var(--lobe-success-6)' }} />
<span style={{ fontWeight: 500 }}>
{isKlavis ? 'Connected and enabled' : 'Installed and enabled'}:{' '}
{isKlavis || isLobehubSkill ? 'Connected and enabled' : 'Installed and enabled'}:{' '}
<code
style={{
background: 'var(--lobe-fill-tertiary)',

View File

@@ -27,6 +27,7 @@ export const AgentBuilderManifest: BuiltinToolManifest = {
{
description:
'Search for tools (MCP plugins) in the marketplace. Users can browse and install tools directly from the search results. Use this when users want to find new tools or capabilities.',
humanIntervention: 'always',
name: AgentBuilderApiName.searchMarketTools,
parameters: {
properties: {
@@ -55,7 +56,7 @@ export const AgentBuilderManifest: BuiltinToolManifest = {
// ==================== Write Operations ====================
{
description:
'Install a plugin for the agent. This tool ALWAYS REQUIRES user approval before installation, even in auto-run mode. For MCP marketplace plugins, it will install and enable the plugin. For Klavis tools that need OAuth, it will initiate the connection flow and wait for user to complete authorization.',
'Install a plugin for the agent. This tool ALWAYS REQUIRES user approval before installation, even in auto-run mode. For MCP marketplace plugins, it will install and enable the plugin. For Klavis tools and LobehubSkill providers that need OAuth, it will initiate the connection flow and wait for user to complete authorization.',
humanIntervention: 'always',
name: AgentBuilderApiName.installPlugin,
parameters: {
@@ -67,7 +68,7 @@ export const AgentBuilderManifest: BuiltinToolManifest = {
},
source: {
description:
'Plugin source type: "market" for MCP marketplace plugins, "official" for builtin/Klavis tools',
'Plugin source type: "market" for MCP marketplace plugins, "official" for builtin/Klavis/LobehubSkill tools',
enum: ['market', 'official'],
type: 'string',
},

View File

@@ -12,7 +12,7 @@ export const systemPrompt = `You are an Agent Configuration Assistant integrated
The injected context includes:
- **agent_meta**: title, description, avatar, backgroundColor, tags
- **agent_config**: model, provider, plugins, systemRole (preview), and other advanced settings
- **official_tools**: List of available official tools including built-in tools and LobeHub integrations (Gmail, Google Calendar, Notion, GitHub, etc.) with their enabled/installed status
- **official_tools**: List of available official tools including built-in tools, Klavis MCP servers, and LobehubSkill providers (Linear, Outlook Calendar, Twitter, etc.) with their enabled/installed status
You should use this context to understand the current state of the agent and available tools before making any modifications.
</context_awareness>
@@ -24,7 +24,7 @@ You have access to tools that can modify agent configurations:
- **getAvailableModels**: Get all available AI models and providers that can be used. Optionally filter by provider ID.
- **searchMarketTools**: Search for tools (MCP plugins) in the marketplace. Shows results with install buttons for users to install directly.
Note: Official tools (built-in tools and LobeHub Mcp integrations) are automatically available in the \`<current_agent_context>\` - no need to search for them.
Note: Official tools (built-in tools, Klavis MCP servers, and LobehubSkill providers) are automatically available in the \`<current_agent_context>\` - no need to search for them.
**Write Operations:**
- **updateConfig**: Update agent configuration fields (model, provider, plugins, and advanced settings). Use this for all config changes.
@@ -180,16 +180,16 @@ User: "What tools are available in the marketplace?"
Action: Use searchMarketTools without query to browse all available tools. Display the list with descriptions and install options.
User: "帮我找一下有什么插件可以用"
Action: Reference the \`<official_tools>\` from the injected context to show available built-in tools and LobeHub integrations. This allows the user to enable tools directly or connect to services like Gmail, Google Calendar, etc.
Action: Reference the \`<official_tools>\` from the injected context to show available built-in tools, Klavis MCP servers, and LobehubSkill providers. This allows the user to enable tools directly or connect to services.
User: "I want to connect my Gmail"
Action: Check the \`<official_tools>\` in the context for Gmail LobeHub integration. If found, use installPlugin with source "official" to connect it.
User: "I want to connect my Linear"
Action: Check the \`<official_tools>\` in the context for Linear LobehubSkill provider. If found, use installPlugin with source "official" to connect it.
User: "帮我安装 GitHub 插件"
Action: Check the \`<official_tools>\` in the context for GitHub integration. If found, use installPlugin with source "official" to install it.
User: "帮我连接 Twitter"
Action: Check the \`<official_tools>\` in the context for Twitter (X) LobehubSkill provider. If found, use installPlugin with source "official" to connect it.
User: "What official integrations are available?"
Action: Reference the \`<official_tools>\` from the injected context to list all available LobeHub integrations like Gmail, Google Calendar, Notion, Slack, GitHub, etc.
Action: Reference the \`<official_tools>\` from the injected context to list all available integrations including built-in tools, Klavis MCP servers, and LobehubSkill providers (Linear, Outlook Calendar, Twitter, etc.).
User: "帮我设置开场白" / "Set an opening message for this agent"
Action: Use updateConfig with { config: { openingMessage: "Hello! I'm your AI assistant. How can I help you today?" } }

View File

@@ -164,7 +164,7 @@ export interface InstallPluginParams {
*/
identifier: string;
/**
* Plugin source type: 'market' for MCP marketplace, 'official' for builtin/klavis tools
* Plugin source type: 'market' for MCP marketplace, 'official' for builtin/klavis/lobehubSkill tools
*/
source: 'market' | 'official';
}
@@ -187,6 +187,10 @@ export interface InstallPluginState {
* Whether the plugin is a Klavis tool that needs OAuth connection
*/
isKlavis?: boolean;
/**
* Whether the plugin is a LobehubSkill provider that needs OAuth connection
*/
isLobehubSkill?: boolean;
/**
* Klavis OAuth URL if authorization is needed
*/
@@ -204,9 +208,9 @@ export interface InstallPluginState {
*/
serverName?: string;
/**
* Klavis server status
* Server status (for Klavis tools and LobehubSkill providers)
*/
serverStatus?: 'connected' | 'pending_auth' | 'error';
serverStatus?: 'connected' | 'pending_auth' | 'error' | 'not_connected';
/**
* Whether the operation was successful
*/

View File

@@ -31,8 +31,8 @@ export interface OfficialToolItem {
installed?: boolean;
/** Tool display name */
name: string;
/** Tool type: 'builtin' for built-in tools, 'klavis' for LobeHub Mcp servers */
type: 'builtin' | 'klavis';
/** Tool type: 'builtin' for built-in tools, 'klavis' for LobeHub Mcp servers, 'lobehub-skill' for LobeHub Skill providers */
type: 'builtin' | 'klavis' | 'lobehub-skill';
}
/**
@@ -58,7 +58,7 @@ export interface AgentBuilderContext {
tags?: string[];
title?: string;
};
/** Available official tools (builtin tools and Klavis integrations) */
/** Available official tools (builtin tools, Klavis integrations, and LobehubSkill providers) */
officialTools?: OfficialToolItem[];
}
@@ -136,6 +136,7 @@ const defaultFormatAgentContext = (context: AgentBuilderContext): string => {
if (context.officialTools && context.officialTools.length > 0) {
const builtinTools = context.officialTools.filter((t) => t.type === 'builtin');
const klavisTools = context.officialTools.filter((t) => t.type === 'klavis');
const lobehubSkillTools = context.officialTools.filter((t) => t.type === 'lobehub-skill');
const toolsSections: string[] = [];
@@ -167,6 +168,21 @@ const defaultFormatAgentContext = (context: AgentBuilderContext): string => {
toolsSections.push(` <klavis_tools>\n${klavisItems}\n </klavis_tools>`);
}
if (lobehubSkillTools.length > 0) {
const lobehubSkillItems = lobehubSkillTools
.map((t) => {
const attrs = [
`id="${t.identifier}"`,
`installed="${t.installed ? 'true' : 'false'}"`,
`enabled="${t.enabled ? 'true' : 'false'}"`,
].join(' ');
const desc = t.description ? ` - ${escapeXml(t.description)}` : '';
return ` <tool ${attrs}>${escapeXml(t.name)}${desc}</tool>`;
})
.join('\n');
toolsSections.push(` <lobehub_skill_tools>\n${lobehubSkillItems}\n </lobehub_skill_tools>`);
}
if (toolsSections.length > 0) {
parts.push(
`<available_official_tools>\n${toolsSections.join('\n')}\n</available_official_tools>`,
@@ -179,7 +195,7 @@ const defaultFormatAgentContext = (context: AgentBuilderContext): string => {
}
return `<current_agent_context>
<instruction>This is the current agent's configuration context. Use this information when the user asks about or wants to modify agent settings. Use togglePlugin to enable/disable tools, or installPlugin to install new tools.</instruction>
<instruction>This is the current agent's configuration context. Use this information when the user asks about or wants to modify agent settings. Use togglePlugin to enable/disable tools, or installPlugin to install new tools (including builtin tools, Klavis servers, and LobehubSkill providers).</instruction>
${parts.join('\n')}
</current_agent_context>`;
};

View File

@@ -47,8 +47,8 @@ export interface GroupOfficialToolItem {
installed?: boolean;
/** Tool display name */
name: string;
/** Tool type: 'builtin' for built-in tools, 'klavis' for LobeHub Mcp servers */
type: 'builtin' | 'klavis';
/** Tool type: 'builtin' for built-in tools, 'klavis' for LobeHub Mcp servers, 'lobehub-skill' for LobeHub Skill providers */
type: 'builtin' | 'klavis' | 'lobehub-skill';
}
/**
@@ -195,6 +195,7 @@ const defaultFormatGroupContext = (context: GroupAgentBuilderContext): string =>
if (context.officialTools && context.officialTools.length > 0) {
const builtinTools = context.officialTools.filter((t) => t.type === 'builtin');
const klavisTools = context.officialTools.filter((t) => t.type === 'klavis');
const lobehubSkillTools = context.officialTools.filter((t) => t.type === 'lobehub-skill');
const toolsSections: string[] = [];
@@ -226,6 +227,21 @@ const defaultFormatGroupContext = (context: GroupAgentBuilderContext): string =>
toolsSections.push(` <klavis_tools>\n${klavisItems}\n </klavis_tools>`);
}
if (lobehubSkillTools.length > 0) {
const lobehubSkillItems = lobehubSkillTools
.map((t) => {
const attrs = [
`id="${t.identifier}"`,
`installed="${t.installed ? 'true' : 'false'}"`,
`enabled="${t.enabled ? 'true' : 'false'}"`,
].join(' ');
const desc = t.description ? ` - ${escapeXml(t.description)}` : '';
return ` <tool ${attrs}>${escapeXml(t.name)}${desc}</tool>`;
})
.join('\n');
toolsSections.push(` <lobehub_skill_tools>\n${lobehubSkillItems}\n </lobehub_skill_tools>`);
}
if (toolsSections.length > 0) {
parts.push(
`<available_official_tools>\n${toolsSections.join('\n')}\n</available_official_tools>`,

View File

@@ -1,13 +1,11 @@
import { ActionIcon } from '@lobehub/ui';
import { Button } from '@lobehub/ui';
import { ShapesUploadIcon } from '@lobehub/ui/icons';
import { memo, useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { message } from '@/components/AntdStaticMethods';
import { HEADER_ICON_SIZE } from '@/const/layoutTokens';
import { useMarketAuth } from '@/layout/AuthProvider/MarketAuth';
import { resolveMarketAuthError } from '@/layout/AuthProvider/MarketAuth/errors';
import { useServerConfigStore } from '@/store/serverConfig';
import ForkConfirmModal from './ForkConfirmModal';
import type { MarketPublishAction } from './types';
@@ -21,8 +19,6 @@ interface MarketPublishButtonProps {
const PublishButton = memo<MarketPublishButtonProps>(({ action, onPublishSuccess }) => {
const { t } = useTranslation(['setting', 'marketAuth']);
const mobile = useServerConfigStore((s) => s.isMobile);
const { isAuthenticated, isLoading, signIn } = useMarketAuth();
const { checkOwnership, isCheckingOwnership, isPublishing, publish } = useMarketPublish({
action,
@@ -102,13 +98,14 @@ const PublishButton = memo<MarketPublishButtonProps>(({ action, onPublishSuccess
return (
<>
<ActionIcon
<Button
icon={ShapesUploadIcon}
loading={loading}
onClick={handleButtonClick}
size={HEADER_ICON_SIZE(mobile)}
title={buttonTitle}
/>
>
{t('publishToCommunity')}
</Button>
<ForkConfirmModal
loading={isPublishing}
onCancel={handleForkCancel}

View File

@@ -5,7 +5,6 @@ import NavHeader from '@/features/NavHeader';
import ToggleRightPanelButton from '@/features/RightPanel/ToggleRightPanelButton';
import WideScreenButton from '@/features/WideScreenContainer/WideScreenButton';
import AgentPublishButton from './AgentPublishButton';
import AutoSaveHint from './AutoSaveHint';
const Header = memo(() => {
@@ -16,7 +15,6 @@ const Header = memo(() => {
<>
<WideScreenButton />
<ToggleRightPanelButton icon={BotMessageSquareIcon} showActive={true} />
<AgentPublishButton />
</>
}
/>

View File

@@ -17,6 +17,7 @@ import { useChatStore } from '@/store/chat';
import AgentCronJobs from '../AgentCronJobs';
import EditorCanvas from '../EditorCanvas';
import AgentPublishButton from '../Header/AgentPublishButton';
import AgentHeader from './AgentHeader';
import AgentTool from './AgentTool';
@@ -79,6 +80,7 @@ const ProfileEditor = memo(() => {
>
{t('startConversation')}
</Button>
<AgentPublishButton />
{ENABLE_BUSINESS_FEATURES && (
<Button icon={Clock} onClick={handleCreateCronJob}>
{t('agentCronJobs.addJob')}

View File

@@ -1,6 +1,11 @@
'use client';
import { KLAVIS_SERVER_TYPES, type KlavisServerType } from '@lobechat/const';
import {
KLAVIS_SERVER_TYPES,
type KlavisServerType,
LOBEHUB_SKILL_PROVIDERS,
type LobehubSkillProviderType,
} from '@lobechat/const';
import { Avatar, Button, Flexbox, Icon, type ItemType, Segmented } from '@lobehub/ui';
import { createStaticStyles, cssVar } from 'antd-style';
import isEqual from 'fast-deep-equal';
@@ -10,6 +15,7 @@ import { useTranslation } from 'react-i18next';
import PluginAvatar from '@/components/Plugins/PluginAvatar';
import KlavisServerItem from '@/features/ChatInput/ActionBar/Tools/KlavisServerItem';
import LobehubSkillServerItem from '@/features/ChatInput/ActionBar/Tools/LobehubSkillServerItem';
import ToolItem from '@/features/ChatInput/ActionBar/Tools/ToolItem';
import ActionDropdown from '@/features/ChatInput/ActionBar/components/ActionDropdown';
import PluginStore from '@/features/PluginStore';
@@ -22,6 +28,7 @@ import { useToolStore } from '@/store/tool';
import {
builtinToolSelectors,
klavisStoreSelectors,
lobehubSkillStoreSelectors,
pluginSelectors,
} from '@/store/tool/selectors';
import { type LobeToolMetaWithAvailability } from '@/store/tool/slices/builtin/selectors';
@@ -81,6 +88,19 @@ const KlavisIcon = memo<Pick<KlavisServerType, 'icon' | 'label'>>(({ icon, label
return <Icon className={styles.icon} fill={cssVar.colorText} icon={icon} size={18} />;
});
/**
* LobeHub Skill Provider 图标组件
*/
const LobehubSkillIcon = memo<Pick<LobehubSkillProviderType, 'icon' | 'label'>>(
({ icon, label }) => {
if (typeof icon === 'string') {
return <img alt={label} className={styles.icon} height={18} src={icon} width={18} />;
}
return <Icon className={styles.icon} fill={cssVar.colorText} icon={icon} size={18} />;
},
);
export interface AgentToolProps {
/**
* Optional agent ID to use instead of currentAgentConfig
@@ -125,12 +145,18 @@ const AgentTool = memo<AgentToolProps>(
);
// Web browsing uses searchMode instead of plugins array - use byId selector
const isSearchEnabled = useAgentStore(chatConfigByIdSelectors.isEnableSearchById(effectiveAgentId));
const isSearchEnabled = useAgentStore(
chatConfigByIdSelectors.isEnableSearchById(effectiveAgentId),
);
// Klavis 相关状态
const allKlavisServers = useToolStore(klavisStoreSelectors.getServers, isEqual);
const isKlavisEnabledInEnv = useServerConfigStore(serverConfigSelectors.enableKlavis);
// LobeHub Skill 相关状态
const allLobehubSkillServers = useToolStore(lobehubSkillStoreSelectors.getServers, isEqual);
const isLobehubSkillEnabled = useServerConfigStore(serverConfigSelectors.enableLobehubSkill);
// Plugin store modal state
const [modalOpen, setModalOpen] = useState(false);
const [updating, setUpdating] = useState(false);
@@ -140,10 +166,12 @@ const AgentTool = memo<AgentToolProps>(
const isInitializedRef = useRef(false);
// Fetch plugins
const [useFetchPluginStore, useFetchUserKlavisServers] = useToolStore((s) => [
s.useFetchPluginStore,
s.useFetchUserKlavisServers,
]);
const [useFetchPluginStore, useFetchUserKlavisServers, useFetchLobehubSkillConnections] =
useToolStore((s) => [
s.useFetchPluginStore,
s.useFetchUserKlavisServers,
s.useFetchLobehubSkillConnections,
]);
useFetchPluginStore();
useFetchInstalledPlugins();
useCheckPluginsIsInstalled(plugins);
@@ -151,6 +179,9 @@ const AgentTool = memo<AgentToolProps>(
// 使用 SWR 加载用户的 Klavis 集成(从数据库)
useFetchUserKlavisServers(isKlavisEnabledInEnv);
// 使用 SWR 加载用户的 LobeHub Skill 连接
useFetchLobehubSkillConnections(isLobehubSkillEnabled);
// Toggle web browsing via searchMode - use byId action
const toggleWebBrowsing = useCallback(async () => {
if (!effectiveAgentId) return;
@@ -270,6 +301,19 @@ const AgentTool = memo<AgentToolProps>(
[isKlavisEnabledInEnv, allKlavisServers],
);
// LobeHub Skill Provider 列表项
const lobehubSkillItems = useMemo(
() =>
isLobehubSkillEnabled
? LOBEHUB_SKILL_PROVIDERS.map((provider) => ({
icon: <LobehubSkillIcon icon={provider.icon} label={provider.label} />,
key: provider.id, // 使用 provider.id 作为 key与 pluginId 保持一致
label: <LobehubSkillServerItem label={provider.label} provider={provider.id} />,
}))
: [],
[isLobehubSkillEnabled, allLobehubSkillServers],
);
// Handle plugin remove via Tag close - use byId actions
const handleRemovePlugin =
(
@@ -292,7 +336,7 @@ const AgentTool = memo<AgentToolProps>(
(id) => !builtinList.some((b) => b.identifier === id),
).length;
// 合并 builtin 工具和 Klavis 服务器
// 合并 builtin 工具、LobeHub Skill Providers 和 Klavis 服务器
const builtinItems = useMemo(
() => [
// 原有的 builtin 工具
@@ -312,10 +356,12 @@ const AgentTool = memo<AgentToolProps>(
/>
),
})),
// LobeHub Skill Providers
...lobehubSkillItems,
// Klavis 服务器
...klavisServerItems,
],
[filteredBuiltinList, klavisServerItems, isToolEnabled, handleToggleTool],
[filteredBuiltinList, klavisServerItems, lobehubSkillItems, isToolEnabled, handleToggleTool],
);
// Plugin items for dropdown
@@ -413,8 +459,17 @@ const AgentTool = memo<AgentToolProps>(
plugins.includes(item.key as string),
);
// 合并 builtin 和 Klavis
const allBuiltinItems = [...enabledBuiltinItems, ...connectedKlavisItems];
// 已连接的 LobeHub Skill Providers
const connectedLobehubSkillItems = lobehubSkillItems.filter((item) =>
plugins.includes(item.key as string),
);
// 合并 builtin、LobeHub Skill 和 Klavis
const allBuiltinItems = [
...enabledBuiltinItems,
...connectedKlavisItems,
...connectedLobehubSkillItems,
];
if (allBuiltinItems.length > 0) {
items.push({
@@ -462,6 +517,7 @@ const AgentTool = memo<AgentToolProps>(
}, [
filteredBuiltinList,
klavisServerItems,
lobehubSkillItems,
installedPluginList,
plugins,
isToolEnabled,
@@ -526,7 +582,7 @@ const AgentTool = memo<AgentToolProps>(
overflowY: 'visible',
},
}}
minHeight={isKlavisEnabledInEnv ? 500 : undefined}
minHeight={isKlavisEnabledInEnv || isLobehubSkillEnabled ? 500 : undefined}
minWidth={400}
placement={'bottomLeft'}
popupRender={(menu) => (
@@ -554,7 +610,7 @@ const AgentTool = memo<AgentToolProps>(
className={styles.scroller}
style={{
maxHeight: 500,
minHeight: isKlavisEnabledInEnv ? 500 : undefined,
minHeight: isKlavisEnabledInEnv || isLobehubSkillEnabled ? 500 : undefined,
}}
>
{menu}

View File

@@ -1,6 +1,11 @@
'use client';
import { KLAVIS_SERVER_TYPES, type KlavisServerType } from '@lobechat/const';
import {
KLAVIS_SERVER_TYPES,
type KlavisServerType,
LOBEHUB_SKILL_PROVIDERS,
type LobehubSkillProviderType,
} from '@lobechat/const';
import { Avatar, Icon, Tag } from '@lobehub/ui';
import { createStaticStyles, cssVar } from 'antd-style';
import isEqual from 'fast-deep-equal';
@@ -16,6 +21,7 @@ import { useToolStore } from '@/store/tool';
import {
builtinToolSelectors,
klavisStoreSelectors,
lobehubSkillStoreSelectors,
pluginSelectors,
} from '@/store/tool/selectors';
import { type LobeToolMetaWithAvailability } from '@/store/tool/slices/builtin/selectors';
@@ -31,6 +37,19 @@ const KlavisIcon = memo<Pick<KlavisServerType, 'icon' | 'label'>>(({ icon, label
return <Icon fill={cssVar.colorText} icon={icon} size={16} />;
});
/**
* LobeHub Skill Provider 图标组件
*/
const LobehubSkillIcon = memo<Pick<LobehubSkillProviderType, 'icon' | 'label'>>(
({ icon, label }) => {
if (typeof icon === 'string') {
return <img alt={label} height={16} src={icon} style={{ flexShrink: 0 }} width={16} />;
}
return <Icon fill={cssVar.colorText} icon={icon} size={16} />;
},
);
const styles = createStaticStyles(({ css, cssVar }) => ({
notInstalledTag: css`
border-color: ${cssVar.colorWarningBorder};
@@ -80,10 +99,14 @@ const PluginTag = memo<PluginTagProps>(
const allKlavisServers = useToolStore(klavisStoreSelectors.getServers, isEqual);
const isKlavisEnabledInEnv = useServerConfigStore(serverConfigSelectors.enableKlavis);
// LobeHub Skill 相关状态
const allLobehubSkillServers = useToolStore(lobehubSkillStoreSelectors.getServers, isEqual);
const isLobehubSkillEnabled = useServerConfigStore(serverConfigSelectors.enableLobehubSkill);
// Check if plugin is installed
const isInstalled = useToolStore(pluginSelectors.isPluginInstalled(identifier));
// Try to find in local lists first (including Klavis)
// Try to find in local lists first (including Klavis and LobehubSkill)
const localMeta = useMemo(() => {
// Check if it's a Klavis server type
if (isKlavisEnabledInEnv) {
@@ -102,6 +125,23 @@ const PluginTag = memo<PluginTagProps>(
}
}
// Check if it's a LobeHub Skill provider
if (isLobehubSkillEnabled) {
const lobehubSkillProvider = LOBEHUB_SKILL_PROVIDERS.find((p) => p.id === identifier);
if (lobehubSkillProvider) {
// Check if this LobehubSkill provider is connected
const connectedServer = allLobehubSkillServers.find((s) => s.identifier === identifier);
return {
availableInWeb: true,
icon: lobehubSkillProvider.icon,
isInstalled: !!connectedServer,
label: lobehubSkillProvider.label,
title: lobehubSkillProvider.label,
type: 'lobehub-skill' as const,
};
}
}
const builtinMeta = builtinList.find((p) => p.identifier === identifier);
if (builtinMeta) {
// availableInWeb is only present when using allMetaList
@@ -130,7 +170,15 @@ const PluginTag = memo<PluginTagProps>(
}
return null;
}, [identifier, builtinList, installedPluginList, isKlavisEnabledInEnv, allKlavisServers]);
}, [
identifier,
builtinList,
installedPluginList,
isKlavisEnabledInEnv,
allKlavisServers,
isLobehubSkillEnabled,
allLobehubSkillServers,
]);
// Fetch from remote if not found locally
const usePluginDetail = useDiscoverStore((s) => s.usePluginDetail);
@@ -162,6 +210,11 @@ const PluginTag = memo<PluginTagProps>(
return <KlavisIcon icon={meta.icon} label={meta.label} />;
}
// LobeHub Skill type has icon property
if (meta.type === 'lobehub-skill' && 'icon' in meta && 'label' in meta) {
return <LobehubSkillIcon icon={meta.icon} label={meta.label} />;
}
// Builtin type has avatar
if (meta.type === 'builtin' && 'avatar' in meta && meta.avatar) {
return <Avatar avatar={meta.avatar} shape={'square'} size={16} style={{ flexShrink: 0 }} />;

View File

@@ -278,6 +278,7 @@ export default {
'plugin.settings.title': '{{id}} Skill Configuration',
'plugin.settings.tooltip': 'Skill Configuration',
'plugin.store': 'Skill Store',
'publishToCommunity': 'Publish to Community',
'settingAgent.avatar.sizeExceeded': 'Image size exceeds 1MB limit, please choose a smaller image',
'settingAgent.avatar.title': 'Avatar',
'settingAgent.backgroundColor.title': 'Background Color',

View File

@@ -1,5 +1,5 @@
import { AgentBuilderIdentifier } from '@lobechat/builtin-tool-agent-builder';
import { KLAVIS_SERVER_TYPES } from '@lobechat/const';
import { KLAVIS_SERVER_TYPES, LOBEHUB_SKILL_PROVIDERS } from '@lobechat/const';
import type { OfficialToolItem } from '@lobechat/context-engine';
import {
type FetchSSEOptions,
@@ -41,6 +41,7 @@ import { getToolStoreState } from '@/store/tool';
import {
builtinToolSelectors,
klavisStoreSelectors,
lobehubSkillStoreSelectors,
pluginSelectors,
} from '@/store/tool/selectors';
import { getUserStoreState, useUserStore } from '@/store/user';
@@ -221,6 +222,28 @@ class ChatService {
}
}
// Get LobehubSkill providers (if enabled)
const isLobehubSkillEnabled =
typeof window !== 'undefined' &&
window.global_serverConfigStore?.getState()?.serverConfig?.enableLobehubSkill;
if (isLobehubSkillEnabled) {
const allLobehubSkillServers = lobehubSkillStoreSelectors.getServers(toolState);
for (const provider of LOBEHUB_SKILL_PROVIDERS) {
const server = allLobehubSkillServers.find((s) => s.identifier === provider.id);
officialTools.push({
description: `LobeHub Skill Provider: ${provider.label}`,
enabled: enabledPlugins.includes(provider.id),
identifier: provider.id,
installed: !!server,
name: provider.label,
type: 'lobehub-skill',
});
}
}
agentBuilderContext = {
...baseContext,
officialTools,

View File

@@ -1,7 +1,7 @@
import { AgentBuilderIdentifier } from '@lobechat/builtin-tool-agent-builder';
import { GroupAgentBuilderIdentifier } from '@lobechat/builtin-tool-group-agent-builder';
import { GTDIdentifier } from '@lobechat/builtin-tool-gtd';
import { KLAVIS_SERVER_TYPES, isDesktop } from '@lobechat/const';
import { KLAVIS_SERVER_TYPES, LOBEHUB_SKILL_PROVIDERS, isDesktop } from '@lobechat/const';
import {
type AgentBuilderContext,
type AgentGroupConfig,
@@ -29,7 +29,11 @@ import { getChatGroupStoreState } from '@/store/agentGroup';
import { agentGroupSelectors } from '@/store/agentGroup/selectors';
import { getChatStoreState } from '@/store/chat';
import { getToolStoreState } from '@/store/tool';
import { builtinToolSelectors, klavisStoreSelectors } from '@/store/tool/selectors';
import {
builtinToolSelectors,
klavisStoreSelectors,
lobehubSkillStoreSelectors,
} from '@/store/tool/selectors';
import { isCanUseVideo, isCanUseVision } from '../helper';
import {
@@ -217,6 +221,28 @@ export const contextEngineering = async ({
}
}
// Get LobehubSkill providers (if enabled)
const isLobehubSkillEnabled =
typeof window !== 'undefined' &&
window.global_serverConfigStore?.getState()?.serverConfig?.enableLobehubSkill;
if (isLobehubSkillEnabled) {
const allLobehubSkillServers = lobehubSkillStoreSelectors.getServers(toolState);
for (const provider of LOBEHUB_SKILL_PROVIDERS) {
const server = allLobehubSkillServers.find((s) => s.identifier === provider.id);
officialTools.push({
description: `LobeHub Skill Provider: ${provider.label}`,
enabled: enabledPlugins.includes(provider.id),
identifier: provider.id,
installed: !!server,
name: provider.label,
type: 'lobehub-skill',
});
}
}
groupAgentBuilderContext = {
config: {
openingMessage: activeGroupDetail.config?.openingMessage || undefined,

View File

@@ -123,10 +123,20 @@ export const pluginTypes: StateCreator<
const context = operationId ? { operationId } : undefined;
// Get agent ID, group ID, and topic ID from operation context
const agentId = operation?.context?.agentId;
let agentId = operation?.context?.agentId;
let groupId = operation?.context?.groupId;
const topicId = operation?.context?.topicId;
// For agent-builder tools, inject activeAgentId from store if not in context
// This is needed because AgentBuilderProvider uses a separate scope for messages
// but the tools need the correct agentId for execution
if (payload.identifier === 'lobe-agent-builder') {
const activeAgentId = get().activeAgentId;
if (activeAgentId) {
agentId = activeAgentId;
}
}
// For group-agent-builder tools, inject activeGroupId from store if not in context
// This is needed because AgentBuilderProvider uses a separate scope for messages
// but still needs groupId for tool execution