```
Some checks failed
CI / pytest (push) Has been cancelled
CI / gui-unit (push) Has been cancelled
CI / gui-e2e (push) Has been cancelled

feat(agent): 添加技能自动路由功能
- 引入 SkillRouter 实现根据用户消息自动推荐技能
- 在构建引擎时集成技能路由器
- 从对话历史中提取最后一条用户消息作为路由输入
- 为技能添加触发关键词和中文标题字段支持

refactor(browser): 重构浏览器自动化为子进程架构

- 将 Playwright 浏览器控制移至独立的子进程 worker
- 解决 PyInstaller 打包环境下 C 扩展兼容性问题
- 通过 JSON RPC 协议与浏览器 worker 通信
- 添加工具目录和脚本路径查找机制

feat(skills): 增强技能元数据和UI展示

- 为技能添加 triggers 和 title 字段
- 在技能商店中包含中文标题信息
- 添加 office-viz 技能优先级排序
- 在服务器管理器中返回技能标题

feat(gui): 实现技能选择器UI组件

- 添加带下拉菜单的技能选择器按钮
- 支持中文标题和拼音首字母显示
- 集成会话技能加载和状态管理
- 提供通用技能选项和已启用技能列表
```
This commit is contained in:
2026-09-14 17:36:02 +08:00
parent 6f402ffcee
commit 3e356b117a
10 changed files with 1493 additions and 306 deletions

View File

@@ -23,6 +23,8 @@ class Skill:
instructions: str = "" # full body — loaded on demand
path: Optional[str] = None
allowed_tools: list[str] = field(default_factory=list)
triggers: list[str] = field(default_factory=list) # 触发关键词,用于自动路由
title: str = "" # 中文标题,用于 UI 显示(如 "办公可视化看板"
class SkillLoader:
@@ -55,15 +57,23 @@ class SkillLoader:
return self._skills.get(name)
def catalog(self) -> list[dict]:
# 按优先级排序office-viz 排最前(数据分析可视化优先),其余按字母顺序
def sort_key(s: Skill) -> tuple[int, str]:
if s.name == "office-viz":
return (0, "")
return (1, s.name)
return [
{"name": s.name, "description": s.description}
for s in self._skills.values()
{"name": s.name, "description": s.description, "title": s.title}
for s in sorted(self._skills.values(), key=sort_key)
]
def _parse_skill(md: Path) -> Skill:
text = md.read_text(encoding="utf-8")
name, description, allowed, body = md.parent.name, "", [], text
triggers: list[str] = []
title: str = ""
if text.startswith("---"):
end = text.find("\n---", 3)
if end != -1:
@@ -80,12 +90,25 @@ def _parse_skill(md: Path) -> Skill:
description = value
elif key in ("allowed-tools", "allowed_tools"):
allowed = [t.strip() for t in value.split(",") if t.strip()]
elif key == "title":
title = value
elif key in ("triggers", "trigger-keywords", "trigger_keywords"):
# 支持逗号分隔: triggers: 关键词1, 关键词2, 关键词3
raw = value
# 去掉首尾可能的引号
if raw.startswith('"') and raw.endswith('"'):
raw = raw[1:-1]
elif raw.startswith("'") and raw.endswith("'"):
raw = raw[1:-1]
triggers = [t.strip() for t in raw.split(",") if t.strip()]
return Skill(
name=name,
description=description,
instructions=body.strip(),
path=str(md.parent),
allowed_tools=allowed,
triggers=triggers,
title=title,
)
@@ -97,8 +120,19 @@ def skill_catalog_text(
]
if not catalog:
return ""
lines = [f"- {c['name']}: {c['description']}" for c in catalog]
# 在列表前加优先级提示
priority_notice = (
"⚠️ Skill 选择优先级说明:\n"
" • 数据分析 + 可视化 + 看板 → 使用 office-viz不要用 xlsx\n"
" • 纯文件读写(无分析) → 使用 xlsx\n"
" • PDF 操作 → 使用 pdf\n"
" • ...\n\n"
)
return (
priority_notice +
"Available skills — call load_skill(name) to load one's full instructions when "
"it's relevant to the task:\n" + "\n".join(lines)
)