feat: 通用slash命令(/help/status/reset/tools/skills/model)+SDLC产线slash(/tasks/diagnose)+AgentExecutor按role加载技能
This commit is contained in:
parent
0a90f60315
commit
c87b8d94ed
@ -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,
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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()
|
||||
|
||||
102
pipeline_service/slash_commands.py
Normal file
102
pipeline_service/slash_commands.py
Normal 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()
|
||||
Loading…
x
Reference in New Issue
Block a user