feat: skill import/list — _import_skill from external path, cockpit intents

This commit is contained in:
yumoqing 2026-08-03 19:20:15 +08:00
parent 4bef8305d4
commit 8a23cb0c14

View File

@ -92,6 +92,63 @@ def _build_skills_prompt(skills):
return '\n'.join(lines)
def _import_skill(skills_dir, source_path, role=None):
"""Import a SKILL.md file into skills_dir/{role}/{name}/SKILL.md.
source_path can be a file path or a directory containing SKILL.md.
Returns (success, message, skill_name)."""
if not skills_dir:
return False, 'skills_dir not configured', ''
src = os.path.abspath(source_path)
if not os.path.exists(src):
return False, f'路径不存在: {source_path}', ''
# Determine skill name and content
if os.path.isfile(src) and src.endswith('.md'):
skill_name = os.path.splitext(os.path.basename(src))[0]
with open(src, 'r') as f:
content = f.read()
elif os.path.isdir(src):
md = os.path.join(src, 'SKILL.md')
if not os.path.isfile(md):
return False, f'目录中未找到 SKILL.md: {src}', ''
skill_name = os.path.basename(src)
with open(md, 'r') as f:
content = f.read()
else:
return False, '源文件必须是 .md 文件或包含 SKILL.md 的目录', ''
if not role:
role = _guess_role_skill_name(skill_name)
dest_dir = os.path.join(skills_dir, role, skill_name)
os.makedirs(dest_dir, exist_ok=True)
dest = os.path.join(dest_dir, 'SKILL.md')
with open(dest, 'w') as f:
f.write(content)
return True, f'已导入 {role}/{skill_name}', skill_name
def _guess_role_skill_name(name):
"""Guess role from skill directory name."""
return _guess_role(name)
def _list_imported_skills(skills_dir):
"""List all skills currently in skills_dir."""
result = {}
if not skills_dir or not os.path.isdir(skills_dir):
return result
for role_dir in os.listdir(skills_dir):
rp = os.path.join(skills_dir, role_dir)
if not os.path.isdir(rp):
continue
skills = []
for sn in os.listdir(rp):
sp = os.path.join(rp, sn)
if os.path.isdir(sp) and os.path.isfile(os.path.join(sp, 'SKILL.md')):
skills.append(sn)
if skills:
result[role_dir] = skills
return result
async def _load_agent_settings(sor, uid):
"""Load user's agent settings, return defaults if not set."""
recs = await sor.sqlExe(
@ -315,13 +372,15 @@ INTENT_PROMPT = """你是一个开发产线意图分类器。分析用户输入
- start_agent: 启动Agent自动执行任务
- agent_status: 查看Agent状态和交付件
- query: 查询当前状态
- skill_list: 列出已导入的企业开发技能(如"查看技能""有哪些skills"
- skill_import: 导入技能文件(如"导入技能 /path/to/skill"
- chat: 开发相关的一般对话
- out_of_scope: 完全无关软件开发
当前上下文:项目={ctx},迭代={iter}
返回纯JSON不要markdown包裹
{"intent":"...","confidence":0.8,"project_name":"...","iteration_name":"...","title":"...","description":"...","missing_info":"..."}"""
{"intent":"...","confidence":0.8,"project_name":"...","iteration_name":"...","title":"...","description":"...","source_path":"...","role":"...","missing_info":"..."}"""
async def _classify_intent(model_info, message, ctx, history_msgs):
@ -358,6 +417,7 @@ SCOPE_GUIDE = """我可以帮你:
📝 提交任务 — "设计用户表结构" / "实现登录API"
🐛 Bug管理 — "登录页报500" / "我的Bug列表"
📊 查询 — "当前项目进度" / "有哪些迭代"
📚 技能管理 — "导入技能 /path/to/skill" / "查看技能列表"
请描述你的需求。"""
@ -603,6 +663,35 @@ if action == 'send_message':
for d in drecs:
lines.append(f" · {d.title} [{d.review_status}] {d.quality_score}分")
agent_reply = '\n'.join(lines) if len(lines) > 1 else "暂无Agent活动"
elif intent_type == 'skill_list':
skills_dir = ctx.get('skills_dir', '')
if not skills_dir:
agent_reply = "企业Skills目录未配置。请先在「组织SDLC设置」中设置 skills_dir。"
else:
listing = _list_imported_skills(skills_dir)
if not listing:
agent_reply = "暂无已导入的企业技能。\n\n技能目录结构应为\n {skills_dir}/\n common/技能名/SKILL.md\n design/技能名/SKILL.md\n develop/技能名/SKILL.md\n test/技能名/SKILL.md\n deploy/技能名/SKILL.md\n\n导入方式说「导入技能 /path/to/react-patterns」"
else:
lines = ["📚 已导入的企业技能:"]
for role, names in sorted(listing.items()):
lines.append(f"\n [{role}]")
for n in sorted(names):
lines.append(f" - {n}")
agent_reply = '\n'.join(lines)
elif intent_type == 'skill_import':
skills_dir = ctx.get('skills_dir', '')
source = intent.get('source_path', '') or message_text.split('导入技能')[-1].strip()
if not skills_dir:
agent_reply = "企业Skills目录未配置。请先在「组织SDLC设置」中设置 skills_dir。"
elif not source:
agent_reply = "请提供要导入的技能路径。例如:导入技能 /home/user/my-skill"
else:
role = intent.get('role', None)
ok, msg, name = _import_skill(skills_dir, source, role)
if ok:
agent_reply = f"✅ {msg}"
else:
agent_reply = f"❌ 导入失败:{msg}"
else:
# chat: general conversation
messages = await _build_context(sor, iteration_id or ctx['iteration_id'], '',