diff --git a/packages/builtin-tool-agent-builder/src/systemRole.ts b/packages/builtin-tool-agent-builder/src/systemRole.ts
index bc25d4f8e0..8a3ee0fbfa 100644
--- a/packages/builtin-tool-agent-builder/src/systemRole.ts
+++ b/packages/builtin-tool-agent-builder/src/systemRole.ts
@@ -17,6 +17,28 @@ The injected context includes:
You should use this context to understand the current state of the agent and available tools before making any modifications.
+
+**You are always the Agent Configuration Assistant — never the agent being configured.**
+
+Your sole role is to help users build, configure, and optimize agents. You do not become or roleplay as any agent under any circumstances.
+
+**Interpreting ambiguous short inputs:**
+When a user's message is a short phrase that could be read as either (a) a request for service from a domain expert, or (b) a description of an agent to create/configure — always choose interpretation (b).
+
+Examples of ambiguous inputs that should be treated as configuration requests:
+- "健康助手,咨询健康问题" → The user wants to create/configure an agent titled "健康助手" (Health Assistant) for "咨询健康问题" (health consultation) — NOT asking you to give health advice.
+- "客服机器人,处理售后问题" → Configure a customer-service agent for post-sales issues — NOT asking you to handle after-sales queries yourself.
+- "旅行助手" → Create/configure a travel assistant agent — NOT asking you for travel tips.
+
+The distinction is simple: **you configure agents; you do not act as them.** If the user genuinely wants health/travel/customer-service help, they would be talking to those agents directly — not to you, the Agent Configuration Assistant.
+
+
+
+When LobeHub skills appear in the system context (listed under \`\`), those skills provide task-execution capabilities (e.g., web search, calendar access, coding assistance). However, for all agent **configuration** tasks — updating the agent's model, system prompt, plugins, metadata, or any other settings — always use the Agent Builder tools directly (\`updateConfig\`, \`updatePrompt\`, \`installPlugin\`, etc.).
+
+Do not delegate agent configuration to a LobeHub skill, even if the skill's name or description appears to overlap. Agent Builder tools apply changes immediately and directly to the current agent's stored configuration; LobeHub skills do not modify agent configuration.
+
+
You have access to tools that can modify agent configurations:
@@ -142,6 +164,13 @@ Always adapt to user's language. Use natural descriptions, not raw field names.
+User: "健康助手,咨询健康问题" (short phrase — agent name + purpose)
+Action: Treat as a configuration request, NOT a health consultation. Follow the modification sequence:
+1. Use updateMeta to set identity: { avatar: "🏥", title: "健康助手", description: "专注于健康咨询的 AI 助手" }
+2. Use updateConfig to set a suitable model
+3. Use updatePrompt to write a system prompt for a health consultant
+Do NOT respond as a health assistant or provide health advice. You are configuring the agent on the left panel to become a health assistant.
+
User: "帮我创建一个代码助手" / "Help me create a coding assistant"
Action: Follow the modification sequence:
1. First, use updateMeta to set identity: { avatar: "👨💻", title: "Code Assistant", description: "A helpful coding assistant for debugging and writing code" }
diff --git a/src/server/services/toolExecution/serverRuntimes/agentBuilder.ts b/src/server/services/toolExecution/serverRuntimes/agentBuilder.ts
new file mode 100644
index 0000000000..6ce6ff7596
--- /dev/null
+++ b/src/server/services/toolExecution/serverRuntimes/agentBuilder.ts
@@ -0,0 +1,362 @@
+import {
+ AgentBuilderIdentifier,
+ type GetAvailableModelsParams,
+ type InstallPluginParams,
+ type SearchMarketToolsParams,
+ type UpdateAgentConfigParams,
+ type UpdatePromptParams,
+} from '@lobechat/builtin-tool-agent-builder';
+import { builtinTools } from '@lobechat/builtin-tools';
+import { BRANDING_PROVIDER } from '@lobechat/business-const';
+import { modelsResultsPrompt } from '@lobechat/prompts';
+
+import { AgentModel } from '@/database/models/agent';
+import { PluginModel } from '@/database/models/plugin';
+import { AiInfraRepos } from '@/database/repositories/aiInfra';
+import { DiscoverService } from '@/server/services/discover';
+
+import { type ToolExecutionContext, type ToolExecutionResult } from '../types';
+import { type ServerRuntimeRegistration } from './types';
+
+const MAX_MODELS = 20;
+
+const handleError = (error: unknown, message: string): ToolExecutionResult => {
+ const err = error as Error;
+ return { content: `${message}: ${err.message}`, success: false };
+};
+
+export const agentBuilderRuntime: ServerRuntimeRegistration = {
+ factory: (context: ToolExecutionContext) => {
+ if (!context.userId || !context.serverDB) {
+ throw new Error('userId and serverDB are required for Agent Builder execution');
+ }
+
+ const agentModel = new AgentModel(context.serverDB, context.userId);
+ const pluginModel = new PluginModel(context.serverDB, context.userId);
+ const aiInfraRepos = new AiInfraRepos(context.serverDB, context.userId, {});
+ const discoverService = new DiscoverService();
+
+ return {
+ getAvailableModels: async (
+ params: GetAvailableModelsParams,
+ ): Promise => {
+ try {
+ const allProviders = await aiInfraRepos.getAiProviderList();
+ const enabledProviders = allProviders.filter((p) => p.enabled);
+
+ // LobeHub provider first, then by sort order
+ enabledProviders.sort((a, b) => {
+ if (a.id === BRANDING_PROVIDER) return -1;
+ if (b.id === BRANDING_PROVIDER) return 1;
+ return (a.sort ?? 999) - (b.sort ?? 999);
+ });
+
+ // Apply optional provider filter
+ const filteredProviders = params.providerId
+ ? enabledProviders.filter((p) => p.id === params.providerId)
+ : enabledProviders;
+
+ const providerResults: Array<{
+ id: string;
+ models: Array<{
+ abilities?: {
+ files?: boolean;
+ functionCall?: boolean;
+ reasoning?: boolean;
+ vision?: boolean;
+ };
+ description?: string;
+ id: string;
+ name: string;
+ }>;
+ name: string;
+ }> = [];
+
+ let totalModels = 0;
+
+ for (const provider of filteredProviders) {
+ if (totalModels >= MAX_MODELS) break;
+
+ const enabledChatModels = await aiInfraRepos.getAiProviderModelList(provider.id, {
+ enabled: true,
+ type: 'chat',
+ });
+
+ const remaining = MAX_MODELS - totalModels;
+ const sliced = enabledChatModels.slice(0, remaining);
+
+ if (sliced.length === 0) continue;
+
+ providerResults.push({
+ id: provider.id,
+ models: sliced.map((m) => ({
+ abilities: (m.abilities as any) ?? undefined,
+ id: m.id,
+ name: m.displayName || m.id,
+ })),
+ name: provider.name || provider.id,
+ });
+
+ totalModels += sliced.length;
+ }
+
+ const xmlContent = modelsResultsPrompt(providerResults);
+ const summary = `Found ${providerResults.length} enabled provider(s) with ${totalModels} model(s).\n\n${xmlContent}`;
+
+ return {
+ content: summary,
+ state: { providers: providerResults },
+ success: true,
+ };
+ } catch (error) {
+ return handleError(error, 'Failed to get available models');
+ }
+ },
+
+ searchMarketTools: async (params: SearchMarketToolsParams): Promise => {
+ try {
+ const response = await discoverService.getMcpList({
+ category: params.category,
+ pageSize: params.pageSize || 10,
+ q: params.query,
+ });
+
+ const tools = response.items.map((item) => ({
+ author: item.author,
+ description: item.description,
+ identifier: item.identifier,
+ name: item.name,
+ tags: item.tags,
+ }));
+
+ let summary = `Found ${response.totalCount} tool(s) in the marketplace.`;
+ if (params.query) {
+ summary = `Found ${response.totalCount} tool(s) matching "${params.query}".`;
+ }
+
+ const toolLines = tools
+ .map((t) => `- ${t.name} (${t.identifier})${t.description ? ': ' + t.description : ''}`)
+ .join('\n');
+
+ return {
+ content: `${summary}\n\n${toolLines}`,
+ state: { query: params.query, tools, totalCount: response.totalCount },
+ success: true,
+ };
+ } catch (error) {
+ return handleError(error, 'Failed to search market tools');
+ }
+ },
+
+ updateConfig: async (
+ params: UpdateAgentConfigParams,
+ ctx: ToolExecutionContext,
+ ): Promise => {
+ const agentId = ctx.agentId;
+
+ if (!agentId) {
+ return {
+ content: 'No active agent found',
+ error: { message: 'No active agent found', type: 'NoAgentContext' },
+ success: false,
+ };
+ }
+
+ try {
+ const agent = await agentModel.getAgentConfigById(agentId);
+ if (!agent) {
+ return { content: `Agent "${agentId}" not found.`, success: false };
+ }
+
+ let rawConfig: any = params.config;
+ if (typeof rawConfig === 'string') {
+ try {
+ rawConfig = JSON.parse(rawConfig);
+ } catch {
+ rawConfig = undefined;
+ }
+ }
+ let rawMeta: any = params.meta;
+ if (typeof rawMeta === 'string') {
+ try {
+ rawMeta = JSON.parse(rawMeta);
+ } catch {
+ rawMeta = undefined;
+ }
+ }
+
+ let finalConfig = rawConfig ? { ...rawConfig } : {};
+ const updatedParts: string[] = [];
+
+ if (params.togglePlugin) {
+ const { pluginId, enabled } = params.togglePlugin;
+ const currentPlugins = (agent.plugins as string[] | null) || [];
+ const isEnabled = currentPlugins.includes(pluginId);
+ const shouldEnable = enabled !== undefined ? enabled : !isEnabled;
+
+ const newPlugins =
+ shouldEnable && !isEnabled
+ ? [...currentPlugins, pluginId]
+ : !shouldEnable && isEnabled
+ ? currentPlugins.filter((id: string) => id !== pluginId)
+ : currentPlugins;
+
+ finalConfig = { ...finalConfig, plugins: newPlugins };
+ updatedParts.push(`plugin ${pluginId} ${shouldEnable ? 'enabled' : 'disabled'}`);
+ }
+
+ if ('systemRole' in finalConfig && !('editorData' in finalConfig)) {
+ finalConfig = { ...finalConfig, editorData: null };
+ }
+
+ if (Object.keys(finalConfig).length > 0) {
+ await agentModel.updateConfig(agentId, finalConfig);
+ const nonPluginFields = Object.keys(finalConfig).filter((f) => f !== 'plugins');
+ if (nonPluginFields.length > 0) {
+ updatedParts.push(`config fields: ${nonPluginFields.join(', ')}`);
+ }
+ }
+
+ if (rawMeta && Object.keys(rawMeta).length > 0) {
+ await agentModel.update(agentId, rawMeta as Record);
+ updatedParts.push(`meta fields: ${Object.keys(rawMeta).join(', ')}`);
+ }
+
+ if (updatedParts.length === 0) {
+ return { content: 'No fields to update.', state: { success: true }, success: true };
+ }
+
+ return {
+ content: `Successfully updated agent. Updated ${updatedParts.join('; ')}`,
+ state: { agentId, success: true },
+ success: true,
+ };
+ } catch (error) {
+ return handleError(error, 'Failed to update agent config');
+ }
+ },
+
+ updatePrompt: async (
+ params: UpdatePromptParams,
+ ctx: ToolExecutionContext,
+ ): Promise => {
+ const agentId = ctx.agentId;
+
+ if (!agentId) {
+ return {
+ content: 'No active agent found',
+ error: { message: 'No active agent found', type: 'NoAgentContext' },
+ success: false,
+ };
+ }
+
+ try {
+ await agentModel.update(agentId, {
+ editorData: null,
+ systemRole: params.prompt,
+ } as Record);
+
+ return {
+ content: params.prompt
+ ? `Successfully updated system prompt (${params.prompt.length} characters)`
+ : 'Successfully cleared system prompt',
+ state: { newPrompt: params.prompt, success: true },
+ success: true,
+ };
+ } catch (error) {
+ return handleError(error, 'Failed to update prompt');
+ }
+ },
+
+ installPlugin: async (
+ params: InstallPluginParams,
+ ctx: ToolExecutionContext,
+ ): Promise => {
+ const agentId = ctx.agentId;
+
+ if (!agentId) {
+ return {
+ content: 'No active agent found',
+ error: { message: 'No active agent found', type: 'NoAgentContext' },
+ success: false,
+ };
+ }
+
+ const { identifier, source } = params;
+
+ if (source === 'official') {
+ if (builtinTools.some((t) => t.identifier === identifier)) {
+ // Builtin tools (lobe-web-browsing, lobe-image-generation, etc.) need no OAuth
+ try {
+ const agent = await agentModel.getAgentConfigById(agentId);
+ if (!agent) return { content: `Agent "${agentId}" not found.`, success: false };
+
+ const currentPlugins = (agent.plugins as string[] | null) || [];
+ if (!currentPlugins.includes(identifier)) {
+ await agentModel.updateConfig(agentId, {
+ plugins: [...currentPlugins, identifier],
+ });
+ }
+ return {
+ content: `Successfully enabled "${identifier}" for agent "${agentId}"`,
+ state: { installed: true, pluginId: identifier, success: true },
+ success: true,
+ };
+ } catch (error) {
+ return handleError(error, 'Failed to enable builtin tool');
+ }
+ }
+
+ // OAuth-based tools (Klavis, LobehubSkill) cannot be installed in background context
+ return {
+ content: `Installing official integrations that require OAuth (Klavis, LobehubSkill) is not supported in background execution. Please install "${identifier}" from the Agent Builder UI instead.`,
+ error: { message: 'OAuth not available in background context', type: 'NotSupported' },
+ success: false,
+ };
+ }
+
+ // source === 'market' — MCP marketplace plugin
+ try {
+ const agent = await agentModel.getAgentConfigById(agentId);
+ if (!agent) {
+ return { content: `Agent "${agentId}" not found.`, success: false };
+ }
+
+ const existing = await pluginModel.findById(identifier);
+ if (!existing) {
+ let manifest: any;
+ try {
+ manifest = await discoverService.getMcpManifest({ identifier });
+ } catch {
+ // proceed without manifest if fetch fails; tool will be unusable until manifest loads
+ }
+ await pluginModel.create({ identifier, manifest: manifest as any, type: 'plugin' });
+ } else if (!existing.manifest) {
+ try {
+ const manifest = await discoverService.getMcpManifest({ identifier });
+ await pluginModel.update(identifier, { manifest: manifest as any });
+ } catch {
+ // best-effort backfill
+ }
+ }
+
+ const currentPlugins = (agent.plugins as string[] | null) || [];
+ if (!currentPlugins.includes(identifier)) {
+ await agentModel.updateConfig(agentId, {
+ plugins: [...currentPlugins, identifier],
+ });
+ }
+
+ return {
+ content: `Successfully enabled plugin "${identifier}" for agent "${agentId}"`,
+ state: { installed: true, pluginId: identifier, success: true },
+ success: true,
+ };
+ } catch (error) {
+ return handleError(error, 'Failed to install plugin');
+ }
+ },
+ };
+ },
+ identifier: AgentBuilderIdentifier,
+};
diff --git a/src/server/services/toolExecution/serverRuntimes/index.ts b/src/server/services/toolExecution/serverRuntimes/index.ts
index f63af9434a..e66b95e120 100644
--- a/src/server/services/toolExecution/serverRuntimes/index.ts
+++ b/src/server/services/toolExecution/serverRuntimes/index.ts
@@ -8,6 +8,7 @@
*/
import type { ToolExecutionContext } from '../types';
import { activatorRuntime } from './activator';
+import { agentBuilderRuntime } from './agentBuilder';
import { agentDocumentsRuntime } from './agentDocuments';
import { agentManagementRuntime } from './agentManagement';
import { briefRuntime } from './brief';
@@ -48,6 +49,7 @@ const registerRuntimes = (runtimes: ServerRuntimeRegistration[]) => {
// Register all server runtimes
registerRuntimes([
+ agentBuilderRuntime,
webBrowsingRuntime,
cloudSandboxRuntime,
calculatorRuntime,