mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-12 11:43:25 +08:00
重构仓库目录为统一的 runtime 分层并清理历史 openclaw 残留。
本次迁移将网关/通道/工具/技能/脚本与协议资源集中到新结构,统一路径常量与脚本转发机制,减少顶层噪音并保证运行与测试行为一致。 Made-with: Cursor
This commit is contained in:
parent
ba3836f00f
commit
4a23b715a2
498 changed files with 2760 additions and 2200 deletions
140
runtime/tools/public_registry.py
Normal file
140
runtime/tools/public_registry.py
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import inspect
|
||||
import logging
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from oclaw.runtime.tools.base import ToolSpec
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
ToolFactory = Callable[[], ToolSpec]
|
||||
_PUBLIC_ROOT = Path(__file__).resolve().parent / "public"
|
||||
_CACHED_FACTORIES: list[ToolFactory] | None = None
|
||||
_CACHED_SPECS: list[ToolSpec] | None = None
|
||||
|
||||
|
||||
def _load_module_from_path(module_path: Path, module_name: str) -> Any | None:
|
||||
try:
|
||||
spec = importlib.util.spec_from_file_location(module_name, module_path)
|
||||
if not spec or not spec.loader:
|
||||
return None
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
except Exception as exc:
|
||||
logger.warning("skip public module %s: %s", module_path, exc)
|
||||
return None
|
||||
|
||||
|
||||
def discover_public_tool_factories() -> list[ToolFactory]:
|
||||
"""Discover public tool factories (naming convention: `*_tool`)."""
|
||||
global _CACHED_FACTORIES
|
||||
if _CACHED_FACTORIES is not None:
|
||||
return list(_CACHED_FACTORIES)
|
||||
if not _PUBLIC_ROOT.exists():
|
||||
_CACHED_FACTORIES = []
|
||||
return []
|
||||
factories: list[ToolFactory] = []
|
||||
for module_path in sorted(_PUBLIC_ROOT.glob("*.py")):
|
||||
if module_path.name == "__init__.py":
|
||||
continue
|
||||
mod_name = f"oclaw.runtime.tools.public.{module_path.stem}"
|
||||
module = _load_module_from_path(module_path, mod_name)
|
||||
if module is None:
|
||||
continue
|
||||
exported = getattr(module, "__all__", None)
|
||||
if isinstance(exported, list) and exported:
|
||||
for name in sorted(exported):
|
||||
value = getattr(module, name, None)
|
||||
if callable(value) and name.endswith("_tool"):
|
||||
factories.append(value)
|
||||
continue
|
||||
for name, value in sorted(inspect.getmembers(module)):
|
||||
if callable(value) and name.endswith("_tool"):
|
||||
factories.append(value)
|
||||
_CACHED_FACTORIES = list(factories)
|
||||
return list(factories)
|
||||
|
||||
|
||||
def materialize_public_tools() -> list[ToolSpec]:
|
||||
global _CACHED_SPECS
|
||||
if _CACHED_SPECS is not None:
|
||||
return list(_CACHED_SPECS)
|
||||
specs: list[ToolSpec] = []
|
||||
for factory in discover_public_tool_factories():
|
||||
try:
|
||||
specs.append(factory())
|
||||
except Exception as exc:
|
||||
logger.warning("skip public tool factory %s: %s", factory, exc)
|
||||
_CACHED_SPECS = list(specs)
|
||||
return list(specs)
|
||||
|
||||
|
||||
def clear_public_tool_cache() -> None:
|
||||
global _CACHED_FACTORIES, _CACHED_SPECS
|
||||
_CACHED_FACTORIES = None
|
||||
_CACHED_SPECS = None
|
||||
|
||||
|
||||
def preview_public_tools() -> dict[str, Any]:
|
||||
"""Preview public tool load outcome, including skip reasons.
|
||||
|
||||
This intentionally bypasses caches so Admin UI can reflect current filesystem state.
|
||||
"""
|
||||
out_tools: list[ToolSpec] = []
|
||||
skipped: list[dict[str, str]] = []
|
||||
if not _PUBLIC_ROOT.exists():
|
||||
return {"tools": [], "skipped": []}
|
||||
for module_path in sorted(_PUBLIC_ROOT.glob("*.py")):
|
||||
if module_path.name == "__init__.py":
|
||||
continue
|
||||
mod_name = f"oclaw.runtime.tools.public.{module_path.stem}"
|
||||
module = None
|
||||
try:
|
||||
module = _load_module_from_path(module_path, mod_name)
|
||||
except Exception as exc:
|
||||
skipped.append({"module": str(module_path), "error_code": "module_load_exception", "error": str(exc)})
|
||||
continue
|
||||
if module is None:
|
||||
skipped.append({"module": str(module_path), "error_code": "module_load_failed", "error": "load_failed"})
|
||||
continue
|
||||
factories: list[ToolFactory] = []
|
||||
exported = getattr(module, "__all__", None)
|
||||
if isinstance(exported, list) and exported:
|
||||
for name in sorted(exported):
|
||||
value = getattr(module, name, None)
|
||||
if callable(value) and name.endswith("_tool"):
|
||||
factories.append(value)
|
||||
else:
|
||||
for name, value in sorted(inspect.getmembers(module)):
|
||||
if callable(value) and name.endswith("_tool"):
|
||||
factories.append(value)
|
||||
if not factories:
|
||||
continue
|
||||
for factory in factories:
|
||||
try:
|
||||
out_tools.append(factory())
|
||||
except Exception as exc:
|
||||
skipped.append(
|
||||
{
|
||||
"module": str(module_path),
|
||||
"error_code": "tool_factory_failed",
|
||||
"error": str(exc),
|
||||
"factory": str(getattr(factory, "__name__", "") or str(factory)),
|
||||
}
|
||||
)
|
||||
return {"tools": out_tools, "skipped": skipped}
|
||||
|
||||
|
||||
__all__ = [
|
||||
"ToolFactory",
|
||||
"clear_public_tool_cache",
|
||||
"discover_public_tool_factories",
|
||||
"materialize_public_tools",
|
||||
"preview_public_tools",
|
||||
]
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue