feat(plugins): manifest v2 — schema version, api_version, inter-plugin deps, pip-dependency declaration seam, config schema (#64165)
Additive plugin.yaml v2 fields (all optional; v1 manifests unchanged forever):
- manifest_version: manifest FILE-FORMAT version (absent = 1). Deliberately
split from api_version per the round-2 design correction. Newer-than-
supported versions load with a warning, unknown fields ignored.
- api_version: runtime plugin API generation the plugin targets (integer).
- requires_plugins: advisory inter-plugin deps ({id, version_range?}).
Missing dep = warn + still load (ctx.has_plugin() runtime probe added).
Load ORDER is dependency-respecting: graphlib topological sort, stable
alphabetical tiebreak; cycles warn and fall back to alphabetical.
- python_dependencies: declared pip requirements — VALIDATED AND SURFACED
ONLY (loader warning + install-time printout + doctor checks with a pip
install hint). Never auto-installed: the isolation design for the install
seam (#15220) is an explicitly deferred follow-up per the round-2 review.
- config_schema: JSON-schema-ish description of plugins.entries.<id>.settings
keys; validated at load, mismatches are actionable warnings naming the key
and expected type — never load failures.
- Formalized metadata: license, homepage, tags.
- Unknown manifest fields warn-don't-fail (debug-level for v1 manifests).
- hermes plugins doctor gains v2 checks: future manifest_version, invalid
api_version, dep declarations, unpinned/missing python_dependencies,
unknown config_schema types.
- Docs: manifest v2 reference table in the developer-guide plugins index,
including the explicit pip-seam isolation deferral and the note that
#64166 packs build on these fields.
- Tests: tests/hermes_cli/test_plugin_manifest_v2.py (19 tests) covering v1
regression, v2 parse, unknown-field warn, dep order, cycle fallback,
config_schema warnings, and the surfaced-not-installed pip seam.
This commit is contained in:
@@ -331,6 +331,32 @@ def _missing_requires_env_names(manifest: dict) -> list[str]:
|
||||
return [s["name"] for s in env_specs if s.get("name") and not get_env_value(s["name"])]
|
||||
|
||||
|
||||
def _print_python_dependencies(manifest: dict, console) -> None:
|
||||
"""Surface declared python_dependencies at install time (#64165).
|
||||
|
||||
Declaration seam ONLY — Hermes never auto-installs plugin pip
|
||||
dependencies (isolation design deferred; see #64165 / #15220). We print
|
||||
the declared requirements with a copy-pasteable install hint.
|
||||
"""
|
||||
deps = manifest.get("python_dependencies") or []
|
||||
if not isinstance(deps, list):
|
||||
return
|
||||
deps = [d.strip() for d in deps if isinstance(d, str) and d.strip()]
|
||||
if not deps:
|
||||
return
|
||||
plugin_name = manifest.get("name", "this plugin")
|
||||
console.print(
|
||||
f"\n[bold]{plugin_name}[/bold] declares Python dependencies "
|
||||
"(not installed automatically):"
|
||||
)
|
||||
for dep in deps:
|
||||
console.print(f" - {dep}")
|
||||
console.print(
|
||||
"[dim]Install them yourself if needed: "
|
||||
f"pip install {' '.join(repr(d) for d in deps)}[/dim]\n"
|
||||
)
|
||||
|
||||
|
||||
def _prompt_plugin_env_vars(manifest: dict, console) -> None:
|
||||
"""Prompt for required environment variables declared in plugin.yaml.
|
||||
|
||||
@@ -835,6 +861,8 @@ def cmd_install(
|
||||
|
||||
_prompt_plugin_env_vars(installed_manifest, console)
|
||||
|
||||
_print_python_dependencies(installed_manifest, console)
|
||||
|
||||
_display_after_install(target, identifier)
|
||||
|
||||
should_enable = enable
|
||||
|
||||
Reference in New Issue
Block a user