mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-08-04 16:38:49 +00:00
feat(extensions): load published OpenClaw runtimes
This commit is contained in:
parent
76d7a33b3b
commit
012c7ce034
@ -11,7 +11,9 @@ from typing import Any
|
||||
from nanobot.extensions.codec import MANIFEST_FILENAME, load_manifest
|
||||
from nanobot.extensions.manifest import (
|
||||
ContributionKind,
|
||||
DependencyKind,
|
||||
ExtensionContribution,
|
||||
ExtensionDependency,
|
||||
ExtensionManifest,
|
||||
ExtensionRuntime,
|
||||
)
|
||||
@ -72,7 +74,10 @@ def _adapt_pi(package: dict[str, Any]) -> AdaptedPackage:
|
||||
|
||||
def _adapt_openclaw(root: Path, package: dict[str, Any]) -> AdaptedPackage:
|
||||
openclaw = package["openclaw"]
|
||||
entries = _string_list(openclaw.get("extensions"), "openclaw.extensions")
|
||||
entries = _optional_string_list(
|
||||
openclaw.get("runtimeExtensions"),
|
||||
"openclaw.runtimeExtensions",
|
||||
) or _string_list(openclaw.get("extensions"), "openclaw.extensions")
|
||||
plugin_path = root / "openclaw.plugin.json"
|
||||
plugin = _read_json(plugin_path, "openclaw.plugin.json") if plugin_path.is_file() else {}
|
||||
plugin_id = str(plugin.get("id") or package.get("name") or "openclaw-plugin")
|
||||
@ -97,6 +102,7 @@ def _adapt_openclaw(root: Path, package: dict[str, Any]) -> AdaptedPackage:
|
||||
runtime=ExtensionRuntime.OPENCLAW,
|
||||
entries=tuple(entries),
|
||||
contributions=contributions,
|
||||
dependencies=_openclaw_dependencies(package, openclaw),
|
||||
description=str(
|
||||
plugin.get("description") or package.get("description") or ""
|
||||
),
|
||||
@ -108,6 +114,23 @@ def _adapt_openclaw(root: Path, package: dict[str, Any]) -> AdaptedPackage:
|
||||
)
|
||||
|
||||
|
||||
def _openclaw_dependencies(
|
||||
package: dict[str, Any],
|
||||
openclaw: dict[str, Any],
|
||||
) -> tuple[ExtensionDependency, ...]:
|
||||
build = openclaw.get("build")
|
||||
version = build.get("openclawVersion") if isinstance(build, dict) else None
|
||||
peers = package.get("peerDependencies")
|
||||
peer_version = peers.get("openclaw") if isinstance(peers, dict) else None
|
||||
return (
|
||||
ExtensionDependency(
|
||||
kind=DependencyKind.NPM,
|
||||
name="openclaw",
|
||||
specifier=str(version or peer_version or "latest"),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _openclaw_contributions(
|
||||
plugin: dict[str, Any],
|
||||
) -> tuple[ExtensionContribution, ...]:
|
||||
|
||||
@ -21,6 +21,7 @@ from nanobot.extensions.discovery import (
|
||||
ExtensionDiscoveryResult,
|
||||
discover_manifest_root,
|
||||
)
|
||||
from nanobot.extensions.manifest import DependencyKind, ExtensionManifest
|
||||
from nanobot.extensions.package_adapter import AdaptedPackage, adapt_package
|
||||
from nanobot.extensions.registry import ExtensionScope
|
||||
|
||||
@ -216,7 +217,7 @@ class ExtensionStore:
|
||||
)
|
||||
if package.generated:
|
||||
dump_manifest(package.manifest, staging / MANIFEST_FILENAME)
|
||||
_install_node_dependencies(staging)
|
||||
_install_node_dependencies(staging, package.manifest)
|
||||
integrity = _tree_hash(staging)
|
||||
if target.exists():
|
||||
target.rename(backup)
|
||||
@ -285,25 +286,46 @@ _DEFAULT_RECORD = InstalledExtension(
|
||||
)
|
||||
|
||||
|
||||
def _install_node_dependencies(root: Path) -> None:
|
||||
def _install_node_dependencies(root: Path, manifest: ExtensionManifest) -> None:
|
||||
package_path = root / "package.json"
|
||||
if not package_path.is_file():
|
||||
return
|
||||
package = json.loads(package_path.read_text(encoding="utf-8"))
|
||||
dependencies = package.get("dependencies") if isinstance(package, dict) else None
|
||||
if not dependencies:
|
||||
return
|
||||
_run(
|
||||
[
|
||||
"npm",
|
||||
"install",
|
||||
"--omit=dev",
|
||||
"--ignore-scripts",
|
||||
"--no-audit",
|
||||
"--no-fund",
|
||||
],
|
||||
cwd=root,
|
||||
)
|
||||
if dependencies:
|
||||
_run(
|
||||
[
|
||||
"npm",
|
||||
"install",
|
||||
"--omit=dev",
|
||||
"--ignore-scripts",
|
||||
"--no-audit",
|
||||
"--no-fund",
|
||||
],
|
||||
cwd=root,
|
||||
)
|
||||
for dependency in manifest.dependencies:
|
||||
if dependency.kind is not DependencyKind.NPM or dependency.optional:
|
||||
continue
|
||||
spec = (
|
||||
f"{dependency.name}@{dependency.specifier}"
|
||||
if dependency.specifier
|
||||
else dependency.name
|
||||
)
|
||||
_run(
|
||||
[
|
||||
"npm",
|
||||
"install",
|
||||
"--save-prod",
|
||||
"--save-exact",
|
||||
"--ignore-scripts",
|
||||
"--no-audit",
|
||||
"--no-fund",
|
||||
"--",
|
||||
spec,
|
||||
],
|
||||
cwd=root,
|
||||
)
|
||||
|
||||
|
||||
def _run(command: list[str], *, cwd: Path | None = None) -> str:
|
||||
|
||||
@ -3,6 +3,7 @@ from pathlib import Path
|
||||
|
||||
from nanobot.extensions import (
|
||||
ContributionKind,
|
||||
DependencyKind,
|
||||
ExtensionRuntime,
|
||||
adapt_package,
|
||||
)
|
||||
@ -34,7 +35,11 @@ def test_adapts_openclaw_contracts_without_loading_code(tmp_path: Path) -> None:
|
||||
{
|
||||
"name": "@openclaw/search-plugin",
|
||||
"version": "2.0.0",
|
||||
"openclaw": {"extensions": ["./index.ts"]},
|
||||
"peerDependencies": {"openclaw": ">=2.0.0"},
|
||||
"openclaw": {
|
||||
"extensions": ["./index.ts"],
|
||||
"runtimeExtensions": ["./dist/index.js"],
|
||||
},
|
||||
}
|
||||
)
|
||||
)
|
||||
@ -55,6 +60,9 @@ def test_adapts_openclaw_contracts_without_loading_code(tmp_path: Path) -> None:
|
||||
result = adapt_package(tmp_path)
|
||||
|
||||
assert result.manifest.id == "openclaw.search"
|
||||
assert result.manifest.activation_entries == ("./dist/index.js",)
|
||||
assert result.manifest.dependencies[0].kind is DependencyKind.NPM
|
||||
assert result.manifest.dependencies[0].specifier == ">=2.0.0"
|
||||
assert {
|
||||
(item.kind, item.name) for item in result.manifest.contributions
|
||||
} == {
|
||||
|
||||
@ -3,8 +3,13 @@ from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
from nanobot.extensions import (
|
||||
DependencyKind,
|
||||
ExtensionDependency,
|
||||
ExtensionManifest,
|
||||
ExtensionRuntime,
|
||||
ExtensionSourceKind,
|
||||
ExtensionStore,
|
||||
dump_manifest,
|
||||
)
|
||||
|
||||
|
||||
@ -93,3 +98,35 @@ def test_store_restores_previous_package_when_registry_write_fails(
|
||||
|
||||
restored = json.loads(installed_package.read_text())
|
||||
assert restored["version"] == "1.0.0"
|
||||
|
||||
|
||||
def test_store_installs_declared_npm_runtime_dependency(tmp_path: Path) -> None:
|
||||
source = tmp_path / "source"
|
||||
source.mkdir()
|
||||
(source / "index.mjs").write_text("export default function () {}")
|
||||
(source / "package.json").write_text("{}")
|
||||
dump_manifest(
|
||||
ExtensionManifest(
|
||||
id="openclaw.test",
|
||||
name="OpenClaw test",
|
||||
version="1.0.0",
|
||||
runtime=ExtensionRuntime.OPENCLAW,
|
||||
entry="./index.mjs",
|
||||
dependencies=(
|
||||
ExtensionDependency(
|
||||
kind=DependencyKind.NPM,
|
||||
name="openclaw",
|
||||
specifier="2026.7.1",
|
||||
),
|
||||
),
|
||||
),
|
||||
source / "nanobot.extension.json",
|
||||
)
|
||||
store = ExtensionStore(tmp_path / "extensions")
|
||||
|
||||
with patch("nanobot.extensions.store._run") as run:
|
||||
store.install_local(source)
|
||||
|
||||
command = run.call_args.args[0]
|
||||
assert "--save-prod" in command
|
||||
assert "openclaw@2026.7.1" in command
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user