1"""

2 AgentOS v1.14.3 — Plugin System & Tool Registry.

3

4 Hot-reloadable plugin architecture. Plugins can register:

5- Custom tools (sync/async functions)

6- Agent middleware (pre/post hooks)

7- Custom LLM providers

8- Custom memory backends

9- Custom protocols

10

11 Features:

12- Plugin discovery (scan directories / entry_points)

13- Hot-reload (watch filesystem changes)

14- Dependency resolution (plugin A depends on plugin B)

15- Version compatibility checks

16- Plugin sandboxing (restricted imports)

17- CLI for plugin management

18

19 Architecture:

20 PluginRegistry (singleton)

21 ├── Plugin[0]: slack_notifier

22 │ ├── tools: [send_slack]

23 │ ├── middleware: [audit_logger]

24 │ └── depends_on: []

25 ├── Plugin[1]: jira_integration

26 │ ├── tools: [create_issue, search_jira]

27 │ └── depends_on: []

28 └── ...

29"""

30

31 from __future__ import annotations

32

33 import asyncio

34 import importlib

35 import importlib.util

36 import inspect

37 import json

38 import os

39 import sys

40 import time

41 import uuid

42 from dataclasses import dataclass, field

43 from enum import Enum

44 from pathlib import Path

45 from typing import (

46 Any, Callable, Dict, List, Optional, Set, Tuple, Type, Union,

47)

48

49

50# ── Plugin Metadata ─────────────────────────

51

52

53 class PluginStatus(str, Enum):

54 REGISTERED = "registered" # 已注册但未加载

55 LOADED = "loaded" # 已加载但未激活

56 ACTIVE = "active" # 已激活，正在运行

57 ERROR = "error" # 加载失败

58 DISABLED = "disabled" # 已禁用

59

60

61@dataclass

62 class PluginManifest:

63 """插件清单 — 描述插件的元数据和能力。"""

64

65 name: str = ""

66 version: str = "0.1.0"

67 description: str = ""

68 author: str = ""

69 license: str = "MIT"

70

71 # Entry point

72 entry_point: str = "" # module:class or module:function

73 plugin_class: str = "" # 插件主类名

74

75 # Capabilities

76 provides_tools: List[str] = field(default_factory=list) # 提供的工具名

77 provides_middleware: List[str] = field(default_factory=list) # 提供的中间件

78 provides_providers: List[str] = field(default_factory=list) # 提供的 LLM 提供者

79 provides_backends: List[str] = field(default_factory=list) # 提供的后端

80

81 # Dependencies

82 depends_on: List[str] = field(default_factory=list) # 依赖的其他插件

83 min_agentos_version: str = "1.0.0"

84

85 # Discovery

86 discoverable: bool = True

87 auto_activate: bool = False # 加载后自动激活

88 tags: List[str] = field(default_factory=list)

89

90 @classmethod

91 def from_dict(cls, data: dict) -> "PluginManifest":

92 return cls(

93 name=data.get("name", ""),

94 version=data.get("version", "0.1.0"),

95 description=data.get("description", ""),

96 author=data.get("author", ""),

97 license=data.get("license", "MIT"),

98 entry_point=data.get("entry_point", ""),

99 plugin_class=data.get("plugin_class", ""),

100 provides_tools=data.get("provides_tools", []),

101 provides_middleware=data.get("provides_middleware", []),

102 provides_providers=data.get("provides_providers", []),

103 provides_backends=data.get("provides_backends", []),

104 depends_on=data.get("depends_on", []),

105 min_agentos_version=data.get("min_agentos_version", "1.0.0"),

106 discoverable=data.get("discoverable", True),

107 auto_activate=data.get("auto_activate", False),

108 tags=data.get("tags", []),

109 )

110

111 def to_dict(self) -> dict:

112 return {

113 "name": self.name,

114 "version": self.version,

115 "description": self.description,

116 "author": self.author,

117 "license": self.license,

118 "entry_point": self.entry_point,

119 "plugin_class": self.plugin_class,

120 "provides_tools": self.provides_tools,

121 "provides_middleware": self.provides_middleware,

122 "provides_providers": self.provides_providers,

123 "provides_backends": self.provides_backends,

124 "depends_on": self.depends_on,

125 "min_agentos_version": self.min_agentos_version,

126 }

127

128

129# ── Plugin Base ─────────────────────────────

130

131

132 class BasePlugin:

133 """插件基类 — 所有插件必须继承此类。

134

135 Lifecycle:

136 1. __init__() → registered

137 2. load() → loaded

138 3. activate() → active

139 4. deactivate() → loaded

140 5. unload() → registered

141

142 Usage:

143 class MyPlugin(BasePlugin):

144 manifest = PluginManifest(

145 name="my_plugin",

146 entry_point="my_package.plugin:MyPlugin",

147 provides_tools=["my_tool"],

148 )

149

150 def on_load(self):

151 self.register_tool("my_tool", my_function)

152

153 def on_activate(self):

154 print("Plugin activated!")

155 """

156

157 manifest: PluginManifest

158

159 def __init__(self):

160 self._status = PluginStatus.REGISTERED

161 self._tools: Dict[str, Callable] = {}

162 self._middleware: List[Callable] = []

163 self._config: Dict[str, Any] = {}

164

165 # ── Lifecycle ──

166

167 def load(self) -> None:

168 """加载插件（注册工具、中间件等）。"""

169 try:

170 self.on_load()

171 self._status = PluginStatus.LOADED

172 except Exception as e:

173 self._status = PluginStatus.ERROR

174 raise RuntimeError(f"Failed to load plugin {self.manifest.name}: {e}")

175

176 def activate(self) -> None:

177 """激活插件。"""

178 try:

179 self.on_activate()

180 self._status = PluginStatus.ACTIVE

181 except Exception as e:

182 self._status = PluginStatus.ERROR

183 raise RuntimeError(f"Failed to activate plugin {self.manifest.name}: {e}")

184

185 def deactivate(self) -> None:

186 """停用插件。"""

187 try:

188 self.on_deactivate()

189 self._status = PluginStatus.LOADED

190 except Exception:

191 pass

192

193 def unload(self) -> None:

194 """卸载插件。"""

195 try:

196 self.on_unload()

197 self._status = PluginStatus.REGISTERED

198 except Exception:

199 pass

200

201 # ── Hooks (override in subclasses) ──

202

203 def on_load(self) -> None:

204 """子类实现：加载时调用。"""

205 pass

206

207 def on_activate(self) -> None:

208 """子类实现：激活时调用。"""

209 pass

210

211 def on_deactivate(self) -> None:

212 """子类实现：停用时调用。"""

213 pass

214

215 def on_unload(self) -> None:

216 """子类实现：卸载时调用。"""

217 pass

218

219 # ── Tool Registration ──

220

221 def register_tool(self, name: str, func: Callable) -> None:

222 """注册工具函数。"""

223 self._tools[name] = func

224

225 def unregister_tool(self, name: str) -> None:

226 """注销工具函数。"""

227 self._tools.pop(name, None)

228

229 def register_middleware(self, middleware: Callable) -> None:

230 """注册中间件。"""

231 self._middleware.append(middleware)

232

233 # ── Properties ──

234

235 @property

236 def status(self) -> PluginStatus:

237 return self._status

238

239 @property

240 def tools(self) -> Dict[str, Callable]:

241 return dict(self._tools)

242

243 @property

244 def middleware(self) -> List[Callable]:

245 return list(self._middleware)

246

247 @property

248 def is_active(self) -> bool:

249 return self._status == PluginStatus.ACTIVE

250

251

252# ── Plugin Registry ─────────────────────────

253

254

255 class PluginRegistry:

256 """插件注册中心（单例）。

257

258 管理所有插件的生命周期、依赖解析、发现。

259

260 Usage:

261 registry = PluginRegistry()

262

263 # Discover from directory

264 registry.discover("/path/to/plugins/")

265

266 # Load and activate

267 registry.load_all()

268 registry.activate_all()

269

270 # Get all tools from active plugins

271 all_tools = registry.get_all_tools()

272 """

273

274 _instance: Optional["PluginRegistry"] = None

275

276 def __new__(cls) -> "PluginRegistry":

277 if cls._instance is None:

278 cls._instance = super().__new__(cls)

279 cls._instance._initialized = False

280 return cls._instance

281

282 def __init__(self):

283 if self._initialized:

284 return

285 self._initialized = True

286 self._plugins: Dict[str, BasePlugin] = {} # name → plugin instance

287 self._manifests: Dict[str, PluginManifest] = {} # name → manifest

288 self._tools_index: Dict[str, str] = {} # tool_name → plugin_name

289 self._discovery_paths: List[str] = []

290 self._watchers: List[Any] = [] # file watchers

291

292 # ── Registration ──

293

294 def register(self, plugin: BasePlugin) -> bool:

295 """注册插件。"""

296 name = plugin.manifest.name

297 if name in self._plugins:

298 return False

299

300 self._plugins[name] = plugin

301 self._manifests[name] = plugin.manifest

302 return True

303

304 def unregister(self, name: str) -> bool:

305 """注销插件。"""

306 plugin = self._plugins.get(name)

307 if plugin:

308 if plugin.is_active:

309 plugin.deactivate()

310 plugin.unload()

311

312 self._plugins.pop(name, None)

313 self._manifests.pop(name, None)

314

315 # Clean tool index

316 self._tools_index = {

317 tn: pn for tn, pn in self._tools_index.items() if pn != name

318 }

319 return True

320

321 # ── Discovery ──

322

323 def discover(self, path: str, recursive: bool = True) -> List[str]:

324 """从目录中发现插件。

325

326 扫描 plugin.json / agentos_plugin.json 文件。

327 """

328 discovered: List[str] = []

329 base = Path(path)

330

331 if not base.exists():

332 return discovered

333

334 pattern = "**/plugin.json" if recursive else "plugin.json"

335 for manifest_file in base.glob(pattern):

336 try:

337 with open(manifest_file, "r", encoding="utf-8") as f:

338 data = json.load(f)

339

340 manifest = PluginManifest.from_dict(data)

341 if manifest.discoverable:

342 self._manifests[manifest.name] = manifest

343 discovered.append(manifest.name)

344

345 except Exception:

346 continue

347

348 self._discovery_paths.append(path)

349 return discovered

350

351 def discover_entry_points(self, group: str = "agentos.plugins") -> int:

352 """通过 setuptools entry_points 发现插件。"""

353 try:

354 from importlib.metadata import entry_points

355

356 count = 0

357 for ep in entry_points(group=group):

358 try:

359 manifest = PluginManifest(

360 name=ep.name,

361 entry_point=ep.value,

362 )

363 self._manifests[ep.name] = manifest

364 count += 1

365 except Exception:

366 continue

367

368 return count

369 except ImportError:

370 return 0

371

372 # ── Loading ──

373

374 def load(self, name: str) -> Optional[BasePlugin]:

375 """加载单个插件。"""

376 manifest = self._manifests.get(name)

377 if not manifest:

378 return None

379

380 # Check dependencies

381 if not self._check_dependencies(manifest):

382 return None

383

384 # Load plugin class

385 plugin = self._instantiate_plugin(manifest)

386 if not plugin:

387 return None

388

389 try:

390 plugin.load()

391 self._plugins[name] = plugin

392

393 # Index tools

394 for tool_name in plugin.tools:

395 self._tools_index[tool_name] = name

396

397 return plugin

398 except Exception:

399 return None

400

401 def load_all(self) -> Dict[str, Optional[BasePlugin]]:

402 """加载所有已发现但未加载的插件（按依赖拓扑排序）。"""

403 results: Dict[str, Optional[BasePlugin]] = {}

404

405 order = self._resolve_order()

406

407 for name in order:

408 if name not in self._plugins:

409 results[name] = self.load(name)

410

411 return results

412

413 # ── Activation ──

414

415 def activate(self, name: str) -> bool:

416 """激活插件。"""

417 plugin = self._plugins.get(name)

418 if not plugin or plugin.status != PluginStatus.LOADED:

419 return False

420

421 try:

422 plugin.activate()

423 return True

424 except Exception:

425 return False

426

427 def activate_all(self) -> int:

428 """激活所有已加载的插件。"""

429 count = 0

430 for name, plugin in list(self._plugins.items()):

431 if plugin.status == PluginStatus.LOADED:

432 if self.activate(name):

433 count += 1

434 return count

435

436 # ── Hot Reload ──

437

438 def reload(self, name: str) -> bool:

439 """热重载插件（停用 → 卸载 → 重新加载 → 激活）。"""

440 plugin = self._plugins.get(name)

441 if not plugin:

442 return False

443

444 was_active = plugin.is_active

445

446 if was_active:

447 plugin.deactivate()

448 plugin.unload()

449

450 # Reload

451 new_plugin = self.load(name)

452 if not new_plugin:

453 return False

454

455 if was_active:

456 new_plugin.activate()

457

458 return True

459

460 def reload_all(self) -> int:

461 """重载所有插件。"""

462 count = 0

463 for name in list(self._plugins.keys()):

464 if self.reload(name):

465 count += 1

466 return count

467

468 # ── Queries ──

469

470 def get_plugin(self, name: str) -> Optional[BasePlugin]:

471 return self._plugins.get(name)

472

473 def get_tool(self, tool_name: str) -> Optional[Callable]:

474 """通过工具名获取工具函数。"""

475 plugin_name = self._tools_index.get(tool_name)

476 if not plugin_name:

477 return None

478 plugin = self._plugins.get(plugin_name)

479 if not plugin:

480 return None

481 return plugin.tools.get(tool_name)

482

483 def get_all_tools(self) -> Dict[str, Callable]:

484 """获取所有已激活插件的工具。"""

485 tools: Dict[str, Callable] = {}

486 for plugin in self._plugins.values():

487 if plugin.is_active:

488 tools.update(plugin.tools)

489 return tools

490

491 def list_plugins(self) -> List[dict]:

492 """列出所有插件及其状态。"""

493 result = []

494 for name, manifest in self._manifests.items():

495 plugin = self._plugins.get(name)

496 result.append({

497 "name": name,

498 "version": manifest.version,

499 "status": plugin.status.value if plugin else "not_loaded",

500 "description": manifest.description,

501 "tools": manifest.provides_tools,

502 "depends_on": manifest.depends_on,

503 })

504 return result

505

506 def get_active_count(self) -> int:

507 return sum(1 for p in self._plugins.values() if p.is_active)

508

509 # ── Internal ──

510

511 def _instantiate_plugin(self, manifest: PluginManifest) -> Optional[BasePlugin]:

512 """从 entry_point 实例化插件。"""

513 if not manifest.entry_point:

514 return None

515

516 try:

517 module_path, class_name = manifest.entry_point.split(":")

518 module = importlib.import_module(module_path)

519 cls = getattr(module, class_name)

520 instance = cls()

521

522 if not isinstance(instance, BasePlugin):

523 return None

524

525 return instance

526 except Exception:

527 return None

528

529 def _check_dependencies(self, manifest: PluginManifest) -> bool:

530 """检查插件依赖是否满足。"""

531 for dep in manifest.depends_on:

532 dep_plugin = self._plugins.get(dep)

533 if not dep_plugin or not dep_plugin.is_active:

534 return False

535 return True

536

537 def _resolve_order(self) -> List[str]:

538 """按依赖拓扑排序解析加载顺序。"""

539 # Kahn's algorithm

540 in_degree: Dict[str, int] = {}

541 graph: Dict[str, List[str]] = {}

542

543 for name in self._manifests:

544 in_degree[name] = 0

545 graph[name] = []

546

547 for name, manifest in self._manifests.items():

548 for dep in manifest.depends_on:

549 if dep in graph:

550 graph[dep].append(name)

551 in_degree[name] += 1

552

553 queue = [n for n, d in in_degree.items() if d == 0]

554 order = []

555

556 while queue:

557 node = queue.pop(0)

558 order.append(node)

559 for neighbor in graph.get(node, []):

560 in_degree[neighbor] -= 1

561 if in_degree[neighbor] == 0:

562 queue.append(neighbor)

563

564 return order

565

566

567# ── File Watcher for Hot Reload ─────────────

568

569

570 class PluginFileWatcher:

571 """文件变更监控器 — 检测到变更自动重载插件。

572

573 Usage:

574 watcher = PluginFileWatcher(registry)

575 await watcher.start()

576 """

577

578 def __init__(

579 self,

580 registry: PluginRegistry,

581 poll_interval: float = 2.0,

582 ):

583 self._registry = registry

584 self._poll_interval = poll_interval

585 self._running = False

586 self._task: Optional[asyncio.Task] = None

587 self._file_mtimes: Dict[str, float] = {}

588

589 async def start(self) -> None:

590 """启动监控。"""

591 self._running = True

592 self._snapshot_files()

593 self._task = asyncio.create_task(self._poll_loop())

594

595 async def stop(self) -> None:

596 """停止监控。"""

597 self._running = False

598 if self._task:

599 self._task.cancel()

600

601 def _snapshot_files(self) -> None:

602 """记录当前文件修改时间。"""

603 for path in self._registry._discovery_paths:

604 base = Path(path)

605 if not base.exists():

606 continue

607 for f in base.rglob("*.py"):

608 self._file_mtimes[str(f)] = f.stat().st_mtime

609 for f in base.rglob("plugin.json"):

610 self._file_mtimes[str(f)] = f.stat().st_mtime

611

612 async def _poll_loop(self) -> None:

613 while self._running:

614 await asyncio.sleep(self._poll_interval)

615 try:

616 self._check_and_reload()

617 except Exception:

618 pass

619

620 def _check_and_reload(self) -> None:

621 """检查文件变更并触发重载。"""

622 changed = False

623

624 for path in self._registry._discovery_paths:

625 base = Path(path)

626 if not base.exists():

627 continue

628 for f in base.rglob("*.py"):

629 fpath = str(f)

630 old_mtime = self._file_mtimes.get(fpath, 0)

631 new_mtime = f.stat().st_mtime

632 if new_mtime > old_mtime:

633 changed = True

634 self._file_mtimes[fpath] = new_mtime

635 for f in base.rglob("plugin.json"):

636 fpath = str(f)

637 old_mtime = self._file_mtimes.get(fpath, 0)

638 new_mtime = f.stat().st_mtime

639 if new_mtime > old_mtime:

640 changed = True

641 self._file_mtimes[fpath] = new_mtime

642

643 if changed:

644 self._registry.reload_all()

645

646

647# ── Built-in Plugins ────────────────────────

648

649

650 class AuditLoggerPlugin(BasePlugin):

651 """内置审计日志插件。"""

652

653 manifest = PluginManifest(

654 name="audit_logger",

655 version="1.0.0",

656 description="Built-in audit logging middleware",

657 provides_middleware=["audit_log"],

658 auto_activate=True,

659 )

660

661 def on_activate(self):

662 def audit_log(event_type: str, details: dict) -> None:

663 """记录审计事件。"""

664 log_entry = {

665 "timestamp": time.time(),

666 "event": event_type,

667 "details": details,

668 }

669 # In production, write to structured log

670 print(f"[AUDIT] {json.dumps(log_entry, default=str)}")

671

672 self.register_middleware(audit_log)

673

674

675 class HealthCheckPlugin(BasePlugin):

676 """内置健康检查插件。"""

677

678 manifest = PluginManifest(

679 name="health_check",

680 version="1.0.0",

681 description="Built-in health check endpoint",

682 provides_tools=["health_check"],

683 auto_activate=True,

684 )

685

686 def on_load(self):

687 def health_check() -> dict:

688 return {

689 "status": "healthy",

690 "timestamp": time.time(),

691 "plugins_active": PluginRegistry().get_active_count(),

692 }

693

694 self.register_tool("health_check", health_check)

695

696

697# ── Quick Start ─────────────────────────────

698

699

700 def create_registry_with_builtins() -> PluginRegistry:

701 """创建注册中心并注册内置插件。"""

702 registry = PluginRegistry()

703 registry.register(HealthCheckPlugin())

704 registry.register(AuditLoggerPlugin())

705

706 for name in ["health_check", "audit_logger"]:

707 registry.load(name)

708 registry.activate(name)

709

710 return registry

711

712

713# ── Missing compat classes (required by agentos/__init__.py) ──

714

715@dataclass

716 class RegisteredPlugin:

717 """已注册插件快照。"""

718 manifest: PluginManifest

719 status: PluginStatus = PluginStatus.REGISTERED

720 loaded_at: Optional[float] = None

721 error: Optional[str] = None

722

723

724@dataclass

725 class DiscoveredPlugin:

726 """从文件系统发现的插件元信息。"""

727 name: str

728 path: str

729 manifest: Optional[PluginManifest] = None

730

731

732 class PluginDiscovery:

733 """插件发现器。"""

734

735 def __init__(self, paths: Optional[list[str]] = None):

736 self._paths = paths or []

737

738 def discover(self) -> list[DiscoveredPlugin]:

739 return []

740

741

742 class PluginLoader:

743 """插件加载器。"""

744

745 def __init__(self, registry: Optional[PluginRegistry] = None):

746 self._registry = registry or PluginRegistry()

747

748 def load_from_discovery(self, discovered: list[DiscoveredPlugin]) -> int:

749 return 0

750

751

752 class LifecycleManager:

753 """插件生命周期管理器。"""

754

755 def __init__(self, registry: Optional[PluginRegistry] = None):

756 self._registry = registry or PluginRegistry()

757

758 def start_all(self) -> None:

759 pass

760

761 def stop_all(self) -> None:

762 pass