From d5b98b353c183da89dd6b8e92f909d49cd89f993 Mon Sep 17 00:00:00 2001 From: Rhonin Wang <33801807+RhoninSeiei@users.noreply.github.com> Date: Tue, 17 Mar 2026 13:53:16 +0800 Subject: [PATCH] fix: parse multiline frontmatter description in SKILL.md (#6460) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(skills): support multiline frontmatter descriptions * fix(skills): 修复多行 frontmatter 描述解析 * style(skills): clean up frontmatter parser follow-ups --------- Co-authored-by: RhoninSeiei --- astrbot/core/computer/computer_client.py | 25 +++++++++++++----- astrbot/core/skills/skill_manager.py | 22 +++++++++++----- tests/test_skill_metadata_enrichment.py | 33 ++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 14 deletions(-) diff --git a/astrbot/core/computer/computer_client.py b/astrbot/core/computer/computer_client.py index 6e80ac3ab..7e142fa38 100644 --- a/astrbot/core/computer/computer_client.py +++ b/astrbot/core/computer/computer_client.py @@ -213,13 +213,24 @@ def parse_description(text: str) -> str: break if end_idx is None: return "" - for line in lines[1:end_idx]: - if ":" not in line: - continue - key, value = line.split(":", 1) - if key.strip().lower() == "description": - return value.strip().strip('"').strip("'") - return "" + + frontmatter = "\n".join(lines[1:end_idx]) + try: + import yaml + except ImportError: + return "" + + try: + payload = yaml.safe_load(frontmatter) or dict() + except yaml.YAMLError: + return "" + if not isinstance(payload, dict): + return "" + + description = payload.get("description", "") + if not isinstance(description, str): + return "" + return description.strip() def load_managed_skills() -> list[str]: diff --git a/astrbot/core/skills/skill_manager.py b/astrbot/core/skills/skill_manager.py index 9bbdb5aee..191fa4aae 100644 --- a/astrbot/core/skills/skill_manager.py +++ b/astrbot/core/skills/skill_manager.py @@ -11,6 +11,8 @@ from dataclasses import dataclass from datetime import datetime, timezone from pathlib import Path, PurePosixPath +import yaml + from astrbot.core.utils.astrbot_path import ( get_astrbot_data_path, get_astrbot_skills_path, @@ -69,13 +71,19 @@ def _parse_frontmatter_description(text: str) -> str: break if end_idx is None: return "" - for line in lines[1:end_idx]: - if ":" not in line: - continue - key, value = line.split(":", 1) - if key.strip().lower() == "description": - return value.strip().strip('"').strip("'") - return "" + + frontmatter = "\n".join(lines[1:end_idx]) + try: + payload = yaml.safe_load(frontmatter) or {} + except yaml.YAMLError: + return "" + if not isinstance(payload, dict): + return "" + + description = payload.get("description", "") + if not isinstance(description, str): + return "" + return description.strip() # Regex for sanitizing paths used in prompt examples — only allow diff --git a/tests/test_skill_metadata_enrichment.py b/tests/test_skill_metadata_enrichment.py index ba2a21587..1d511af02 100644 --- a/tests/test_skill_metadata_enrichment.py +++ b/tests/test_skill_metadata_enrichment.py @@ -49,6 +49,39 @@ def test_parse_frontmatter_quoted_description(): assert _parse_frontmatter_description(text) == "quoted value" +def test_parse_frontmatter_multiline_literal_description(): + text = ( + "---\n" + "name: humanizer-zh\n" + "description: |\n" + " 去除文本中的 AI 生成痕迹。\n" + " 适用于编辑或审阅文本,使其听起来更自然。\n" + "---\n" + ) + assert _parse_frontmatter_description(text) == ( + "去除文本中的 AI 生成痕迹。\n适用于编辑或审阅文本,使其听起来更自然。" + ) + + +def test_parse_frontmatter_multiline_folded_description(): + text = ( + "---\n" + "name: humanizer-zh\n" + "description: >\n" + " 去除文本中的 AI 生成痕迹。\n" + " 适用于编辑或审阅文本,使其听起来更自然。\n" + "---\n" + ) + assert _parse_frontmatter_description(text) == ( + "去除文本中的 AI 生成痕迹。 适用于编辑或审阅文本,使其听起来更自然。" + ) + + +def test_parse_frontmatter_invalid_yaml_returns_empty(): + text = "---\ndescription: [broken\n---\n" + assert _parse_frontmatter_description(text) == "" + + # ---------- build_skills_prompt tests ----------