feat(webui): add skills marketplace

This commit is contained in:
Xubin Ren
2026-07-30 01:13:37 +08:00
parent 129b74b4cf
commit ba0ba4749d
23 changed files with 3042 additions and 246 deletions
+18 -6
View File
@@ -70,6 +70,7 @@ class ContextBuilder:
def build_system_prompt(
self,
*,
active_skill_names: Sequence[str] | None = None,
channel: str | None = None,
session_summary: str | None = None,
workspace: Path | None = None,
@@ -91,13 +92,18 @@ class ContextBuilder:
if memory and not self._is_template_content(memory, "memory/MEMORY.md"):
parts.append(f"# Memory\n\n## Long-term Memory\n{memory}")
always_skills = self.skills.get_always_skills()
if always_skills:
always_content = self.skills.load_skills_for_context(always_skills)
if always_content:
parts.append(f"# Active Skills\n\n{always_content}")
active_skills = self.skills.get_always_skills()
active_skills.extend(
name
for name in (active_skill_names or ())
if name not in active_skills
)
if active_skills:
active_content = self.skills.load_skills_for_context(active_skills)
if active_content:
parts.append(f"# Active Skills\n\n{active_content}")
skills_summary = self.skills.build_skills_summary(exclude=set(always_skills))
skills_summary = self.skills.build_skills_summary(exclude=set(active_skills))
if skills_summary:
parts.append(render_template("agent/skills_section.md", skills_summary=skills_summary))
@@ -214,6 +220,11 @@ class ContextBuilder:
) -> list[dict[str, Any]]:
"""Build the complete message list for an LLM call."""
root = workspace or self.workspace
active_skill_names = (
self.skills.get_explicitly_invoked_skills(current_message)
if current_role == "user"
else []
)
user_content = self.build_user_content(current_message, image_paths=media)
blocks = list(runtime_context_blocks or ()) if current_role == "user" else []
merged, runtime_context_meta = append_runtime_context(user_content, blocks)
@@ -221,6 +232,7 @@ class ContextBuilder:
{
"role": "system",
"content": self.build_system_prompt(
active_skill_names=active_skill_names,
channel=channel,
session_summary=session_summary,
workspace=root,
+16
View File
@@ -17,6 +17,7 @@ _STRIP_SKILL_FRONTMATTER = re.compile(
r"^---\s*\r?\n(.*?)\r?\n---\s*\r?\n?",
re.DOTALL,
)
_SKILL_REFERENCE = re.compile(r"(?<![\w$])\$([A-Za-z0-9_-]+)")
class SkillsLoader:
@@ -109,6 +110,21 @@ class SkillsLoader:
]
return "\n\n---\n\n".join(parts)
def get_explicitly_invoked_skills(self, text: str) -> list[str]:
"""Resolve ``$skill-name`` references to enabled, available skills."""
if not text:
return []
available = {
entry["name"]
for entry in self.list_skills(filter_unavailable=True)
}
invoked: list[str] = []
for match in _SKILL_REFERENCE.finditer(text):
name = match.group(1)
if name in available and name not in invoked:
invoked.append(name)
return invoked
def build_skills_summary(self, exclude: set[str] | None = None) -> str:
"""
Build a summary of all skills (name, description, path, availability).