107 lines
4.1 KiB
Python
107 lines
4.1 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
|
|
from beaver.engine.loader import EngineLoader
|
|
from beaver.foundation.config import BeaverConfig, PluginsConfig
|
|
from beaver.foundation.utils.file_lock import WorkspaceWriteLock
|
|
from beaver.memory.skills import SkillLearningStore
|
|
from beaver.plugins.discovery import discover_plugins
|
|
from beaver.plugins.skills import PluginManager
|
|
from beaver.plugins.state import PluginStateStore
|
|
from beaver.skills.learning.safety import SkillDraftSafetyChecker
|
|
from beaver.skills.publisher import SkillPublisher
|
|
from beaver.skills.specs import SkillSpecStore
|
|
|
|
|
|
def _write_plugin(root: Path, *, version: str = "1.0.0", body: str = "# Plugin\n\nV1.\n") -> Path:
|
|
plugin_root = root / "baoyu-comic"
|
|
skill_root = plugin_root / "skills" / "baoyu-comic"
|
|
skill_root.mkdir(parents=True, exist_ok=True)
|
|
(skill_root / "SKILL.md").write_text(
|
|
"---\nname: baoyu-comic\ndescription: Comic workflow\ntools: []\n---\n\n" + body,
|
|
encoding="utf-8",
|
|
)
|
|
(plugin_root / "beaver.plugin.json").write_text(
|
|
json.dumps(
|
|
{
|
|
"schema_version": 1,
|
|
"id": "baoyu-comic",
|
|
"name": "Baoyu Comic",
|
|
"version": version,
|
|
"skills": [{"name": "baoyu-comic", "path": "skills/baoyu-comic"}],
|
|
}
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
return plugin_root
|
|
|
|
|
|
def _rewrite_plugin(plugin_root: Path, *, version: str, body: str) -> None:
|
|
manifest_path = plugin_root / "beaver.plugin.json"
|
|
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
|
manifest["version"] = version
|
|
manifest_path.write_text(json.dumps(manifest), encoding="utf-8")
|
|
(plugin_root / "skills" / "baoyu-comic" / "SKILL.md").write_text(
|
|
"---\nname: baoyu-comic\ndescription: Comic workflow\ntools: []\n---\n\n" + body,
|
|
encoding="utf-8",
|
|
)
|
|
|
|
|
|
def _enable(workspace: Path) -> None:
|
|
discovery = discover_plugins(workspace, search_paths=[])
|
|
store = SkillSpecStore(workspace)
|
|
PluginManager(
|
|
workspace=workspace,
|
|
manifests=discovery.manifests,
|
|
discovery_errors=discovery.errors,
|
|
state_store=PluginStateStore(workspace),
|
|
skill_store=store,
|
|
learning_store=SkillLearningStore(workspace / "memory" / "skills"),
|
|
publisher=SkillPublisher(store),
|
|
safety_checker=SkillDraftSafetyChecker(),
|
|
write_lock=WorkspaceWriteLock(workspace),
|
|
).enable("baoyu-comic")
|
|
|
|
|
|
def test_engine_loader_discovers_disabled_plugin_without_mirroring(tmp_path: Path) -> None:
|
|
workspace = tmp_path / "workspace"
|
|
_write_plugin(workspace / "plugins")
|
|
|
|
loaded = EngineLoader(workspace=workspace).load()
|
|
|
|
assert "baoyu-comic" not in loaded.skills
|
|
assert loaded.plugin_manager is not None
|
|
assert loaded.plugins[0]["id"] == "baoyu-comic"
|
|
assert loaded.plugins[0]["enabled"] is False
|
|
|
|
|
|
def test_engine_loader_syncs_enabled_plugin_updates_before_result_skills(tmp_path: Path) -> None:
|
|
workspace = tmp_path / "workspace"
|
|
plugin_root = _write_plugin(workspace / "plugins")
|
|
_enable(workspace)
|
|
_rewrite_plugin(plugin_root, version="1.1.0", body="# Plugin\n\nV2.\n")
|
|
|
|
loaded = EngineLoader(workspace=workspace).load()
|
|
candidates = SkillLearningStore(workspace / "memory" / "skills").list_learning_candidates()
|
|
|
|
assert "baoyu-comic" in loaded.skills
|
|
assert loaded.plugin_manager is not None
|
|
assert loaded.plugins[0]["status"] == "update_pending"
|
|
assert len(candidates) == 1
|
|
assert candidates[0].kind == "plugin_skill_update"
|
|
|
|
|
|
def test_engine_loader_respects_plugin_auto_sync_config(tmp_path: Path) -> None:
|
|
workspace = tmp_path / "workspace"
|
|
plugin_root = _write_plugin(workspace / "plugins")
|
|
_enable(workspace)
|
|
_rewrite_plugin(plugin_root, version="1.1.0", body="# Plugin\n\nV2.\n")
|
|
|
|
config = BeaverConfig(plugins=PluginsConfig(auto_sync=False))
|
|
loaded = EngineLoader(workspace=workspace, config=config).load()
|
|
|
|
assert loaded.plugin_manager is not None
|
|
assert SkillLearningStore(workspace / "memory" / "skills").list_learning_candidates() == []
|