diff --git a/locales/en-US/setting.json b/locales/en-US/setting.json
index a3b563e3ed..cc6dd10a4a 100644
--- a/locales/en-US/setting.json
+++ b/locales/en-US/setting.json
@@ -1014,6 +1014,7 @@
"tab.usage": "Usage",
"tools.activation.auto": "Auto",
"tools.activation.auto.desc": "Smart",
+ "tools.activation.fixed.hint": "Always on — managed by the app and can’t be turned off",
"tools.activation.pinned": "Pinned",
"tools.activation.pinned.desc": "Always On",
"tools.add": "Add Skill",
@@ -1033,6 +1034,8 @@
"tools.builtins.lobe-agent-documents.title": "Documents",
"tools.builtins.lobe-agent-management.description": "Create, manage, and orchestrate AI agents",
"tools.builtins.lobe-agent-management.title": "Agent Management",
+ "tools.builtins.lobe-agent.description": "Built-in Lobe Agent capabilities: plan and todo management, sub-agent dispatch, and visual media analysis",
+ "tools.builtins.lobe-agent.title": "Lobe Agent",
"tools.builtins.lobe-artifacts.description": "Generate and preview interactive UI components and visualizations",
"tools.builtins.lobe-artifacts.readme": "Generate and live-preview interactive UI components, data visualizations, charts, SVG graphics, and web applications. Create rich visual content that users can interact with directly.",
"tools.builtins.lobe-artifacts.title": "Artifacts",
diff --git a/locales/zh-CN/setting.json b/locales/zh-CN/setting.json
index 17c16ab1cc..2f0cc87d3b 100644
--- a/locales/zh-CN/setting.json
+++ b/locales/zh-CN/setting.json
@@ -1015,6 +1015,7 @@
"tab.usage": "用量",
"tools.activation.auto": "自动",
"tools.activation.auto.desc": "智能调用",
+ "tools.activation.fixed.hint": "由应用强制开启,始终可用,无法关闭",
"tools.activation.pinned": "固定启用",
"tools.activation.pinned.desc": "始终注入",
"tools.add": "集成技能",
@@ -1034,6 +1035,8 @@
"tools.builtins.lobe-agent-documents.title": "文档",
"tools.builtins.lobe-agent-management.description": "创建、管理并编排 AI 助手",
"tools.builtins.lobe-agent-management.title": "助手管理",
+ "tools.builtins.lobe-agent.description": "内置 Lobe Agent 能力:计划与待办管理、子助手调度、视觉媒体分析",
+ "tools.builtins.lobe-agent.title": "Lobe Agent",
"tools.builtins.lobe-artifacts.description": "生成并预览交互式 UI 组件和可视化内容",
"tools.builtins.lobe-artifacts.readme": "生成并实时预览交互式 UI 组件、数据可视化、图表、SVG 图形和 Web 应用。创建用户可直接交互的丰富可视化内容。",
"tools.builtins.lobe-artifacts.title": "Artifacts",
diff --git a/packages/builtin-tools/src/index.ts b/packages/builtin-tools/src/index.ts
index ad7dbf196d..f8585be2dc 100644
--- a/packages/builtin-tools/src/index.ts
+++ b/packages/builtin-tools/src/index.ts
@@ -55,8 +55,20 @@ export const defaultToolIds = [
/**
* Tool IDs that are always enabled regardless of user selection.
* These are core system tools that the agent needs to function properly.
+ *
+ * `lobe-agent` is listed first: its built-in capabilities (plan + todo management,
+ * sub-agent dispatch, visual-media fallback) should be available on every agent-mode turn,
+ * not gated behind explicit injection. NOTE: these rules only apply in agent mode — chat
+ * mode (`enableAgentMode === false`) drops `alwaysOnToolIds` entirely. In manual
+ * skill-activate mode the discovery tools in `manualModeExcludeToolIds` are still removed
+ * from the defaults before the enable checker runs, so they end up disabled there.
+ *
+ * This list is also the source for the chat-input Tools popover's read-only "Pinned"
+ * section (`builtinToolSelectors.fixedDisplayMetaList`), so users can see what the app
+ * keeps active — that selector applies the same manual-mode exclusion to stay truthful.
*/
export const alwaysOnToolIds = [
+ LobeAgentManifest.identifier,
LobeActivatorManifest.identifier,
SkillsManifest.identifier,
SkillStoreManifest.identifier,
diff --git a/src/features/ChatInput/ActionBar/Tools/useControls.tsx b/src/features/ChatInput/ActionBar/Tools/useControls.tsx
index 2e1b933fa8..b0148e113a 100644
--- a/src/features/ChatInput/ActionBar/Tools/useControls.tsx
+++ b/src/features/ChatInput/ActionBar/Tools/useControls.tsx
@@ -157,6 +157,17 @@ const styles = createStaticStyles(({ css }) => ({
iconPinned: css`
color: ${cssVar.colorInfo};
`,
+ fixedIndicator: css`
+ display: inline-flex;
+ flex: none;
+ align-items: center;
+ justify-content: center;
+
+ width: 24px;
+ height: 24px;
+
+ color: ${cssVar.colorTextQuaternary};
+ `,
policyButton: css`
cursor: pointer;
@@ -428,6 +439,14 @@ export const useControls = ({ closeDropdown }: { closeDropdown?: () => void } =
: builtinToolSelectors.metaList,
isEqual,
);
+ // Application-fixed tools (always-on, not user-controllable, e.g. lobe-agent).
+ // Rendered read-only at the top of the "Pinned" section so users can see what the
+ // app keeps active for every conversation. Mode-aware: in manual skill-activate mode the
+ // discovery tools the engine strips (activator, skill-store) are dropped from the list.
+ const fixedDisplayList = useToolStore(
+ builtinToolSelectors.fixedDisplayMetaList({ isManualMode: isManualSkillMode }),
+ isEqual,
+ );
const plugins = useAgentStore((s) => agentByIdSelectors.getAgentPluginsById(agentId)(s));
const updateSkillPolicy = useCallback(
@@ -943,6 +962,70 @@ export const useControls = ({ closeDropdown }: { closeDropdown?: () => void } =
[filteredBuiltinList, t, createManagedSkillItem, uninstallBuiltinTool],
);
+ // Application-fixed tool items (read-only). Always-on tools owned by the runtime
+ // (lobe-agent + always-on infra), so they get a fixed indicator instead of the policy
+ // menu and can't be switched to "auto" or uninstalled.
+ const fixedItems = useMemo(
+ () =>
+ fixedDisplayList.map((item) => {
+ const title = t(`tools.builtins.${item.identifier}.title` as any, {
+ defaultValue: item.meta?.title || item.identifier,
+ });
+ const icon = item.meta?.avatar ? (
+
+ ) : (
+
+ );
+ const popoverContent = (
+
+ ) : (
+
+ )
+ }
+ />
+ );
+
+ return {
+ closeOnClick: false,
+ key: item.identifier,
+ label: (
+
+
+ {icon}
+ {title}
+ {officialTag}
+
+
+
+
+
+
+
+
+
+
+ ),
+ popoverContent,
+ searchText: `${title} ${item.identifier}`,
+ } as SkillMenuItem;
+ }),
+ [fixedDisplayList, t],
+ );
+
// Builtin Agent Skills list items (grouped under LobeHub)
const builtinAgentSkillItems = useMemo(
() =>
@@ -1190,7 +1273,8 @@ export const useControls = ({ closeDropdown }: { closeDropdown?: () => void } =
};
const allPinnedItems = allSkillItems.filter((item) => checkedSet.has(String(item.key)));
const allAutoItems = allSkillItems.filter((item) => !checkedSet.has(String(item.key)));
- const pinnedItems = filterBySearch(allPinnedItems);
+ // App-fixed tools always lead the pinned section, ahead of user-pinned plugins.
+ const pinnedItems = filterBySearch([...fixedItems, ...allPinnedItems]);
const autoItems = filterBySearch(allAutoItems);
const renderActivationGroupLabel = ({
@@ -1270,11 +1354,11 @@ export const useControls = ({ closeDropdown }: { closeDropdown?: () => void } =
);
const marketFooter =
- allSkillItems.length > 0 ? (
+ allSkillItems.length > 0 || fixedItems.length > 0 ? (
- {allPinnedItems.length}
+ {allPinnedItems.length + fixedItems.length}
@@ -1703,6 +1787,6 @@ export const useControls = ({ closeDropdown }: { closeDropdown?: () => void } =
marketFooter,
marketHeader,
marketItems,
- pinnedCount: allPinnedItems.length,
+ pinnedCount: allPinnedItems.length + fixedItems.length,
};
};
diff --git a/src/helpers/toolEngineering/index.test.ts b/src/helpers/toolEngineering/index.test.ts
index 2f47854d7c..754f872081 100644
--- a/src/helpers/toolEngineering/index.test.ts
+++ b/src/helpers/toolEngineering/index.test.ts
@@ -248,11 +248,12 @@ describe('toolEngineering', () => {
provider: 'openai',
});
- expect(result.enabledToolIds).toEqual(['search', 'lobe-web-browsing']);
- expect(result.enabledToolIds).toHaveLength(2);
+ // lobe-agent is always-on (alwaysOnToolIds), so it rides along with user tools.
+ expect(result.enabledToolIds).toEqual(['search', 'lobe-web-browsing', 'lobe-agent']);
+ expect(result.enabledToolIds).toHaveLength(3);
});
- it('should enable visual understanding when it is injected into runtime plugin ids', () => {
+ it('should enable lobe-agent when it is injected into runtime plugin ids', () => {
const toolsEngine = createAgentToolsEngine({ model: 'deepseek-chat', provider: 'deepseek' }, [
'lobe-agent',
]);
@@ -266,7 +267,7 @@ describe('toolEngineering', () => {
expect(result.enabledToolIds).toContain('lobe-agent');
});
- it('should not enable visual understanding by default', () => {
+ it('should enable lobe-agent by default since it is always-on', () => {
const toolsEngine = createAgentToolsEngine({
model: 'deepseek-chat',
provider: 'deepseek',
@@ -278,7 +279,7 @@ describe('toolEngineering', () => {
toolIds: [],
});
- expect(result.enabledToolIds).not.toContain('lobe-agent');
+ expect(result.enabledToolIds).toContain('lobe-agent');
});
});
diff --git a/src/locales/default/setting.ts b/src/locales/default/setting.ts
index 1a6c5f42d7..4f5a87f21c 100644
--- a/src/locales/default/setting.ts
+++ b/src/locales/default/setting.ts
@@ -1213,6 +1213,9 @@ When I am ___, I need ___
'tools.builtins.lobe-agent-documents.title': 'Documents',
'tools.builtins.lobe-agent-management.description': 'Create, manage, and orchestrate AI agents',
'tools.builtins.lobe-agent-management.title': 'Agent Management',
+ 'tools.builtins.lobe-agent.description':
+ 'Built-in Lobe Agent capabilities: plan and todo management, sub-agent dispatch, and visual media analysis',
+ 'tools.builtins.lobe-agent.title': 'Lobe Agent',
'tools.builtins.lobe-brief.description':
'Report progress, deliver results, and request user decisions',
'tools.builtins.lobe-brief.title': 'Brief Tools',
@@ -1501,6 +1504,7 @@ When I am ___, I need ___
'tools.search': 'Search skills...',
'tools.activation.auto': 'Auto',
'tools.activation.auto.desc': 'Smart',
+ 'tools.activation.fixed.hint': 'Always on — managed by the app and can’t be turned off',
'tools.activation.pinned': 'Pinned',
'tools.activation.pinned.desc': 'Always On',
'tools.skillActivateMode.auto.desc':
diff --git a/src/server/modules/Mecha/AgentToolsEngine/__tests__/index.test.ts b/src/server/modules/Mecha/AgentToolsEngine/__tests__/index.test.ts
index 251cbc5026..5ed195e23f 100644
--- a/src/server/modules/Mecha/AgentToolsEngine/__tests__/index.test.ts
+++ b/src/server/modules/Mecha/AgentToolsEngine/__tests__/index.test.ts
@@ -269,7 +269,7 @@ describe('createServerAgentToolsEngine', () => {
expect(result.enabledToolIds).toContain(LobeAgentManifest.identifier);
});
- it('should not enable VisualUnderstanding by default', () => {
+ it('should enable lobe-agent by default since it is always-on', () => {
const context = createMockContext();
const engine = createServerAgentToolsEngine(context, {
agentConfig: { plugins: [] },
@@ -283,7 +283,8 @@ describe('createServerAgentToolsEngine', () => {
toolIds: [],
});
- expect(result.enabledToolIds).not.toContain(LobeAgentManifest.identifier);
+ // lobe-agent is in alwaysOnToolIds, so its core capabilities are on for every agent-mode turn.
+ expect(result.enabledToolIds).toContain(LobeAgentManifest.identifier);
});
it('should enable KnowledgeBase when hasEnabledKnowledgeBases is true', () => {
diff --git a/src/store/tool/slices/builtin/selectors.test.ts b/src/store/tool/slices/builtin/selectors.test.ts
index 903a47a5e4..81e5106e91 100644
--- a/src/store/tool/slices/builtin/selectors.test.ts
+++ b/src/store/tool/slices/builtin/selectors.test.ts
@@ -110,6 +110,74 @@ describe('builtinToolSelectors', () => {
});
});
+ describe('fixedDisplayMetaList', () => {
+ const fixedState = {
+ ...initialState,
+ builtinTools: [
+ {
+ hidden: true,
+ identifier: 'lobe-agent',
+ manifest: {
+ api: [],
+ identifier: 'lobe-agent',
+ meta: { avatar: '🤖', title: 'Lobe Agent' },
+ systemRole: '',
+ },
+ type: 'builtin',
+ },
+ {
+ discoverable: false,
+ hidden: true,
+ identifier: 'lobe-activator',
+ manifest: { api: [], identifier: 'lobe-activator', meta: {}, systemRole: '' },
+ type: 'builtin',
+ },
+ {
+ hidden: true,
+ identifier: 'lobe-skill-store',
+ manifest: { api: [], identifier: 'lobe-skill-store', meta: {}, systemRole: '' },
+ type: 'builtin',
+ },
+ {
+ hidden: true,
+ identifier: 'tool-1',
+ manifest: { api: [], identifier: 'tool-1', meta: { title: 'Tool 1' }, systemRole: '' },
+ type: 'builtin',
+ },
+ ],
+ uninstalledBuiltinTools: [],
+ } as ToolStoreState;
+
+ it('should surface app-fixed tools (e.g. lobe-agent) even though they are hidden', () => {
+ const result = builtinToolSelectors.fixedDisplayMetaList()(fixedState);
+
+ // Only fixed-display ids are returned; lobe-agent leads the list, unrelated tools excluded.
+ expect(result.map((item) => item.identifier)).toContain('lobe-agent');
+ expect(result.map((item) => item.identifier)).not.toContain('tool-1');
+ expect(result[0].identifier).toBe('lobe-agent');
+ });
+
+ it('should drop manual-mode-excluded discovery tools in manual mode', () => {
+ const result = builtinToolSelectors.fixedDisplayMetaList({ isManualMode: true })(fixedState);
+ const ids = result.map((item) => item.identifier);
+
+ // activator + skill-store are stripped from defaults in manual mode, so they aren't on.
+ expect(ids).not.toContain('lobe-activator');
+ expect(ids).not.toContain('lobe-skill-store');
+ // lobe-agent stays on in manual mode.
+ expect(ids).toContain('lobe-agent');
+ });
+
+ it('should skip fixed ids that are not registered in builtinTools', () => {
+ const state = {
+ ...initialState,
+ builtinTools: [],
+ } as unknown as ToolStoreState;
+
+ expect(builtinToolSelectors.fixedDisplayMetaList()(state)).toEqual([]);
+ });
+ });
+
describe('installedAllMetaList', () => {
it('should include all non-uninstalled tools in agent profile configuration', () => {
const state = {
diff --git a/src/store/tool/slices/builtin/selectors.ts b/src/store/tool/slices/builtin/selectors.ts
index 45b8152606..ef456098b4 100644
--- a/src/store/tool/slices/builtin/selectors.ts
+++ b/src/store/tool/slices/builtin/selectors.ts
@@ -1,4 +1,8 @@
-import { runtimeManagedToolIds } from '@lobechat/builtin-tools';
+import {
+ alwaysOnToolIds,
+ manualModeExcludeToolIds,
+ runtimeManagedToolIds,
+} from '@lobechat/builtin-tools';
import { type BuiltinSkill, type LobeToolMeta } from '@lobechat/types';
import {
@@ -228,6 +232,32 @@ const installedAllMetaList = (s: ToolStoreState): LobeToolMetaWithAvailability[]
return [...builtinMetas, ...getKlavisMetasWithAvailability(s)];
};
+const MANUAL_MODE_EXCLUDE_TOOL_IDS = new Set(manualModeExcludeToolIds);
+
+/**
+ * Get meta for the application-fixed tools (always-on, not user-controllable) that should
+ * be shown read-only in the chat-input Tools popover's "Pinned" section.
+ *
+ * These tools are normally `hidden` (and some are `discoverable: false`), so they never
+ * appear in `metaList` / `metaListIncludingHidden`. Here we read them directly from
+ * `builtinTools` by identifier, preserving the `alwaysOnToolIds` order and dropping any
+ * that aren't available in the current environment.
+ *
+ * The list must match what the engine actually enables: in manual skill-activate mode the
+ * discovery tools in `manualModeExcludeToolIds` (activator, skill-store) are stripped from
+ * the defaults before the enable checker runs, so they are NOT on — exclude them here too,
+ * otherwise the UI would claim a fixed tool that the runtime omits.
+ */
+const fixedDisplayMetaList =
+ ({ isManualMode }: { isManualMode: boolean } = { isManualMode: false }) =>
+ (s: ToolStoreState): LobeToolMeta[] =>
+ alwaysOnToolIds
+ .filter((id) => !(isManualMode && MANUAL_MODE_EXCLUDE_TOOL_IDS.has(id)))
+ .map((id) => s.builtinTools.find((tool) => tool.identifier === id))
+ .filter((tool): tool is ToolStoreState['builtinTools'][number] => !!tool)
+ .filter((tool) => isBuiltinToolAvailableInCurrentEnv(tool.identifier))
+ .map(toBuiltinMeta);
+
/**
* Get installed builtin skills (excludes uninstalled ones)
*/
@@ -253,6 +283,7 @@ const isBuiltinToolInstalled = (identifier: string) => (s: ToolStoreState) =>
export const builtinToolSelectors = {
allMetaList,
discoverableMetaList,
+ fixedDisplayMetaList,
installedAllMetaList,
installedBuiltinSkills,
isBuiltinToolInstalled,