存档
This commit is contained in:
@@ -21,7 +21,7 @@ from kilostar.plugin_runtime.tool_bridge import make_dispatch_tool
|
||||
from kilostar.utils.logger import get_logger
|
||||
from kilostar.utils.ray_compat import _STANDALONE, actor_class
|
||||
from kilostar.utils.ray_hook import register_standalone
|
||||
from kilostar.utils.settings import get_plugin_dir
|
||||
from kilostar.utils.settings import get_plugin_data_dir, get_plugin_dir
|
||||
|
||||
logger = get_logger("plugin_manager")
|
||||
|
||||
@@ -84,15 +84,21 @@ class GlobalPluginManager:
|
||||
# ─── 查询接口 ──────────────────────────────────────────────
|
||||
|
||||
def list_plugins(self) -> List[Dict[str, Any]]:
|
||||
return [
|
||||
{
|
||||
out: List[Dict[str, Any]] = []
|
||||
plugin_root = get_plugin_dir()
|
||||
for name, info in self._orgs.items():
|
||||
manifest = info.get("manifest", {}) or {}
|
||||
ui = manifest.get("ui", {}) or {}
|
||||
wc_manifest = plugin_root / name / "frontend" / "dist" / "wc-manifest.json"
|
||||
out.append({
|
||||
"name": name,
|
||||
"display_name": info.get("display_name", name),
|
||||
"description": info.get("description", ""),
|
||||
"status": "running",
|
||||
}
|
||||
for name, info in self._orgs.items()
|
||||
]
|
||||
"has_ui": wc_manifest.exists(),
|
||||
"icon": ui.get("icon"),
|
||||
})
|
||||
return out
|
||||
|
||||
def get_dispatch_tools(self) -> Dict[str, Any]:
|
||||
"""返回所有 dispatch tools 的 {tool_name: callable} 字典。"""
|
||||
@@ -111,6 +117,23 @@ class GlobalPluginManager:
|
||||
|
||||
# 实例化 organization actor
|
||||
instance = cls(manifest_dict, agents_dict, dir_str)
|
||||
|
||||
# 一次性安装钩子:marker 文件不存在时调用 on_first_install
|
||||
marker = get_plugin_data_dir(name) / ".installed"
|
||||
first_install = not marker.exists()
|
||||
if first_install:
|
||||
try:
|
||||
await instance.on_first_install()
|
||||
except Exception as e:
|
||||
logger.exception(
|
||||
f"plugin {name} on_first_install failed: {e}; aborting install"
|
||||
)
|
||||
raise
|
||||
marker.write_text(manifest.version, encoding="utf-8")
|
||||
|
||||
# 把 agents.json 的 slot 登记到 plugin_owned 表(best-effort,DB 不可用时静默跳过)
|
||||
await self._register_plugin_slots(name, agents_dict)
|
||||
|
||||
await instance.setup()
|
||||
|
||||
# 注册到 ray_actor_hook 命名空间
|
||||
@@ -135,3 +158,47 @@ class GlobalPluginManager:
|
||||
"actor_name": actor_name,
|
||||
}
|
||||
logger.info(f"loaded plugin: {name} (actor={actor_name})")
|
||||
|
||||
async def _register_plugin_slots(self, name: str, agents_dict: Dict[str, Any]) -> None:
|
||||
"""把插件 agents.json 中的每个 agent upsert 为一行 plugin_owned slot。
|
||||
|
||||
只刷新 description/node_affinity;用户在前端配置的 provider/model 不被覆盖。
|
||||
DB 不可用时静默跳过(standalone 启动早期 / 单测场景)。
|
||||
"""
|
||||
try:
|
||||
from kilostar.utils.ray_hook import ray_actor_hook
|
||||
|
||||
pg = ray_actor_hook("postgres_database").postgres_database
|
||||
for adef in agents_dict.get("agents", []):
|
||||
slot_name = adef.get("name")
|
||||
if not slot_name:
|
||||
continue
|
||||
description = adef.get("role") or adef.get("system_prompt") or slot_name
|
||||
await pg.upsert_plugin_slot.remote(
|
||||
plugin_name=name,
|
||||
slot_name=slot_name,
|
||||
description=description,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.debug(f"register_plugin_slots skipped for {name}: {e}")
|
||||
|
||||
async def cleanup_orphan_plugin_slots(self) -> None:
|
||||
"""启动期兜底:DB 中存在但目录已不在的 plugin_owned slot 全部清掉。"""
|
||||
try:
|
||||
from kilostar.utils.ray_hook import ray_actor_hook
|
||||
|
||||
pg = ray_actor_hook("postgres_database").postgres_database
|
||||
recorded: List[str] = await pg.list_plugin_owned_names.remote() or []
|
||||
except Exception as e:
|
||||
logger.debug(f"cleanup_orphan_plugin_slots skipped: {e}")
|
||||
return
|
||||
|
||||
plugin_root = get_plugin_dir()
|
||||
present = {p.name for p in plugin_root.iterdir() if p.is_dir()} if plugin_root.exists() else set()
|
||||
for plugin_name in recorded:
|
||||
if plugin_name not in present:
|
||||
try:
|
||||
n = await pg.delete_plugin_slots.remote(plugin_name)
|
||||
logger.info(f"cleaned {n} orphan slots for missing plugin {plugin_name!r}")
|
||||
except Exception as e:
|
||||
logger.warning(f"failed to clean orphan slots for {plugin_name}: {e}")
|
||||
|
||||
Reference in New Issue
Block a user