feat: 通用slash命令(/help/status/reset/tools/skills/model)+SDLC产线slash(/tasks/diagnose)+AgentExecutor按role加载技能

This commit is contained in:
ymq 2026-08-15 09:38:31 +08:00
parent 0a90f60315
commit c87b8d94ed
4 changed files with 164 additions and 2 deletions

View File

@ -31,6 +31,9 @@ from .agent_loop_v2 import AgentExecutor, run_agent
# 产线能力包注册(import 即注册到 pipeline_core 的 PipelineAbility 注册表) # 产线能力包注册(import 即注册到 pipeline_core 的 PipelineAbility 注册表)
from . import sdlc_ability # noqa: F401 from . import sdlc_ability # noqa: F401
# 通用 slash 命令注册(import 即注册)
from . import slash_commands # noqa: F401
# 应用部署主机逻辑账号系统(零 root 逻辑隔离 + bwrap 沙箱) # 应用部署主机逻辑账号系统(零 root 逻辑隔离 + bwrap 沙箱)
from .deploy_account import ( from .deploy_account import (
ensure_account, ensure_account,

View File

@ -59,12 +59,14 @@ class AgentExecutor:
user_id: str = "", user_id: str = "",
workspace_dir: str = "", workspace_dir: str = "",
model_name: str = None, model_name: str = None,
role: str = "", # 产线内角色(可选,如 develop/design;驾驶舱 agent 为空)
): ):
self.config = config self.config = config
self.project_id = project_id self.project_id = project_id
self.user_id = user_id self.user_id = user_id
self.org_id = "" # loaded from project context self.org_id = "" # loaded from project context
self.pipeline_id = "" # loaded from project context(产线能力包 key) self.pipeline_id = "" # loaded from project context(产线能力包 key)
self.role = role # 产线内角色(技能/工具/prompt 按角色加载)
self.workspace_dir = workspace_dir or "/tmp/pipeline_ws" self.workspace_dir = workspace_dir or "/tmp/pipeline_ws"
self.model_name = model_name or config.model_name self.model_name = model_name or config.model_name
@ -102,9 +104,29 @@ class AgentExecutor:
self._turn_count = 0 self._turn_count = 0
self._tool_call_count = 0 self._tool_call_count = 0
# Step 1: 初始化 # Step 0: 初始化(解析 pipeline_id / skill_loader 等,slash 命令也需要)
await self._init_components() 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: 加载历史 # Step 2: 加载历史
hist_msgs = await self._load_history(history) hist_msgs = await self._load_history(history)
@ -331,10 +353,13 @@ class AgentExecutor:
if mem_block: if mem_block:
prompt += f"\n\n## 持久记忆\n{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: if self.config.skills.enabled and self._skill_loader:
skill_block = self._skill_loader.build_prompt_block( skill_block = self._skill_loader.build_prompt_block(
user_input=user_input, 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 "", org_id=self.org_id if self.config.skills.enable_org else "",
user_id=self.user_id if self.config.skills.enable_user else "", user_id=self.user_id if self.config.skills.enable_user else "",
max_skills=self.config.skills.max_skills_per_turn, max_skills=self.config.skills.max_skills_per_turn,
@ -535,6 +560,13 @@ class AgentExecutor:
"config": self.config, "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]: async def _execute_ability_tool(self, tool_name: str, params: dict) -> Optional[str]:
"""产线能力包工具:按 pipeline_id 从 PipelineAbility 注册表取 handler 执行。""" """产线能力包工具:按 pipeline_id 从 PipelineAbility 注册表取 handler 执行。"""
try: try:

View File

@ -19,6 +19,8 @@ from pipeline_core import (
ToolDefinition, ToolDefinition,
PipelineAbility, PipelineAbility,
register_ability, register_ability,
SlashCommand,
register_slash_command,
) )
# ── 工具定义 ── # ── 工具定义 ──
@ -531,5 +533,28 @@ def register_sdlc_ability():
return 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 即生效 # import 即注册(模块加载副作用),pipeline-app 启动时 import pipeline_service 即生效
register_sdlc_ability() register_sdlc_ability()
register_sdlc_slash_commands()

View File

@ -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()