diff --git a/wwwroot/api/cockpit_chat.dspy b/wwwroot/api/cockpit_chat.dspy index 7d77304..aaa49e4 100644 --- a/wwwroot/api/cockpit_chat.dspy +++ b/wwwroot/api/cockpit_chat.dspy @@ -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'], '',