From ebcc33b367664c4bc5eeeb86b3dc6262a9f304d7 Mon Sep 17 00:00:00 2001 From: john Date: Mon, 6 Jul 2026 16:06:39 +0800 Subject: [PATCH] =?UTF-8?q?feat(skills):=20=E5=9C=A8=E6=8A=80=E8=83=BD?= =?UTF-8?q?=E9=A1=B5=E9=85=8D=E7=BD=AE=E8=81=94=E7=BD=91=E6=90=9C=E7=B4=A2?= =?UTF-8?q?=20web=20=E8=83=BD=E5=8A=9B=E5=BC=80=E5=85=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 与 web 技能一并保存角色/用户默认,避免 load_skill 后缺少 web_search 工具。 Co-authored-by: Cursor --- src/components/SkillSettings.tsx | 186 ++++++++++++++++++++++++++++--- 1 file changed, 170 insertions(+), 16 deletions(-) diff --git a/src/components/SkillSettings.tsx b/src/components/SkillSettings.tsx index b91ebe6..5e90208 100644 --- a/src/components/SkillSettings.tsx +++ b/src/components/SkillSettings.tsx @@ -1,14 +1,23 @@ -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { + clearUserCapabilityOverrides, clearUserSkillOverrides, + getRoleCapabilities, getRoleSkills, + getUserCapabilities, getUserSkills, + listCapabilityCatalog, listSkillCatalog, + updateRoleCapabilities, updateRoleSkills, + updateUserCapabilities, updateUserSkills, } from '../api/client'; import type { AdminUserRow, SkillDefinition, SkillMap } from '../types'; +const WEB_CAPABILITY_KEY = 'web'; +const WEB_SKILL_NAME = 'web'; + function SkillGrid({ catalog, values, @@ -51,6 +60,75 @@ function SkillGrid({ ); } +function WebSearchToolPanel({ + roleEnabled, + userEnabled, + showUser, + disabled, + onRoleChange, + onUserChange, +}: { + roleEnabled: boolean; + userEnabled: boolean; + showUser: boolean; + disabled?: boolean; + onRoleChange: (enabled: boolean) => void; + onUserChange: (enabled: boolean) => void; +}) { + return ( +
+
+
+

联网搜索工具

+ platform/web +
+
    +
  • +
    + + 网页搜索 + {WEB_CAPABILITY_KEY} + 风险 中 + + + 在 Agent session 挂载 platform/web 扩展,提供{' '} + web_search / fetch_url。需同时开启下方{' '} + {WEB_SKILL_NAME} 技能;用户策略 network_egress=deny{' '} + 时会自动禁用。 + +
    + {!showUser ? ( + + ) : ( + + )} +
  • +
+
+
+ ); +} + export function SkillSettings({ users, userId, @@ -62,8 +140,12 @@ export function SkillSettings({ }) { const [catalog, setCatalog] = useState([]); const [roleSkills, setRoleSkills] = useState({}); + const [roleWebEnabled, setRoleWebEnabled] = useState(false); + const [hasWebCapabilityCatalog, setHasWebCapabilityCatalog] = useState(false); const [selectedUserId, setSelectedUserId] = useState(userId ?? ''); const [userSkills, setUserSkills] = useState({}); + const [userWebEnabled, setUserWebEnabled] = useState(false); + const [userWebOverride, setUserWebOverride] = useState(null); const [userOverrides, setUserOverrides] = useState({}); const [loading, setLoading] = useState(true); const [message, setMessage] = useState(null); @@ -73,12 +155,16 @@ export function SkillSettings({ setLoading(true); setError(null); try { - const [catalogItems, roleState] = await Promise.all([ + const [catalogItems, roleState, capabilityCatalog, roleCapabilities] = await Promise.all([ listSkillCatalog(), getRoleSkills('user'), + listCapabilityCatalog(), + getRoleCapabilities('user'), ]); setCatalog(catalogItems); setRoleSkills(roleState.skills); + setHasWebCapabilityCatalog(capabilityCatalog.some((item) => item.key === WEB_CAPABILITY_KEY)); + setRoleWebEnabled(Boolean(roleCapabilities.capabilities[WEB_CAPABILITY_KEY])); } catch (err) { setError(err instanceof Error ? err.message : '加载技能目录失败'); } finally { @@ -89,13 +175,24 @@ export function SkillSettings({ const loadUserSkills = useCallback(async (userId: string) => { if (!userId) { setUserSkills({}); + setUserWebEnabled(false); + setUserWebOverride(null); setUserOverrides({}); return; } try { - const state = await getUserSkills(userId); + const [state, capabilityState] = await Promise.all([ + getUserSkills(userId), + getUserCapabilities(userId), + ]); setUserSkills(state.skills); setUserOverrides(state.overrides); + setUserWebEnabled(Boolean(capabilityState.capabilities[WEB_CAPABILITY_KEY])); + setUserWebOverride( + Object.prototype.hasOwnProperty.call(capabilityState.overrides, WEB_CAPABILITY_KEY) + ? Boolean(capabilityState.overrides[WEB_CAPABILITY_KEY]) + : null, + ); } catch (err) { setError(err instanceof Error ? err.message : '加载用户技能失败'); } @@ -119,9 +216,14 @@ export function SkillSettings({ setMessage(null); setError(null); try { - const result = await updateRoleSkills('user', roleSkills); - setRoleSkills(result.skills); - setMessage('普通用户默认技能已保存,并已同步到各用户工作区'); + const [skillsResult] = await Promise.all([ + updateRoleSkills('user', roleSkills), + hasWebCapabilityCatalog + ? updateRoleCapabilities('user', { [WEB_CAPABILITY_KEY]: roleWebEnabled }) + : Promise.resolve(null), + ]); + setRoleSkills(skillsResult.skills); + setMessage('普通用户默认技能与联网工具已保存,并已同步到各用户工作区'); } catch (err) { setError(err instanceof Error ? err.message : '保存失败'); } @@ -132,10 +234,15 @@ export function SkillSettings({ setMessage(null); setError(null); try { - const result = await updateUserSkills(selectedUserId, userSkills); - setUserSkills(result.skills); + const [skillsResult] = await Promise.all([ + updateUserSkills(selectedUserId, userSkills), + hasWebCapabilityCatalog + ? updateUserCapabilities(selectedUserId, { [WEB_CAPABILITY_KEY]: userWebEnabled }) + : Promise.resolve(null), + ]); + setUserSkills(skillsResult.skills); await loadUserSkills(selectedUserId); - setMessage('用户技能已保存,并已安装到其 MindSpace 发布目录'); + setMessage('用户技能与联网工具已保存,并已安装到其 MindSpace 发布目录'); } catch (err) { setError(err instanceof Error ? err.message : '保存失败'); } @@ -146,15 +253,28 @@ export function SkillSettings({ setMessage(null); setError(null); try { - await clearUserSkillOverrides(selectedUserId); + await Promise.all([ + clearUserSkillOverrides(selectedUserId), + hasWebCapabilityCatalog ? clearUserCapabilityOverrides(selectedUserId) : Promise.resolve(), + ]); await loadUserSkills(selectedUserId); - setMessage('已恢复为角色默认技能'); + setMessage('已恢复为角色默认技能与联网工具'); } catch (err) { setError(err instanceof Error ? err.message : '重置失败'); } }; const regularUsers = users.filter((user) => user.role === 'user'); + const webSkillEnabledForRole = Boolean(roleSkills[WEB_SKILL_NAME]); + const webSkillEnabledForUser = Boolean(userSkills[WEB_SKILL_NAME]); + const webMismatchRole = useMemo( + () => hasWebCapabilityCatalog && webSkillEnabledForRole && !roleWebEnabled, + [hasWebCapabilityCatalog, webSkillEnabledForRole, roleWebEnabled], + ); + const webMismatchUser = useMemo( + () => hasWebCapabilityCatalog && webSkillEnabledForUser && !userWebEnabled, + [hasWebCapabilityCatalog, webSkillEnabledForUser, userWebEnabled], + ); return (
@@ -166,18 +286,36 @@ export function SkillSettings({

勾选后技能会安装到用户 MindSpace/用户名/.agents/skills/,对话中可通过 load_skill - 使用。开通「静态页面发布」技能会自动启用页面写入能力。 + 使用。开通「静态页面发布」技能会自动启用页面写入能力。联网搜索需在下方同时开启{' '} + 网页搜索工具web 技能。

{error &&

{error}

} {message &&

{message}

} {loading ? (

加载中…

+ ) : !hasWebCapabilityCatalog ? ( +

+ 当前 Portal 能力目录缺少 web,请更新 Memind 后端并重启 admin-api。 +

) : catalog.length === 0 ? (

暂无平台技能,请在 Memind/skills/ 下添加 SKILL.md

) : ( <> {!userOnly && ( <> +

普通用户默认

+ + {webMismatchRole ? ( +

+ 已开启 web 技能但未启用网页搜索工具,Agent 将无法使用 web_search。 +

+ ) : null}

普通用户默认技能

)} - {!userOnly &&

单用户技能覆盖

} + {!userOnly &&

单用户覆盖

} {!userOnly && (