Coverage for merco/plugins/base.py: 96%
47 statements
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-07 14:04 +0800
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-07 14:04 +0800
1"""Plugin base class + PluginContext"""
2from __future__ import annotations
4from abc import ABC, abstractmethod
5from typing import TYPE_CHECKING
7if TYPE_CHECKING:
8 from merco.hooks.registry import HookRegistry
9 from merco.tools.registry import ToolRegistry
10 from merco.core.agent import PromptBuilder, PromptChunk, Agent
11 from merco.core.pipeline import RecoveryPipeline, ResultPipeline
12 from merco.memory.save_pipeline import MemorySavePipeline
13 from merco.memory.recall import HybridRecaller, BaseRecaller
14 from merco.core.config import MercoConfig
15 from merco.observability.observer import Observer
16 from merco.tools.base import BaseTool
17 from merco.todo.manager import TodoManager
18 from merco.agents.profile import AgentProfileRegistry
19 from merco.agents.subagent import SubAgentManager
20 from merco.context.pipeline import ContextPipeline
21 from merco.memory.backend import MemoryBackendRegistry
22 from merco.core.loop_policy import LoopPolicyRegistry
23 from merco.skills.registry import SkillRegistry
24 from merco.mcp.manager import MCPServerManager
25 from merco.scheduler.cron import CronScheduler
26 from typing import Callable
29_PIPELINE_WHITELIST = {
30 "result_pipeline",
31 "recovery_pipeline",
32 "memory_save_pipeline",
33 "context_pipeline",
34}
37class Plugin(ABC):
38 """merco plugin base class"""
39 name: str = "" # unique identifier
40 version: str = "" # semantic version
41 description: str = "" # one-line description
43 @abstractmethod
44 async def activate(self, ctx: "PluginContext") -> None:
45 """Called on activation; ctx provides all extension points"""
46 ...
48 async def deactivate(self) -> None:
49 """Called on deactivation to clean up resources. Default no-op."""
50 pass
53class PluginContext:
54 """Extension point entry for plugins, injected during activate"""
56 def __init__(
57 self,
58 hooks: "HookRegistry",
59 tool_registry: "ToolRegistry",
60 prompt_builder: "PromptBuilder",
61 recovery_pipeline: "RecoveryPipeline",
62 result_pipeline: "ResultPipeline",
63 memory_save_pipeline: "MemorySavePipeline",
64 recaller: "HybridRecaller",
65 config: "MercoConfig",
66 observer: "Observer" = None,
67 todo_manager: "TodoManager" = None,
68 sub_agent_manager: "SubAgentManager" = None,
69 context_pipeline: "ContextPipeline" = None,
70 agent_profiles: "AgentProfileRegistry" = None,
71 memory_backends: "MemoryBackendRegistry" = None,
72 loop_policies: "LoopPolicyRegistry" = None,
73 agent: "Agent" = None,
74 skill_registry: "SkillRegistry" = None,
75 mcp_manager: "MCPServerManager" = None,
76 scheduler: "CronScheduler" = None,
77 ):
78 self.hooks = hooks
79 self.tool_registry = tool_registry
80 self.prompt_builder = prompt_builder
81 self.recovery_pipeline = recovery_pipeline
82 self.result_pipeline = result_pipeline
83 self.memory_save_pipeline = memory_save_pipeline
84 self.recaller = recaller
85 self.config = config
86 self.observer = observer
87 self.todo_manager = todo_manager
88 self.sub_agent_manager = sub_agent_manager
89 self.context_pipeline = context_pipeline
90 self.agent_profiles = agent_profiles
91 self.memory_backends = memory_backends
92 self.loop_policies = loop_policies
93 self.agent = agent
94 self.skill_registry = skill_registry
95 self.mcp_manager = mcp_manager
96 self.scheduler = scheduler
98 def on(self, event: str, handler: "Callable") -> None:
99 """Subscribe to event (convenience method)"""
100 self.hooks.on(event, handler)
102 def register_tool(self, tool: "BaseTool") -> None:
103 """Register a tool"""
104 self.tool_registry.register(tool)
106 def add_prompt_chunk(self, chunk: "PromptChunk") -> None:
107 """Inject a system prompt chunk"""
108 self.prompt_builder.use(chunk)
110 def add_processor(self, pipeline_name: str, processor) -> None:
111 """加处理器到白名单管线"""
112 if pipeline_name not in _PIPELINE_WHITELIST:
113 raise ValueError(f"Pipeline '{pipeline_name}' not extensible")
114 pipeline = getattr(self, pipeline_name, None)
115 if pipeline and hasattr(pipeline, 'use'):
116 pipeline.use(processor)
118 def add_recaller(self, recaller: "BaseRecaller") -> None:
119 """Add a memory recaller"""
120 self.recaller.add(recaller)