chore(openwebui-skills-manager): release v0.3.1
- support multi-line SKILL.md frontmatter descriptions during install - sync README, docs, and release notes for v0.3.1 - add regression coverage for YAML block scalars and CRLF inputs
This commit is contained in:
@@ -5,5 +5,5 @@ OpenWebUI native Tool plugins that can be used across models.
|
|||||||
## Available Tool Plugins
|
## Available Tool Plugins
|
||||||
|
|
||||||
- [Batch Install Plugins from GitHub](batch-install-plugins-tool.md) (v1.1.0) - One-click batch install plugins from GitHub repositories with an interactive selection dialog and multi-language support.
|
- [Batch Install Plugins from GitHub](batch-install-plugins-tool.md) (v1.1.0) - One-click batch install plugins from GitHub repositories with an interactive selection dialog and multi-language support.
|
||||||
- [OpenWebUI Skills Manager Tool](openwebui-skills-manager-tool.md) (v0.3.0) - Simple native skill management (`list/show/install/create/update/delete`).
|
- [OpenWebUI Skills Manager Tool](openwebui-skills-manager-tool.md) (v0.3.1) - Native skill management with multi-line `SKILL.md` frontmatter description support.
|
||||||
- [Smart Mind Map Tool](smart-mind-map-tool.md) (v1.0.0) - Intelligently analyzes text content and proactively generates interactive mind maps to help users structure and visualize knowledge.
|
- [Smart Mind Map Tool](smart-mind-map-tool.md) (v1.0.0) - Intelligently analyzes text content and proactively generates interactive mind maps to help users structure and visualize knowledge.
|
||||||
|
|||||||
@@ -5,5 +5,5 @@
|
|||||||
## 可用 Tool 插件
|
## 可用 Tool 插件
|
||||||
|
|
||||||
- [Batch Install Plugins from GitHub](batch-install-plugins-tool.zh.md) (v1.1.0) - 一键从 GitHub 仓库批量安装插件,支持交互式选择对话框和多语言。
|
- [Batch Install Plugins from GitHub](batch-install-plugins-tool.zh.md) (v1.1.0) - 一键从 GitHub 仓库批量安装插件,支持交互式选择对话框和多语言。
|
||||||
- [OpenWebUI Skills 管理工具](openwebui-skills-manager-tool.zh.md) (v0.3.0) - 简化技能管理(`list/show/install/create/update/delete`)。
|
- [OpenWebUI Skills 管理工具](openwebui-skills-manager-tool.zh.md) (v0.3.1) - 支持多行 `SKILL.md` frontmatter 描述的原生技能管理工具。
|
||||||
- [智能思维导图工具 (Smart Mind Map Tool)](smart-mind-map-tool.zh.md) (v1.0.0) - 智能分析文本内容并主动生成交互式思维导图,帮助用户结构化与可视化知识。
|
- [智能思维导图工具 (Smart Mind Map Tool)](smart-mind-map-tool.zh.md) (v1.0.0) - 智能分析文本内容并主动生成交互式思维导图,帮助用户结构化与可视化知识。
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
# OpenWebUI Skills Manager Tool
|
# OpenWebUI Skills Manager Tool
|
||||||
|
|
||||||
**Author:** [Fu-Jie](https://github.com/Fu-Jie/openwebui-extensions) | **Version:** 0.3.0 | **Project:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions)
|
**Author:** [Fu-Jie](https://github.com/Fu-Jie/openwebui-extensions) | **Version:** 0.3.1 | **Project:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions)
|
||||||
|
|
||||||
A standalone OpenWebUI Tool plugin for managing native Workspace Skills across models.
|
A standalone OpenWebUI Tool plugin for managing native Workspace Skills across models.
|
||||||
|
|
||||||
## What's New
|
## What's New
|
||||||
|
|
||||||
- Added GitHub skills-directory auto-discovery for `install_skill` (e.g., `.../tree/main/skills`) to install all child skills in one request.
|
- `install_skill` now supports multi-line `description: >` / `description: |` frontmatter blocks when importing remote `SKILL.md` files.
|
||||||
- Fixed language detection with robust frontend-first fallback (`__event_call__` + timeout), request header fallback, and profile fallback.
|
- Added metadata fallback to use `title` when `name` is missing, plus regression tests for CRLF and YAML block scalars.
|
||||||
|
|
||||||
## Key Features
|
## Key Features
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
# OpenWebUI Skills 管理工具
|
# OpenWebUI Skills 管理工具
|
||||||
|
|
||||||
**Author:** [Fu-Jie](https://github.com/Fu-Jie/openwebui-extensions) | **Version:** 0.3.0 | **Project:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions)
|
**Author:** [Fu-Jie](https://github.com/Fu-Jie/openwebui-extensions) | **Version:** 0.3.1 | **Project:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions)
|
||||||
|
|
||||||
一个可跨模型使用的 OpenWebUI 原生 Tool 插件,用于管理 Workspace Skills。
|
一个可跨模型使用的 OpenWebUI 原生 Tool 插件,用于管理 Workspace Skills。
|
||||||
|
|
||||||
## 最新更新
|
## 最新更新
|
||||||
|
|
||||||
- `install_skill` 新增 GitHub 技能目录自动发现(例如 `.../tree/main/skills`),可一键安装目录下所有子技能。
|
- `install_skill` 现已支持远程 `SKILL.md` 中的多行 `description: >` / `description: |` frontmatter 描述。
|
||||||
- 修复语言获取逻辑:前端优先(`__event_call__` + 超时保护),并回退到请求头与用户资料。
|
- 新增 `title` 作为 `name` 缺失时的元数据回退,并补齐 CRLF 与 YAML 块标量回归测试。
|
||||||
|
|
||||||
## 核心特性
|
## 核心特性
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# 🧰 OpenWebUI Skills Manager Tool
|
# 🧰 OpenWebUI Skills Manager Tool
|
||||||
|
|
||||||
| By [Fu-Jie](https://github.com/Fu-Jie) · v0.3.0 | [⭐ Star this repo](https://github.com/Fu-Jie/openwebui-extensions) |
|
| By [Fu-Jie](https://github.com/Fu-Jie) · v0.3.1 | [⭐ Star this repo](https://github.com/Fu-Jie/openwebui-extensions) |
|
||||||
| :--- | ---: |
|
| :--- | ---: |
|
||||||
|
|
||||||
|  |  |  |  |  |  |  |
|
|  |  |  |  |  |  |  |
|
||||||
@@ -23,10 +23,9 @@ When the selection dialog opens, search for this plugin, check it, and continue.
|
|||||||
|
|
||||||
## What's New
|
## What's New
|
||||||
|
|
||||||
- **🤖 Automatic Repo Root Discovery**: Install any GitHub repo by providing just the root URL (e.g., `https://github.com/owner/repo`). System auto-converts to discovery mode and installs all skills.
|
- **📝 Multi-line Frontmatter Descriptions**: `install_skill` now correctly parses `description: >` and `description: |` blocks in remote `SKILL.md` files, so imported skill descriptions no longer truncate to a single line.
|
||||||
- **🔄 Batch Deduplication**: Automatically removes duplicate URLs from batch installations and detects duplicate skill names.
|
- **↩️ Better Metadata Fallbacks**: If a skill frontmatter provides `title` without `name`, the installer now uses that title before falling back to directory-based names.
|
||||||
- Added GitHub skills-directory auto-discovery for `install_skill` (e.g., `.../tree/main/skills`) to install all child skills in one request.
|
- **🧪 Regression Coverage**: Added focused tests for folded/literal YAML blocks and CRLF line endings to keep external skill imports stable.
|
||||||
- Fixed language detection with robust frontend-first fallback (`__event_call__` + timeout), request header fallback, and profile fallback.
|
|
||||||
|
|
||||||
> [!TIP]
|
> [!TIP]
|
||||||
> **💡 Looking to batch install global plugins (Actions, Filters, Pipes, Tools)?**
|
> **💡 Looking to batch install global plugins (Actions, Filters, Pipes, Tools)?**
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# 🧰 OpenWebUI Skills 管理工具
|
# 🧰 OpenWebUI Skills 管理工具
|
||||||
|
|
||||||
| 作者:[Fu-Jie](https://github.com/Fu-Jie) · v0.3.0 | [⭐ 点个 Star 支持项目](https://github.com/Fu-Jie/openwebui-extensions) |
|
| 作者:[Fu-Jie](https://github.com/Fu-Jie) · v0.3.1 | [⭐ 点个 Star 支持项目](https://github.com/Fu-Jie/openwebui-extensions) |
|
||||||
| :--- | ---: |
|
| :--- | ---: |
|
||||||
|
|
||||||
|  |  |  |  |  |  |  |
|
|  |  |  |  |  |  |  |
|
||||||
@@ -23,10 +23,9 @@
|
|||||||
|
|
||||||
## 最新更新
|
## 最新更新
|
||||||
|
|
||||||
- **🤖 自动发现仓库根目录**:现在可以直接提供 GitHub 仓库根 URL(如 `https://github.com/owner/repo`),系统会自动转换为发现模式并安装所有 skill。
|
- **📝 支持多行 Frontmatter 描述**:`install_skill` 现在可以正确解析远程 `SKILL.md` 里的 `description: >` 和 `description: |`,导入后的技能描述不再被截断成单行。
|
||||||
- **🔄 批量去重**:自动清除重复 URL,检测重复的 skill 名称。
|
- **↩️ 更稳的元数据回退**:当 frontmatter 只有 `title` 没有 `name` 时,安装器会优先使用 `title`,避免退回到通用目录名。
|
||||||
- `install_skill` 新增 GitHub 技能目录自动发现(例如 `.../tree/main/skills`),可一键安装目录下所有子技能。
|
- **🧪 回归测试补齐**:新增 folded/literal YAML 块和 CRLF 换行场景测试,保证外部技能导入行为稳定。
|
||||||
- 修复语言获取逻辑:前端优先(`__event_call__` + 超时保护),并回退到请求头与用户资料。
|
|
||||||
|
|
||||||
> [!TIP]
|
> [!TIP]
|
||||||
> **💡 想要批量安装/管理全局插件 (Actions, Filters, Pipes, Tools)?**
|
> **💡 想要批量安装/管理全局插件 (Actions, Filters, Pipes, Tools)?**
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# 🧰 OpenWebUI Skills Manager Tool
|
# 🧰 OpenWebUI Skills Manager Tool
|
||||||
|
|
||||||
| By [Fu-Jie](https://github.com/Fu-Jie) · v0.3.0 | [⭐ Star this repo](https://github.com/Fu-Jie/openwebui-extensions) |
|
| By [Fu-Jie](https://github.com/Fu-Jie) · v0.3.1 | [⭐ Star this repo](https://github.com/Fu-Jie/openwebui-extensions) |
|
||||||
| :--- | ---: |
|
| :--- | ---: |
|
||||||
|
|
||||||
|  |  |  |  |  |  |  |
|
|  |  |  |  |  |  |  |
|
||||||
@@ -23,10 +23,9 @@ When the selection dialog opens, search for this plugin, check it, and continue.
|
|||||||
|
|
||||||
## What's New
|
## What's New
|
||||||
|
|
||||||
- **🤖 Automatic Repo Root Discovery**: Install any GitHub repo by providing just the root URL (e.g., `https://github.com/owner/repo`). System auto-converts to discovery mode and installs all skills.
|
- **📝 Multi-line Frontmatter Descriptions**: `install_skill` now correctly parses `description: >` and `description: |` blocks in remote `SKILL.md` files, so imported skill descriptions no longer truncate to a single line.
|
||||||
- **🔄 Batch Deduplication**: Automatically removes duplicate URLs from batch installations and detects duplicate skill names.
|
- **↩️ Better Metadata Fallbacks**: If a skill frontmatter provides `title` without `name`, the installer now uses that title before falling back to directory-based names.
|
||||||
- Added GitHub skills-directory auto-discovery for `install_skill` (e.g., `.../tree/main/skills`) to install all child skills in one request.
|
- **🧪 Regression Coverage**: Added focused tests for folded/literal YAML blocks and CRLF line endings to keep external skill imports stable.
|
||||||
- Fixed language detection with robust frontend-first fallback (`__event_call__` + timeout), request header fallback, and profile fallback.
|
|
||||||
|
|
||||||
> [!TIP]
|
> [!TIP]
|
||||||
> **💡 Looking to batch install global plugins (Actions, Filters, Pipes, Tools)?**
|
> **💡 Looking to batch install global plugins (Actions, Filters, Pipes, Tools)?**
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# 🧰 OpenWebUI Skills 管理工具
|
# 🧰 OpenWebUI Skills 管理工具
|
||||||
|
|
||||||
| 作者:[Fu-Jie](https://github.com/Fu-Jie) · v0.3.0 | [⭐ 点个 Star 支持项目](https://github.com/Fu-Jie/openwebui-extensions) |
|
| 作者:[Fu-Jie](https://github.com/Fu-Jie) · v0.3.1 | [⭐ 点个 Star 支持项目](https://github.com/Fu-Jie/openwebui-extensions) |
|
||||||
| :--- | ---: |
|
| :--- | ---: |
|
||||||
|
|
||||||
|  |  |  |  |  |  |  |
|
|  |  |  |  |  |  |  |
|
||||||
@@ -23,10 +23,9 @@
|
|||||||
|
|
||||||
## 最新更新
|
## 最新更新
|
||||||
|
|
||||||
- **🤖 自动发现仓库根目录**:现在可以直接提供 GitHub 仓库根 URL(如 `https://github.com/owner/repo`),系统会自动转换为发现模式并安装所有 skill。
|
- **📝 支持多行 Frontmatter 描述**:`install_skill` 现在可以正确解析远程 `SKILL.md` 里的 `description: >` 和 `description: |`,导入后的技能描述不再被截断成单行。
|
||||||
- **🔄 批量去重**:自动清除重复 URL,检测重复的 skill 名称。
|
- **↩️ 更稳的元数据回退**:当 frontmatter 只有 `title` 没有 `name` 时,安装器会优先使用 `title`,避免退回到通用目录名。
|
||||||
- `install_skill` 新增 GitHub 技能目录自动发现(例如 `.../tree/main/skills`),可一键安装目录下所有子技能。
|
- **🧪 回归测试补齐**:新增 folded/literal YAML 块和 CRLF 换行场景测试,保证外部技能导入行为稳定。
|
||||||
- 修复语言获取逻辑:前端优先(`__event_call__` + 超时保护),并回退到请求头与用户资料。
|
|
||||||
|
|
||||||
> [!TIP]
|
> [!TIP]
|
||||||
> **💡 想要批量安装/管理全局插件 (Actions, Filters, Pipes, Tools)?**
|
> **💡 想要批量安装/管理全局插件 (Actions, Filters, Pipes, Tools)?**
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ title: OpenWebUI Skills Manager Tool
|
|||||||
author: Fu-Jie
|
author: Fu-Jie
|
||||||
author_url: https://github.com/Fu-Jie/openwebui-extensions
|
author_url: https://github.com/Fu-Jie/openwebui-extensions
|
||||||
funding_url: https://github.com/open-webui
|
funding_url: https://github.com/open-webui
|
||||||
version: 0.3.0
|
version: 0.3.1
|
||||||
openwebui_id: b4bce8e4-08e7-4f90-bea7-dc31d463a0bb
|
openwebui_id: b4bce8e4-08e7-4f90-bea7-dc31d463a0bb
|
||||||
requirements:
|
requirements:
|
||||||
description: Standalone OpenWebUI tool for managing native Workspace Skills (list/show/install/create/update/delete) for any model.
|
description: Standalone OpenWebUI tool for managing native Workspace Skills (list/show/install/create/update/delete) for any model.
|
||||||
@@ -846,26 +846,138 @@ async def _fetch_bytes(valves, url: str) -> bytes:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
_FRONTMATTER_KEY_RE = re.compile(r"^([A-Za-z0-9_-]+):(.*)$")
|
||||||
|
|
||||||
|
|
||||||
|
def _normalize_newlines(text: str) -> str:
|
||||||
|
"""Normalize CRLF/CR text to LF for stable frontmatter parsing."""
|
||||||
|
return text.replace("\r\n", "\n").replace("\r", "\n")
|
||||||
|
|
||||||
|
|
||||||
|
def _strip_matching_quotes(value: str) -> str:
|
||||||
|
"""Remove matching wrapping quotes from a scalar value."""
|
||||||
|
value = (value or "").strip()
|
||||||
|
if len(value) >= 2 and value[0] == value[-1] and value[0] in {"'", '"'}:
|
||||||
|
return value[1:-1]
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def _collect_frontmatter_block(lines: List[str], start_index: int) -> Tuple[List[str], int]:
|
||||||
|
"""Collect indented block lines until the next top-level frontmatter key."""
|
||||||
|
block_lines: List[str] = []
|
||||||
|
index = start_index
|
||||||
|
|
||||||
|
while index < len(lines):
|
||||||
|
line = lines[index]
|
||||||
|
if not line.strip():
|
||||||
|
block_lines.append("")
|
||||||
|
index += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
indent = len(line) - len(line.lstrip(" "))
|
||||||
|
if indent == 0 and _FRONTMATTER_KEY_RE.match(line):
|
||||||
|
break
|
||||||
|
if indent == 0:
|
||||||
|
break
|
||||||
|
|
||||||
|
block_lines.append(line)
|
||||||
|
index += 1
|
||||||
|
|
||||||
|
non_empty_indents = [
|
||||||
|
len(line) - len(line.lstrip(" ")) for line in block_lines if line.strip()
|
||||||
|
]
|
||||||
|
min_indent = min(non_empty_indents) if non_empty_indents else 0
|
||||||
|
dedented = [line[min_indent:] if line else "" for line in block_lines]
|
||||||
|
return dedented, index
|
||||||
|
|
||||||
|
|
||||||
|
def _fold_yaml_block(lines: List[str]) -> str:
|
||||||
|
"""Fold YAML `>` block lines into paragraphs separated by blank lines."""
|
||||||
|
parts: List[str] = []
|
||||||
|
paragraph: List[str] = []
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
if line == "":
|
||||||
|
if paragraph:
|
||||||
|
parts.append(" ".join(segment.strip() for segment in paragraph))
|
||||||
|
paragraph = []
|
||||||
|
if parts and parts[-1] != "":
|
||||||
|
parts.append("")
|
||||||
|
continue
|
||||||
|
paragraph.append(line.strip())
|
||||||
|
|
||||||
|
if paragraph:
|
||||||
|
parts.append(" ".join(segment.strip() for segment in paragraph))
|
||||||
|
|
||||||
|
return "\n".join(parts).strip()
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_frontmatter_scalars(frontmatter_text: str) -> Dict[str, str]:
|
||||||
|
"""Parse simple top-level YAML frontmatter scalars, including block scalars."""
|
||||||
|
lines = _normalize_newlines(frontmatter_text).split("\n")
|
||||||
|
metadata: Dict[str, str] = {}
|
||||||
|
index = 0
|
||||||
|
|
||||||
|
while index < len(lines):
|
||||||
|
line = lines[index]
|
||||||
|
stripped = line.strip()
|
||||||
|
|
||||||
|
if not stripped or stripped.startswith("#") or line.startswith(" "):
|
||||||
|
index += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
match = _FRONTMATTER_KEY_RE.match(line)
|
||||||
|
if not match:
|
||||||
|
index += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
key = match.group(1)
|
||||||
|
raw_value = match.group(2).lstrip()
|
||||||
|
|
||||||
|
if raw_value[:1] in {"|", ">"}:
|
||||||
|
block_lines, index = _collect_frontmatter_block(lines, index + 1)
|
||||||
|
metadata[key] = (
|
||||||
|
"\n".join(block_lines).strip()
|
||||||
|
if raw_value.startswith("|")
|
||||||
|
else _fold_yaml_block(block_lines)
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
block_lines, next_index = _collect_frontmatter_block(lines, index + 1)
|
||||||
|
if block_lines:
|
||||||
|
segments = [_strip_matching_quotes(raw_value)] + [
|
||||||
|
segment.strip() for segment in block_lines
|
||||||
|
]
|
||||||
|
metadata[key] = _fold_yaml_block(segments)
|
||||||
|
index = next_index
|
||||||
|
continue
|
||||||
|
|
||||||
|
metadata[key] = _strip_matching_quotes(raw_value)
|
||||||
|
index += 1
|
||||||
|
|
||||||
|
return metadata
|
||||||
|
|
||||||
|
|
||||||
def _parse_skill_md_meta(content: str, fallback_name: str) -> Tuple[str, str, str]:
|
def _parse_skill_md_meta(content: str, fallback_name: str) -> Tuple[str, str, str]:
|
||||||
"""Parse markdown skill content into (name, description, body)."""
|
"""Parse markdown skill content into (name, description, body)."""
|
||||||
fm_match = re.match(r"^---\s*\n(.*?)\n---\s*\n", content, re.DOTALL)
|
normalized_content = _normalize_newlines(content)
|
||||||
|
fm_match = re.match(r"^---\s*\n(.*?)\n---\s*(?:\n|$)", normalized_content, re.DOTALL)
|
||||||
if fm_match:
|
if fm_match:
|
||||||
fm_text = fm_match.group(1)
|
fm_text = fm_match.group(1)
|
||||||
body = content[fm_match.end() :].strip()
|
body = normalized_content[fm_match.end() :].strip()
|
||||||
name = fallback_name
|
metadata = _parse_frontmatter_scalars(fm_text)
|
||||||
description = ""
|
name = (
|
||||||
for line in fm_text.split("\n"):
|
metadata.get("name")
|
||||||
m_name = re.match(r"^name:\s*(.+)$", line)
|
or metadata.get("title")
|
||||||
if m_name:
|
or fallback_name
|
||||||
name = m_name.group(1).strip().strip("\"'")
|
).strip()
|
||||||
m_desc = re.match(r"^description:\s*(.+)$", line)
|
description = (metadata.get("description") or "").strip()
|
||||||
if m_desc:
|
|
||||||
description = m_desc.group(1).strip().strip("\"'")
|
|
||||||
return name, description, body
|
return name, description, body
|
||||||
|
|
||||||
h1_match = re.search(r"^#\s+(.+)$", content.strip(), re.MULTILINE)
|
stripped_content = normalized_content.strip()
|
||||||
|
h1_match = re.search(r"^#\s+(.+)$", stripped_content, re.MULTILINE)
|
||||||
name = h1_match.group(1).strip() if h1_match else fallback_name
|
name = h1_match.group(1).strip() if h1_match else fallback_name
|
||||||
return name, "", content.strip()
|
return name, "", stripped_content
|
||||||
|
|
||||||
|
|
||||||
def _append_source_url_to_content(content: str, url: str, lang: str = "en-US") -> str:
|
def _append_source_url_to_content(content: str, url: str, lang: str = "en-US") -> str:
|
||||||
|
|||||||
15
plugins/tools/openwebui-skills-manager/v0.3.1.md
Normal file
15
plugins/tools/openwebui-skills-manager/v0.3.1.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# OpenWebUI Skills Manager v0.3.1 Release Notes
|
||||||
|
|
||||||
|
This patch release improves compatibility when importing external `SKILL.md` files by correctly handling multi-line YAML frontmatter descriptions and safer metadata fallbacks.
|
||||||
|
|
||||||
|
### New Features
|
||||||
|
- Support folded (`description: >`) and literal (`description: |`) multi-line descriptions during `install_skill`.
|
||||||
|
- Normalize CRLF/CR line endings before parsing skill frontmatter metadata.
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
- Fixed imported skill descriptions being truncated or reduced to a single line when source frontmatter used YAML block scalars.
|
||||||
|
- Added `title` as a name fallback when `name` is absent, reducing generic directory-name fallbacks for third-party skills.
|
||||||
|
|
||||||
|
### Enhancements
|
||||||
|
- Added focused regression tests for folded/literal YAML blocks and CRLF inputs.
|
||||||
|
- Synced README and docs mirrors so the latest release surface documents the new metadata compatibility.
|
||||||
15
plugins/tools/openwebui-skills-manager/v0.3.1_CN.md
Normal file
15
plugins/tools/openwebui-skills-manager/v0.3.1_CN.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# OpenWebUI Skills Manager v0.3.1 版本发布说明
|
||||||
|
|
||||||
|
这个补丁版本重点增强了外部 `SKILL.md` 导入兼容性,能够正确处理多行 YAML frontmatter 描述,并提供更稳健的元数据回退逻辑。
|
||||||
|
|
||||||
|
### 新功能
|
||||||
|
- `install_skill` 现已支持 folded(`description: >`)和 literal(`description: |`)两种多行描述格式。
|
||||||
|
- 在解析技能 frontmatter 之前,统一规范化 CRLF/CR 换行,提升跨平台兼容性。
|
||||||
|
|
||||||
|
### 问题修复
|
||||||
|
- 修复第三方 `SKILL.md` 使用 YAML 块标量时,导入后的技能描述被截断或压缩成单行的问题。
|
||||||
|
- 当 frontmatter 缺少 `name` 但提供了 `title` 时,新增 `title` 回退逻辑,避免退回到通用目录名。
|
||||||
|
|
||||||
|
### 优化提升
|
||||||
|
- 新增 folded/literal YAML 块与 CRLF 输入的定向回归测试。
|
||||||
|
- 同步更新 README 与 docs 镜像页,确保最新发布文案与元数据兼容行为保持一致。
|
||||||
63
tests/plugins/tools/test_openwebui_skills_manager.py
Normal file
63
tests/plugins/tools/test_openwebui_skills_manager.py
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import importlib.util
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
MODULE_PATH = (
|
||||||
|
Path(__file__).resolve().parents[3]
|
||||||
|
/ "plugins"
|
||||||
|
/ "tools"
|
||||||
|
/ "openwebui-skills-manager"
|
||||||
|
/ "openwebui_skills_manager.py"
|
||||||
|
)
|
||||||
|
SPEC = importlib.util.spec_from_file_location("openwebui_skills_manager", MODULE_PATH)
|
||||||
|
openwebui_skills_manager = importlib.util.module_from_spec(SPEC)
|
||||||
|
assert SPEC.loader is not None
|
||||||
|
sys.modules[SPEC.name] = openwebui_skills_manager
|
||||||
|
SPEC.loader.exec_module(openwebui_skills_manager)
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_skill_md_meta_supports_folded_multiline_description():
|
||||||
|
content = (
|
||||||
|
"---\r\n"
|
||||||
|
"name: persona-selector\r\n"
|
||||||
|
"description: >\r\n"
|
||||||
|
" Two-step persona picker. Step 1: numbered category list.\r\n"
|
||||||
|
" Step 2: numbered persona list. 160 personas + Custom.\r\n"
|
||||||
|
"---\r\n\r\n"
|
||||||
|
"# Persona Selector\r\n\r\n"
|
||||||
|
"Body content.\r\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
name, description, body = openwebui_skills_manager._parse_skill_md_meta(
|
||||||
|
content, "fallback-skill"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert name == "persona-selector"
|
||||||
|
assert description == (
|
||||||
|
"Two-step persona picker. Step 1: numbered category list. "
|
||||||
|
"Step 2: numbered persona list. 160 personas + Custom."
|
||||||
|
)
|
||||||
|
assert body == "# Persona Selector\n\nBody content."
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_skill_md_meta_supports_literal_multiline_description_and_title_fallback():
|
||||||
|
content = (
|
||||||
|
"---\n"
|
||||||
|
'title: "Data Storyteller"\n'
|
||||||
|
"description: |\n"
|
||||||
|
" First line.\n"
|
||||||
|
" Second line.\n"
|
||||||
|
"\n"
|
||||||
|
" Third paragraph.\n"
|
||||||
|
"---\n\n"
|
||||||
|
"Explain how to turn analysis into a narrative.\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
name, description, body = openwebui_skills_manager._parse_skill_md_meta(
|
||||||
|
content, "fallback-skill"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert name == "Data Storyteller"
|
||||||
|
assert description == "First line.\nSecond line.\n\nThird paragraph."
|
||||||
|
assert body == "Explain how to turn analysis into a narrative."
|
||||||
Reference in New Issue
Block a user