feat: 增强命令调用逻辑,支持通过 plugin.invoke_command 返回原始结构,并优化消息统计功能
This commit is contained in:
@@ -582,11 +582,13 @@ class TestComponentRegistry:
|
|||||||
|
|
||||||
match = reg.find_command_by_text("/help me")
|
match = reg.find_command_by_text("/help me")
|
||||||
assert match is not None
|
assert match is not None
|
||||||
assert match.name == "help"
|
comp, groups = match
|
||||||
|
assert comp.name == "help"
|
||||||
|
|
||||||
match = reg.find_command_by_text("/echo hello")
|
match = reg.find_command_by_text("/echo hello")
|
||||||
assert match is not None
|
assert match is not None
|
||||||
assert match.name == "echo"
|
comp, groups = match
|
||||||
|
assert comp.name == "echo"
|
||||||
|
|
||||||
match = reg.find_command_by_text("no match")
|
match = reg.find_command_by_text("no match")
|
||||||
assert match is None
|
assert match is None
|
||||||
|
|||||||
@@ -209,11 +209,22 @@ class PluginSupervisor:
|
|||||||
return result if isinstance(result, dict) else {}
|
return result if isinstance(result, dict) else {}
|
||||||
raise RuntimeError(payload.get("result", "workflow step invoke failed"))
|
raise RuntimeError(payload.get("result", "workflow step invoke failed"))
|
||||||
|
|
||||||
|
async def _command_invoke(plugin_id: str, component_name: str, args: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""命令走 plugin.invoke_command,保留原始返回值结构。"""
|
||||||
|
resp = await self.invoke_plugin(
|
||||||
|
method="plugin.invoke_command",
|
||||||
|
plugin_id=plugin_id,
|
||||||
|
component_name=component_name,
|
||||||
|
args=args,
|
||||||
|
)
|
||||||
|
return resp.payload
|
||||||
|
|
||||||
return await self._workflow_executor.execute(
|
return await self._workflow_executor.execute(
|
||||||
invoke_fn=_invoke,
|
invoke_fn=_invoke,
|
||||||
message=message,
|
message=message,
|
||||||
stream_id=stream_id,
|
stream_id=stream_id,
|
||||||
context=context,
|
context=context,
|
||||||
|
command_invoke_fn=_command_invoke,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def start(self) -> None:
|
async def start(self) -> None:
|
||||||
|
|||||||
@@ -118,9 +118,15 @@ class WorkflowExecutor:
|
|||||||
message: Optional[Dict[str, Any]] = None,
|
message: Optional[Dict[str, Any]] = None,
|
||||||
stream_id: Optional[str] = None,
|
stream_id: Optional[str] = None,
|
||||||
context: Optional[WorkflowContext] = None,
|
context: Optional[WorkflowContext] = None,
|
||||||
|
command_invoke_fn: Optional[InvokeFn] = None,
|
||||||
) -> Tuple[WorkflowResult, Optional[Dict[str, Any]], WorkflowContext]:
|
) -> Tuple[WorkflowResult, Optional[Dict[str, Any]], WorkflowContext]:
|
||||||
"""执行 workflow pipeline。
|
"""执行 workflow pipeline。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
invoke_fn: 用于 workflow_step 的回调
|
||||||
|
command_invoke_fn: 用于 command 的回调(走 plugin.invoke_command),
|
||||||
|
未传则复用 invoke_fn
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
(result, final_message, context)
|
(result, final_message, context)
|
||||||
"""
|
"""
|
||||||
@@ -136,7 +142,7 @@ class WorkflowExecutor:
|
|||||||
# PLAN 阶段: 先做 Command 路由
|
# PLAN 阶段: 先做 Command 路由
|
||||||
if stage == "plan" and current_message:
|
if stage == "plan" and current_message:
|
||||||
cmd_result = await self._route_command(
|
cmd_result = await self._route_command(
|
||||||
invoke_fn, current_message, ctx
|
command_invoke_fn or invoke_fn, current_message, ctx
|
||||||
)
|
)
|
||||||
if cmd_result is not None:
|
if cmd_result is not None:
|
||||||
# 命令匹配成功,跳过 PLAN 阶段的 hook,直接存结果进 stage_outputs
|
# 命令匹配成功,跳过 PLAN 阶段的 hook,直接存结果进 stage_outputs
|
||||||
@@ -377,10 +383,12 @@ class WorkflowExecutor:
|
|||||||
if not plain_text:
|
if not plain_text:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
matched = self._registry.find_command_by_text(plain_text)
|
match_result = self._registry.find_command_by_text(plain_text)
|
||||||
if matched is None:
|
if match_result is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
matched, matched_groups = match_result
|
||||||
|
|
||||||
ctx.matched_command = matched.full_name
|
ctx.matched_command = matched.full_name
|
||||||
logger.info(f"[{ctx.trace_id}] 命令匹配: {matched.full_name}")
|
logger.info(f"[{ctx.trace_id}] 命令匹配: {matched.full_name}")
|
||||||
|
|
||||||
@@ -389,6 +397,7 @@ class WorkflowExecutor:
|
|||||||
"text": plain_text,
|
"text": plain_text,
|
||||||
"message": message,
|
"message": message,
|
||||||
"trace_id": ctx.trace_id,
|
"trace_id": ctx.trace_id,
|
||||||
|
"matched_groups": matched_groups,
|
||||||
})
|
})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[{ctx.trace_id}] 命令 {matched.full_name} 执行失败: {e}", exc_info=True)
|
logger.error(f"[{ctx.trace_id}] 命令 {matched.full_name} 执行失败: {e}", exc_info=True)
|
||||||
|
|||||||
@@ -1060,7 +1060,7 @@ class PluginRuntimeManager:
|
|||||||
async def _cap_message_count_new(plugin_id: str, capability: str, args: Dict[str, Any]) -> Any:
|
async def _cap_message_count_new(plugin_id: str, capability: str, args: Dict[str, Any]) -> Any:
|
||||||
"""统计新消息数量
|
"""统计新消息数量
|
||||||
|
|
||||||
args: chat_id, start_time?, end_time?
|
args: chat_id, since? | start_time?, end_time?
|
||||||
"""
|
"""
|
||||||
from src.services import message_service as message_api
|
from src.services import message_service as message_api
|
||||||
|
|
||||||
@@ -1069,9 +1069,12 @@ class PluginRuntimeManager:
|
|||||||
return {"success": False, "error": "缺少必要参数 chat_id"}
|
return {"success": False, "error": "缺少必要参数 chat_id"}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# 兼容 SDK 传 since 和直接传 start_time 两种方式
|
||||||
|
since = args.get("since")
|
||||||
|
start_time = float(since) if since is not None else float(args.get("start_time", 0.0))
|
||||||
count = message_api.count_new_messages(
|
count = message_api.count_new_messages(
|
||||||
chat_id=chat_id,
|
chat_id=chat_id,
|
||||||
start_time=float(args.get("start_time", 0.0)),
|
start_time=start_time,
|
||||||
end_time=args.get("end_time"),
|
end_time=args.get("end_time"),
|
||||||
)
|
)
|
||||||
return {"success": True, "count": count}
|
return {"success": True, "count": count}
|
||||||
@@ -1083,21 +1086,29 @@ class PluginRuntimeManager:
|
|||||||
async def _cap_message_build_readable(plugin_id: str, capability: str, args: Dict[str, Any]) -> Any:
|
async def _cap_message_build_readable(plugin_id: str, capability: str, args: Dict[str, Any]) -> Any:
|
||||||
"""将消息列表构建成可读字符串
|
"""将消息列表构建成可读字符串
|
||||||
|
|
||||||
args: chat_id, start_time, end_time, limit?, replace_bot_name?, timestamp_mode?
|
支持两种调用方式:
|
||||||
|
1. SDK 方式: 传入 messages(已查询的消息列表)
|
||||||
|
2. 直接方式: 传入 chat_id + start_time + end_time(Host 端查询)
|
||||||
|
|
||||||
|
args: messages? | chat_id?, start_time?, end_time?, limit?, replace_bot_name?, timestamp_mode?
|
||||||
"""
|
"""
|
||||||
from src.services import message_service as message_api
|
from src.services import message_service as message_api
|
||||||
|
|
||||||
chat_id: str = args.get("chat_id", "")
|
|
||||||
if not chat_id:
|
|
||||||
return {"success": False, "error": "缺少必要参数 chat_id"}
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
messages = message_api.get_messages_by_time_in_chat(
|
# 优先使用调用方已提供的消息列表
|
||||||
chat_id=chat_id,
|
messages = args.get("messages")
|
||||||
start_time=float(args.get("start_time", 0.0)),
|
if messages is None:
|
||||||
end_time=float(args.get("end_time", 0.0)),
|
# 回退到 chat_id + 时间范围查询
|
||||||
limit=args.get("limit", 0),
|
chat_id: str = args.get("chat_id", "")
|
||||||
)
|
if not chat_id:
|
||||||
|
return {"success": False, "error": "缺少必要参数: messages 或 chat_id"}
|
||||||
|
messages = message_api.get_messages_by_time_in_chat(
|
||||||
|
chat_id=chat_id,
|
||||||
|
start_time=float(args.get("start_time", 0.0)),
|
||||||
|
end_time=float(args.get("end_time", 0.0)),
|
||||||
|
limit=args.get("limit", 0),
|
||||||
|
)
|
||||||
|
|
||||||
readable = message_api.build_readable_messages_to_str(
|
readable = message_api.build_readable_messages_to_str(
|
||||||
messages=messages,
|
messages=messages,
|
||||||
replace_bot_name=args.get("replace_bot_name", True),
|
replace_bot_name=args.get("replace_bot_name", True),
|
||||||
|
|||||||
Reference in New Issue
Block a user