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 注册表)
from . import sdlc_ability # noqa: F401
# 通用 slash 命令注册import 即注册)
from . import slash_commands # noqa: F401
# 应用部署主机逻辑账号系统(零 root 逻辑隔离 + bwrap 沙箱)
from .deploy_account import (
ensure_account,

View File

@ -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:

View File

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

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