mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-06-10 09:25:57 +00:00
16391e35ab
* support slash skill activation * format slash skill activation * Preserve slash skill activation with uploads * Address slash skill review feedback * Address slash skill follow-up review * Fix lazy slash skill storage resolution * Keep slash skill activation out of system prompt * Address slash skill review issues * fix: harden slash skill command handling * feat(frontend): add slash skill autocomplete * fix: address slash skill review feedback * fix: preserve slash skill text for IM uploads
66 lines
1.9 KiB
Python
66 lines
1.9 KiB
Python
from __future__ import annotations
|
|
|
|
import re
|
|
from dataclasses import dataclass
|
|
|
|
from deerflow.skills.types import Skill
|
|
|
|
RESERVED_SLASH_SKILL_NAMES = frozenset({"bootstrap", "help", "memory", "models", "new", "status"})
|
|
_SLASH_SKILL_RE = re.compile(r"^/([a-z0-9]+(?:-[a-z0-9]+)*)(?:\s+|$)")
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class SlashSkillReference:
|
|
"""Parsed slash-skill command with the skill name and remaining task text."""
|
|
|
|
name: str
|
|
remaining_text: str
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class ResolvedSlashSkill:
|
|
"""Slash-skill activation resolved against enabled runtime-visible skills."""
|
|
|
|
skill: Skill
|
|
remaining_text: str
|
|
container_file_path: str
|
|
|
|
|
|
def parse_slash_skill_reference(text: str) -> SlashSkillReference | None:
|
|
"""Parse strict `/skill-name task` syntax, ignoring reserved control commands."""
|
|
match = _SLASH_SKILL_RE.match(text)
|
|
if not match:
|
|
return None
|
|
name = match.group(1)
|
|
if name in RESERVED_SLASH_SKILL_NAMES:
|
|
return None
|
|
return SlashSkillReference(
|
|
name=name,
|
|
remaining_text=text[match.end() :].lstrip(),
|
|
)
|
|
|
|
|
|
def resolve_slash_skill(
|
|
text: str,
|
|
skills: list[Skill],
|
|
*,
|
|
available_skills: set[str] | None = None,
|
|
container_base_path: str = "/mnt/skills",
|
|
) -> ResolvedSlashSkill | None:
|
|
"""Resolve text into an enabled, whitelisted skill activation if possible."""
|
|
reference = parse_slash_skill_reference(text)
|
|
if reference is None:
|
|
return None
|
|
if available_skills is not None and reference.name not in available_skills:
|
|
return None
|
|
|
|
skill = next((candidate for candidate in skills if candidate.name == reference.name and candidate.enabled), None)
|
|
if skill is None:
|
|
return None
|
|
|
|
return ResolvedSlashSkill(
|
|
skill=skill,
|
|
remaining_text=reference.remaining_text,
|
|
container_file_path=skill.get_container_file_path(container_base_path),
|
|
)
|