qa296 Claude Sonnet 4.5 commited on
Commit
356efc7
·
1 Parent(s): 2ab31a1

fix: resolve plugin class instantiation and tool registration issues

Browse files

- Fix PluginManager being incorrectly detected as plugin class by checking module.__module__
- Change tool registration from module functions to instance methods
- Rename network plugin to net to avoid conflicts
- Add self_evolution plugin manifest for self-programming capability
- Ensure all 4 core plugins load with 5 total tools

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

backend/plugins/core/{network → net}/main.py RENAMED
File without changes
backend/plugins/core/{network → net}/manifest.json RENAMED
File without changes
backend/plugins/core/self_evolution/main.py ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Self-Evolution Plugin
3
+ 允许Agent创建新插件 - 自我进化的核心能力!
4
+ """
5
+ import os
6
+ import ast
7
+ import json
8
+ from typing import Dict, Any
9
+ from plugins.manager import PluginManager
10
+
11
+
12
+ class SelfEvolutionPlugin:
13
+ """自我进化插件"""
14
+
15
+ def __init__(self, manager: PluginManager):
16
+ self.manager = manager
17
+
18
+ def load(self):
19
+ """插件加载"""
20
+ pass
21
+
22
+ def unload(self):
23
+ """插件卸载"""
24
+ pass
25
+
26
+ async def create_plugin(self, name: str, code: str, reason: str) -> Dict[str, Any]:
27
+ """
28
+ 创建并安装新插件
29
+
30
+ 这是agent自我进化的核心能力!
31
+
32
+ Args:
33
+ name: 插件名称(将作为目录名和工具名)
34
+ code: Python代码(必须包含async def函数)
35
+ reason: 创建这个插件的理由
36
+
37
+ Returns:
38
+ {
39
+ "success": bool,
40
+ "plugin_name": str,
41
+ "path": str,
42
+ "message": str,
43
+ "error": str (if failed)
44
+ }
45
+ """
46
+ # 验证插件名称
47
+ if not name or not name.replace("_", "").isalnum():
48
+ return {
49
+ "success": False,
50
+ "plugin_name": name,
51
+ "error": f"Invalid plugin name: {name}. Only letters, numbers and underscores allowed"
52
+ }
53
+
54
+ # 验证代码语法
55
+ try:
56
+ ast.parse(code)
57
+ except SyntaxError as e:
58
+ return {
59
+ "success": False,
60
+ "plugin_name": name,
61
+ "error": f"Syntax error: {str(e)}"
62
+ }
63
+
64
+ # 验证包含异步函数
65
+ if "async def" not in code:
66
+ return {
67
+ "success": False,
68
+ "plugin_name": name,
69
+ "error": "Code must contain at least one async def function"
70
+ }
71
+
72
+ # 确保custom目录存在
73
+ custom_dir = "plugins/custom"
74
+ os.makedirs(custom_dir, exist_ok=True)
75
+
76
+ # 创建插件目录
77
+ plugin_dir = os.path.join(custom_dir, name)
78
+
79
+ # 检查是否已存在
80
+ if os.path.exists(plugin_dir):
81
+ return {
82
+ "success": False,
83
+ "plugin_name": name,
84
+ "error": f"Plugin already exists: {name}. Delete old version first"
85
+ }
86
+
87
+ os.makedirs(plugin_dir, exist_ok=True)
88
+
89
+ # 写入manifest.json
90
+ manifest = {
91
+ "name": name,
92
+ "version": "1.0.0",
93
+ "author": "entelechy_agent",
94
+ "description": reason,
95
+ "type": "custom",
96
+ "created_at": __import__('datetime').datetime.now().isoformat()
97
+ }
98
+
99
+ manifest_path = os.path.join(plugin_dir, "manifest.json")
100
+ with open(manifest_path, 'w', encoding='utf-8') as f:
101
+ json.dump(manifest, f, ensure_ascii=False, indent=2)
102
+
103
+ # 写入main.py
104
+ main_path = os.path.join(plugin_dir, "main.py")
105
+ plugin_content = f'''"""
106
+ Custom Plugin: {name}
107
+ Created by agent for self-evolution
108
+
109
+ Reason: {reason}
110
+ """
111
+ {code}
112
+ '''
113
+
114
+ with open(main_path, 'w', encoding='utf-8') as f:
115
+ f.write(plugin_content)
116
+
117
+ # 保存元数据
118
+ metadata = {
119
+ "name": name,
120
+ "created_at": manifest["created_at"],
121
+ "reason": reason,
122
+ "type": "custom"
123
+ }
124
+
125
+ return {
126
+ "success": True,
127
+ "plugin_name": name,
128
+ "path": plugin_dir,
129
+ "message": f"Plugin created: {name}",
130
+ "metadata": metadata
131
+ }
backend/plugins/custom/parse_html.py DELETED
@@ -1,26 +0,0 @@
1
- """
2
- Custom Plugin: parse_html
3
- Created by agent for self-evolution
4
-
5
- Reason: 需要从HTML网页中提取可读的文本内容
6
- """
7
-
8
- import re
9
-
10
- async def parse_html(html: str) -> str:
11
- """
12
- 从HTML中提取纯文本内容
13
-
14
- Args:
15
- html: HTML字符串
16
-
17
- Returns:
18
- 纯文本内容
19
- """
20
- # 移除所有HTML标签
21
- text = re.sub(r'<[^>]+>', '', html)
22
- # 移除多余的空白
23
- text = re.sub(r'\s+', ' ', text)
24
- # 去除首尾空白
25
- return text.strip()
26
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/plugins/manager.py CHANGED
@@ -10,7 +10,7 @@ import json
10
  import shutil
11
  from typing import Dict, List, Callable, Any, Optional
12
  from datetime import datetime
13
- from dataclasses import dataclass
14
 
15
 
16
  @dataclass
@@ -24,6 +24,7 @@ class PluginMetadata:
24
  path: str
25
  created_at: Optional[str] = None
26
  enabled: bool = True
 
27
 
28
 
29
  class PluginManager:
@@ -35,6 +36,7 @@ class PluginManager:
35
  self.plugin_instances: Dict[str, object] = {} # 插件实例: plugin_name -> instance
36
  self.core_dir = "plugins/core"
37
  self.custom_dir = "plugins/custom"
 
38
 
39
  def load_all_plugins(self):
40
  """加载所有插件"""
@@ -57,7 +59,7 @@ class PluginManager:
57
  plugin_path = os.path.join(directory, item)
58
 
59
  # 跳过 __pycache__ 和非目录
60
- if item.startswith('__') or not os.path.isdir(plugin_path):
61
  continue
62
 
63
  self._load_plugin(plugin_path, item, plugin_type)
@@ -99,27 +101,35 @@ class PluginManager:
99
  module = importlib.util.module_from_spec(spec)
100
  spec.loader.exec_module(module)
101
 
102
- # 查找插件类
103
  plugin_class = None
 
104
  for item_name, item in inspect.getmembers(module):
105
- if inspect.isclass(item) and not item_name.startswith('__'):
106
  plugin_class = item
107
  break
108
 
109
  if plugin_class is None:
110
- # 兼容旧版:没有类定义,直接找函数
111
- self._register_functions_from_module(module, plugin_name, manifest, plugin_path, plugin_type)
 
 
 
 
 
 
 
112
  return
113
 
114
- # 实例化插件类(传入 PluginManager 作为参数)
115
- plugin_instance = plugin_class(self)
116
 
117
  # 调用插件的 load 方法(如果存在)
118
  if hasattr(plugin_instance, 'load'):
119
  plugin_instance.load()
120
 
121
- # 注册插件中的工具函数
122
- self._register_functions_from_module(module, plugin_name, manifest, plugin_path, plugin_type, instance=plugin_instance)
123
 
124
  # 保存元数据和实例
125
  metadata = PluginMetadata(
@@ -140,12 +150,12 @@ class PluginManager:
140
  except Exception as e:
141
  print(f" {plugin_name}: 加载失败 - {str(e)}")
142
 
143
- def _register_functions_from_module(self, module, plugin_name: str, manifest: dict,
144
- plugin_path: str, plugin_type: str, instance: Optional[object] = None):
145
- """从模块注册工具函数"""
146
- for func_name, func in inspect.getmembers(module):
147
- if inspect.iscoroutinefunction(func) and not func_name.startswith('_'):
148
- # 注册为工具
149
  tool_name = func_name
150
  self.plugins[tool_name] = func
151
 
@@ -190,8 +200,8 @@ class PluginManager:
190
  instance.unload()
191
 
192
  # 移除旧的工具函数
193
- if "tools" in metadata:
194
- for tool_name in metadata["tools"]:
195
  if tool_name in self.plugins:
196
  del self.plugins[tool_name]
197
 
@@ -227,8 +237,8 @@ class PluginManager:
227
  instance.unload()
228
 
229
  # 移除工具函数
230
- if "tools" in metadata:
231
- for tool_name in metadata["tools"]:
232
  if tool_name in self.plugins:
233
  del self.plugins[tool_name]
234
 
@@ -252,7 +262,7 @@ class PluginManager:
252
  "author": meta.author,
253
  "description": meta.description,
254
  "type": meta.plugin_type,
255
- "tools": self._get_plugin_tools(name),
256
  "enabled": meta.enabled
257
  }
258
  for name, meta in self.plugin_metadata.items()
@@ -261,8 +271,8 @@ class PluginManager:
261
  def _get_plugin_tools(self, plugin_name: str) -> List[str]:
262
  """获取插件提供的所有工具"""
263
  meta = self.plugin_metadata.get(plugin_name)
264
- if meta and "tools" in meta.__dict__:
265
- return meta.tools or []
266
  return []
267
 
268
  def get_plugin_names(self) -> List[str]:
 
10
  import shutil
11
  from typing import Dict, List, Callable, Any, Optional
12
  from datetime import datetime
13
+ from dataclasses import dataclass, field
14
 
15
 
16
  @dataclass
 
24
  path: str
25
  created_at: Optional[str] = None
26
  enabled: bool = True
27
+ tools: List[str] = field(default_factory=list) # 该插件提供的工具列表
28
 
29
 
30
  class PluginManager:
 
36
  self.plugin_instances: Dict[str, object] = {} # 插件实例: plugin_name -> instance
37
  self.core_dir = "plugins/core"
38
  self.custom_dir = "plugins/custom"
39
+ self.pm = self # 保存对自身的引用,供插件使用
40
 
41
  def load_all_plugins(self):
42
  """加载所有插件"""
 
59
  plugin_path = os.path.join(directory, item)
60
 
61
  # 跳过 __pycache__ 和非目录
62
+ if item.startswith("__") or not os.path.isdir(plugin_path):
63
  continue
64
 
65
  self._load_plugin(plugin_path, item, plugin_type)
 
101
  module = importlib.util.module_from_spec(spec)
102
  spec.loader.exec_module(module)
103
 
104
+ # 查找插件类(必须是本模块定义的,不是导入的)
105
  plugin_class = None
106
+ module_name = module.__name__
107
  for item_name, item in inspect.getmembers(module):
108
+ if inspect.isclass(item) and not item_name.startswith("__") and item.__module__ == module_name:
109
  plugin_class = item
110
  break
111
 
112
  if plugin_class is None:
113
+ # 兼容旧版:没有类定义,直接从模块找函数
114
+ for func_name, func in inspect.getmembers(module):
115
+ if inspect.iscoroutinefunction(func) and not func_name.startswith("_"):
116
+ tool_name = func_name
117
+ self.plugins[tool_name] = func
118
+ if "tools" not in manifest:
119
+ manifest["tools"] = []
120
+ if tool_name not in manifest["tools"]:
121
+ manifest["tools"].append(tool_name)
122
  return
123
 
124
+ # 实例化插件类(传递 PluginManager 实例作为位置参数)
125
+ plugin_instance = plugin_class(self.pm)
126
 
127
  # 调用插件的 load 方法(如果存在)
128
  if hasattr(plugin_instance, 'load'):
129
  plugin_instance.load()
130
 
131
+ # 注册插件中的工具函数(从实例的方法中查找)
132
+ self._register_functions_from_instance(plugin_instance, plugin_name, manifest, plugin_path, plugin_type)
133
 
134
  # 保存元数据和实例
135
  metadata = PluginMetadata(
 
150
  except Exception as e:
151
  print(f" {plugin_name}: 加载失败 - {str(e)}")
152
 
153
+ def _register_functions_from_instance(self, instance, plugin_name: str, manifest: dict,
154
+ plugin_path: str, plugin_type: str):
155
+ """从插件实例注册工具函数(async方法)"""
156
+ for func_name, func in inspect.getmembers(instance):
157
+ if inspect.ismethod(func) and inspect.iscoroutinefunction(func) and not func_name.startswith("_"):
158
+ # 注册为工具(保存实例绑定的方法)
159
  tool_name = func_name
160
  self.plugins[tool_name] = func
161
 
 
200
  instance.unload()
201
 
202
  # 移除旧的工具函数
203
+ if metadata.tools:
204
+ for tool_name in metadata.tools:
205
  if tool_name in self.plugins:
206
  del self.plugins[tool_name]
207
 
 
237
  instance.unload()
238
 
239
  # 移除工具函数
240
+ if metadata.tools:
241
+ for tool_name in metadata.tools:
242
  if tool_name in self.plugins:
243
  del self.plugins[tool_name]
244
 
 
262
  "author": meta.author,
263
  "description": meta.description,
264
  "type": meta.plugin_type,
265
+ "tools": meta.tools,
266
  "enabled": meta.enabled
267
  }
268
  for name, meta in self.plugin_metadata.items()
 
271
  def _get_plugin_tools(self, plugin_name: str) -> List[str]:
272
  """获取插件提供的所有工具"""
273
  meta = self.plugin_metadata.get(plugin_name)
274
+ if meta:
275
+ return meta.tools
276
  return []
277
 
278
  def get_plugin_names(self) -> List[str]: