From c87b8d94ed080c7ad78c3d870a94d3a48583354a Mon Sep 17 00:00:00 2001 From: ymq Date: Sat, 15 Aug 2026 09:38:31 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=80=9A=E7=94=A8slash=E5=91=BD?= =?UTF-8?q?=E4=BB=A4(/help/status/reset/tools/skills/model)+SDLC=E4=BA=A7?= =?UTF-8?q?=E7=BA=BFslash(/tasks/diagnose)+AgentExecutor=E6=8C=89role?= =?UTF-8?q?=E5=8A=A0=E8=BD=BD=E6=8A=80=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pipeline_service/__init__.py | 3 + pipeline_service/agent_loop_v2.py | 36 +++++++++- pipeline_service/sdlc_ability.py | 25 +++++++ pipeline_service/slash_commands.py | 102 +++++++++++++++++++++++++++++ 4 files changed, 164 insertions(+), 2 deletions(-) create mode 100644 pipeline_service/slash_commands.py diff --git a/pipeline_service/__init__.py b/pipeline_service/__init__.py index 63203fb..e337807 100644 --- a/pipeline_service/__init__.py +++ b/pipeline_service/__init__.py @@ -31,6 +31,9 @@ from .agent_loop_v2 import AgentExecutor, run_agent # 产线能力包注册(import 即注册到 pipeline_core 的 PipelineAbility 注册表) from . import sdlc_ability # noqa: F401 +# 通用 slash 命令注册(import 即注册) +from . import slash_commands # noqa: F401 + # 应用部署主机逻辑账号系统(零 root 逻辑隔离 + bwrap 沙箱) from .deploy_account import ( ensure_account, diff --git a/pipeline_service/agent_loop_v2.py b/pipeline_service/agent_loop_v2.py index 7101045..ac0010d 100644 --- a/pipeline_service/agent_loop_v2.py +++ b/pipeline_service/agent_loop_v2.py @@ -59,12 +59,14 @@ class AgentExecutor: user_id: str = "", workspace_dir: str = "", model_name: str = None, + role: str = "", # 产线内角色(可选,如 develop/design;驾驶舱 agent 为空) ): self.config = config self.project_id = project_id self.user_id = user_id self.org_id = "" # loaded from project context self.pipeline_id = "" # loaded from project context(产线能力包 key) + self.role = role # 产线内角色(技能/工具/prompt 按角色加载) self.workspace_dir = workspace_dir or "/tmp/pipeline_ws" self.model_name = model_name or config.model_name @@ -102,9 +104,29 @@ class AgentExecutor: self._turn_count = 0 self._tool_call_count = 0 - # Step 1: 初始化 + # Step 0: 初始化(解析 pipeline_id / skill_loader 等,slash 命令也需要) await self._init_components() + # Step 0.5: slash 命令短路(/xxx 直接执行,不进入 LLM 循环) + if user_input and user_input.strip().startswith("/"): + from pipeline_core import resolve_slash_command, parse_slash_args + cmd = resolve_slash_command(user_input.strip(), self.pipeline_id, self.role) + if cmd and cmd.handler: + args = parse_slash_args(user_input.strip()) + try: + result = await cmd.handler(args, self._build_slash_ctx()) + except Exception as e: + logger.error(f"slash /{cmd.name} error: {e}") + result = f"ERROR: {str(e)[:300]}" + yield json.dumps({"type": "reply", "message": result}, ensure_ascii=False) + "\n" + return + # 未知 slash 命令:提示并列出可用命令 + from pipeline_core import get_slash_commands + visible = get_slash_commands(self.pipeline_id, self.role) + help_text = "未知命令。可用: " + ", ".join(f"/{n}" for n in sorted(visible)) + yield json.dumps({"type": "reply", "message": help_text}, ensure_ascii=False) + "\n" + return + # Step 2: 加载历史 hist_msgs = await self._load_history(history) @@ -331,10 +353,13 @@ class AgentExecutor: if mem_block: prompt += f"\n\n## 持久记忆\n{mem_block}" - # 注入技能(三级隔离:user > org > global) + # 注入技能(六级隔离:global→org→pipeline→role→project→user) if self.config.skills.enabled and self._skill_loader: skill_block = self._skill_loader.build_prompt_block( user_input=user_input, + pipeline_id=self.pipeline_id if self.config.skills.enable_pipeline else "", + role=self.role if self.config.skills.enable_role else "", + project_id=self.project_id if self.config.skills.enable_project else "", org_id=self.org_id if self.config.skills.enable_org else "", user_id=self.user_id if self.config.skills.enable_user else "", max_skills=self.config.skills.max_skills_per_turn, @@ -535,6 +560,13 @@ class AgentExecutor: "config": self.config, } + def _build_slash_ctx(self) -> dict: + """构造传给 slash 命令 handler 的上下文(含 executor 引用 + role)。""" + ctx = self._build_ctx() + ctx["executor"] = self + ctx["role"] = self.role + return ctx + async def _execute_ability_tool(self, tool_name: str, params: dict) -> Optional[str]: """产线能力包工具:按 pipeline_id 从 PipelineAbility 注册表取 handler 执行。""" try: diff --git a/pipeline_service/sdlc_ability.py b/pipeline_service/sdlc_ability.py index 181960d..ff837dd 100644 --- a/pipeline_service/sdlc_ability.py +++ b/pipeline_service/sdlc_ability.py @@ -19,6 +19,8 @@ from pipeline_core import ( ToolDefinition, PipelineAbility, register_ability, + SlashCommand, + register_slash_command, ) # ── 工具定义 ── @@ -531,5 +533,28 @@ def register_sdlc_ability(): return ability +# ── SDLC 产线 slash 命令(产线专属命名) ── + +async def _slash_tasks(args, ctx): + ex = ctx.get("executor") + r = await ex._execute_ability_tool("list_tasks", {"role": args.strip() or ""}) if ex else None + return r or "无任务" + + +async def _slash_diagnose(args, ctx): + ex = ctx.get("executor") + r = await ex._execute_ability_tool("diagnose_project", {}) if ex else None + return r or "无诊断数据" + + +def register_sdlc_slash_commands(): + for cmd in [ + SlashCommand("tasks", "列出任务(可按角色筛选)", _slash_tasks, "pipeline", "sdlc_general"), + SlashCommand("diagnose", "诊断项目状态", _slash_diagnose, "pipeline", "sdlc_general"), + ]: + register_slash_command(cmd) + + # import 即注册(模块加载副作用),pipeline-app 启动时 import pipeline_service 即生效 register_sdlc_ability() +register_sdlc_slash_commands() diff --git a/pipeline_service/slash_commands.py b/pipeline_service/slash_commands.py new file mode 100644 index 0000000..378561b --- /dev/null +++ b/pipeline_service/slash_commands.py @@ -0,0 +1,102 @@ +""" +pipeline-service: slash_commands — 通用 slash 命令(core 内核,产线无关) + +handler 签名:async def handler(args, ctx) -> str + ctx = {project_id, user_id, pipeline_id, workspace_dir, model_name, config, executor} + +通用 slash 命令(任何产线都可用):/help /status /reset /tools /skills /model +产线 slash 命令(SDLC 的 /tasks /diagnose 等)在 sdlc_ability 注册。 +""" + +import json +import logging + +from pipeline_core import SlashCommand, register_slash_command, get_slash_commands + +logger = logging.getLogger("pipeline.slash_commands") + + +async def _h_help(args, ctx): + visible = get_slash_commands(ctx.get("pipeline_id", ""), ctx.get("role", "")) + lines = ["可用命令:"] + for name in sorted(visible): + cmd = visible[name] + hint = f" {cmd.args_hint}" if cmd.args_hint else "" + lines.append(f" /{name}{hint} — {cmd.description}") + return "\n".join(lines) + + +async def _h_status(args, ctx): + executor = ctx.get("executor") + if not executor: + return "ERROR: 无 executor 上下文" + # 复用能力包诊断工具(SDLC 产线 → diagnose_project;其他产线 → 通用状态) + r = await executor._execute_ability_tool("diagnose_project", {}) + if r is None: + r = await executor._execute_ability_tool("check_progress", {}) + if r is None: + return "当前项目无状态诊断工具" + return r + + +async def _h_reset(args, ctx): + executor = ctx.get("executor") + if not executor: + return "ERROR: 无 executor 上下文" + executor._msgs = [] + executor._turn_count = 0 + executor._tool_call_count = 0 + executor._todos = [] + return "OK: 会话已重置(清空历史 + 任务清单)" + + +async def _h_tools(args, ctx): + cfg = ctx.get("config") + if not cfg: + return "无工具配置" + lines = ["可用工具:"] + for t in cfg.tools: + if getattr(t, "enabled", True): + params = ", ".join(f"{k}" for k in (t.parameters or {}).keys()) + lines.append(f" - {t.name}({params}) — {t.description}") + return "\n".join(lines) + + +async def _h_skills(args, ctx): + executor = ctx.get("executor") + if not executor or not executor._skill_loader: + return "技能系统未启用" + merged = executor._skill_loader.get_merged( + pipeline_id=ctx.get("pipeline_id", ""), + role=ctx.get("role", ""), + project_id=ctx.get("project_id", ""), + org_id=executor.org_id, + user_id=ctx.get("user_id", ""), + ) + if not merged: + return "暂无可用技能" + lines = ["可用技能:"] + for name, s in merged.items(): + lines.append(f" - [{s.scope}] {name} — {s.description[:100]}") + return "\n".join(lines) + + +async def _h_model(args, ctx): + return f"当前模型: {ctx.get('model_name', '未知')}" + + +# ── 注册通用 slash 命令 ── + +def register_general_slash_commands(): + for cmd in [ + SlashCommand("help", "列出可用命令", _h_help, "global"), + SlashCommand("status", "诊断当前项目状态", _h_status, "global"), + SlashCommand("reset", "重置会话(清空历史)", _h_reset, "global"), + SlashCommand("tools", "列出可用工具", _h_tools, "global"), + SlashCommand("skills", "列出可用技能", _h_skills, "global"), + SlashCommand("model", "显示当前模型", _h_model, "global"), + ]: + register_slash_command(cmd) + + +register_general_slash_commands()