diff --git a/backend/package/yuxi/agents/skills/buildin/__init__.py b/backend/package/yuxi/agents/skills/buildin/__init__.py index eebb61fe..1dbc73a0 100644 --- a/backend/package/yuxi/agents/skills/buildin/__init__.py +++ b/backend/package/yuxi/agents/skills/buildin/__init__.py @@ -25,7 +25,7 @@ BUILTIN_SKILLS: list[BuiltinSkillSpec] = [ slug="reporter", source_dir=_SKILLS_ROOT / "reporter", description="生成 SQL 查询报表并生成可视化图表。", - version="1.0.0", + version="2026.03.28", tool_dependencies=[t["name"] for t in get_tool_info(get_mysql_tools())], mcp_dependencies=("mcp-server-chart",), ), diff --git a/backend/package/yuxi/services/skill_service.py b/backend/package/yuxi/services/skill_service.py index e43b42ec..8e382c56 100644 --- a/backend/package/yuxi/services/skill_service.py +++ b/backend/package/yuxi/services/skill_service.py @@ -838,7 +838,7 @@ async def update_builtin_skill( item = await repo.get_by_slug(slug) if not item: raise ValueError(f"内置 skill '{slug}' 未安装") - if not item.is_builtin: + if not item.is_builtin and not _is_builtin_managed(item, slug): raise ValueError(f"技能 '{slug}' 不是内置 skill") if item.content_hash != spec["content_hash"] and not force: diff --git a/backend/test/test_skill_service.py b/backend/test/test_skill_service.py index f5498e95..fa99160e 100644 --- a/backend/test/test_skill_service.py +++ b/backend/test/test_skill_service.py @@ -677,6 +677,110 @@ async def test_update_builtin_skill_needs_confirm_when_hash_mismatch( assert exc_info.value.needs_confirm is True +@pytest.mark.asyncio +async def test_update_builtin_skill_accepts_legacy_managed_record( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +): + monkeypatch.setattr(svc.sys_config, "save_dir", str(tmp_path)) + + source_dir = tmp_path / "builtin" / "reporter" + source_dir.mkdir(parents=True, exist_ok=True) + (source_dir / "SKILL.md").write_text( + "---\nname: reporter\ndescription: builtin\n---\n# SQL Reporter\n", + encoding="utf-8", + ) + + monkeypatch.setattr( + svc, + "list_builtin_skill_specs", + lambda: [ + { + "slug": "reporter", + "name": "reporter", + "description": "builtin", + "version": "1.0.1", + "tool_dependencies": ["mysql_query"], + "mcp_dependencies": ["charts"], + "skill_dependencies": [], + "content_hash": "hash-v2", + "source_dir": source_dir, + } + ], + ) + monkeypatch.setattr( + svc, + "get_builtin_skill_specs", + lambda: [SimpleNamespace(slug="reporter", source_dir=source_dir)], + ) + + installed = Skill( + slug="reporter", + name="reporter", + description="old", + dir_path="skills/reporter", + created_by="system", + updated_by="system", + version=None, + content_hash=None, + ) + + captured: dict[str, object] = {} + + class FakeRepo: + def __init__(self, _db): + pass + + async def get_by_slug(self, slug: str): + return installed + + async def update_metadata(self, item: Skill, *, name: str, description: str, updated_by: str | None): + item.name = name + item.description = description + captured["metadata_updated_by"] = updated_by + return item + + async def update_dependencies( + self, + item: Skill, + *, + tool_dependencies: list[str], + mcp_dependencies: list[str], + skill_dependencies: list[str], + updated_by: str | None, + ): + item.tool_dependencies = tool_dependencies + item.mcp_dependencies = mcp_dependencies + item.skill_dependencies = skill_dependencies + captured["deps_updated_by"] = updated_by + return item + + async def update_builtin_install( + self, + item: Skill, + *, + version: str, + content_hash: str, + updated_by: str | None, + ): + item.version = version + item.content_hash = content_hash + item.is_builtin = True + item.updated_by = updated_by + captured["version"] = version + captured["content_hash"] = content_hash + captured["updated_by"] = updated_by + return item + + monkeypatch.setattr(svc, "SkillRepository", FakeRepo) + + item = await svc.update_builtin_skill(None, "reporter", force=True, updated_by="root") + + assert item.is_builtin is True + assert item.version == "1.0.1" + assert item.content_hash == "hash-v2" + assert captured["updated_by"] == "root" + + @pytest.mark.asyncio async def test_update_builtin_skill_force_overwrites(tmp_path: Path, monkeypatch: pytest.MonkeyPatch): monkeypatch.setattr(svc.sys_config, "save_dir", str(tmp_path)) diff --git a/docs/develop-guides/roadmap.md b/docs/develop-guides/roadmap.md index 728d8bd9..80b2a64a 100644 --- a/docs/develop-guides/roadmap.md +++ b/docs/develop-guides/roadmap.md @@ -50,6 +50,7 @@ ### 修复 +- 兼容旧版已安装的内置 `reporter` 技能记录:`update_builtin_skill` 现在会识别由 `system` 或 `builtin-system` 管理的历史记录,避免更新时误报“技能 `reporter` 不是内置 skill” - 调整沙盒 user-data 目录隔离策略:`workspace` 改为全局共享目录 `saves/user-data/workspace`,`uploads/outputs` 继续保持 thread 级隔离;同时更新 thread artifact 权限校验、viewer 文件系统列举逻辑,以及对应的 router/E2E 测试 - 重构聊天接口请求模型:流式与非流式聊天统一使用 `query + agent_config_id` 请求体,并移除路径中的 `agent_id`;同时修复非流式接口实际误走流式执行链路的问题,改为调用 `invoke_messages` 一次性执行,并补充对应测试 - 修复对话线程与 Agent 配置错位的问题:发送消息时将当前 `agent_config_id` 绑定到 thread 的 `extra_metadata`,线程列表接口返回该绑定值,前端切换历史 thread 时会自动恢复对应配置 diff --git a/web/src/assets/css/extensions.less b/web/src/assets/css/extensions.less index ec8aff92..e1b0052b 100644 --- a/web/src/assets/css/extensions.less +++ b/web/src/assets/css/extensions.less @@ -93,6 +93,9 @@ transition: all 0.2s; border: 1px solid var(--gray-100); background-color: var(--gray-0); + display: flex; + flex-direction: column; + gap: 6px; &:hover { border-color: @border-color; @@ -136,7 +139,7 @@ .item-details { display: flex; - flex-direction: column; + // flex-direction: column; gap: 4px; } diff --git a/web/src/components/SkillsManagerComponent.vue b/web/src/components/SkillsManagerComponent.vue index 18ed0e02..90e8aaf2 100644 --- a/web/src/components/SkillsManagerComponent.vue +++ b/web/src/components/SkillsManagerComponent.vue @@ -18,76 +18,47 @@
-
+
-
内置 Skills
-