feat(plugin): Add installed plugins management utilities#2031
Conversation
Add support for managing plugins installed in ~/.openhands/skills/installed/: - install_plugin(): Install plugins from GitHub repos, git URLs, or local paths - uninstall_plugin(): Remove installed plugins by name - list_installed_plugins(): List all installed plugins with metadata - load_installed_plugins(): Load all installed plugins as Plugin objects - get_installed_plugin(): Get info about a specific installed plugin - update_plugin(): Update an installed plugin to the latest version This follows the Claude Code plugin pattern where plugins can be installed from various sources and managed in a user-specific directory. Includes comprehensive tests for all functionality. Co-authored-by: openhands <openhands@all-hands.dev>
Add support for managing plugins installed in ~/.openhands/skills/installed/: TUI Command: - /plugin - Shows installed plugins and usage instructions CLI Subcommand: - openhands plugin list [--json] - List installed plugins - openhands plugin install <source> [--ref] [--repo-path] [--force] - Install plugin - openhands plugin uninstall <name> - Uninstall plugin - openhands plugin update <name> - Update plugin to latest version Supported sources: - github:owner/repo - GitHub repository shorthand - https://github.com/owner/repo - Full git URL - /local/path - Local directory path This uses the new installed plugins management utilities from SDK PR #2031: OpenHands/software-agent-sdk#2031 Closes #405 Co-authored-by: openhands <openhands@all-hands.dev>
|
Looks like there are a few issues preventing this PR from being merged!
If you'd like me to help, just leave a comment, like Feel free to include any additional details that might help me get this PR into a better state. You can manage your notification settings |
API breakage checks (Griffe)Result: Failed Log excerpt (first 1000 characters) |
Agent server REST API breakage checks (OpenAPI)Result: Passed |
|
@OpenHands Understand this PR very well. Fetch plugins specification from claude code too, to see the goal. Then:
|
|
I'm on it! enyst can track my progress at all-hands.dev |
Co-authored-by: openhands <openhands@all-hands.dev>
|
Pre-commit failures are fixed and pushed.
Claude Code plugins spec (what we’re trying to mirror)From the official Claude Code docs:
OpenHands’ implementation in this PR is aiming for the same directory shape and “installed plugins” concept, but (currently) only implements a “user-scope-ish” installed directory at /codereview (important issues only)1) Style and Formatting (significant only)[openhands-sdk/openhands/sdk/plugin/installed.py, Lines 412–421] Readability: duplicated
[tests/sdk/plugin/test_installed_plugins.py, Lines 118–122] Readability: duplicated “Load and verify” comment line. 2) Clarity and Readability[openhands-sdk/openhands/sdk/plugin/installed.py, Lines 443–485] Doc/behavior mismatch: docstring says “The original source and ref are preserved from the installation metadata”, but:
Concrete fix options:
[openhands-sdk/openhands/sdk/plugin/installed.py, Lines 213–232] Efficiency:
[openhands-sdk/openhands/sdk/plugin/installed.py, Lines 347–376] Side effects in “list”:
3) Security and Common Bug Patterns[openhands-sdk/openhands/sdk/plugin/installed.py, Lines 222–242] Security: path traversal / arbitrary write risk via
[openhands-sdk/openhands/sdk/plugin/installed.py, Lines 262–306] Security: path traversal / arbitrary delete risk via
Concrete improvement:
This is the biggest “must fix” gap before wiring this into a CLI command that accepts untrusted plugin sources. 4) Testing and Behavior Verification[tests/sdk/plugin/test_installed_plugins.py] Tests cover the happy paths and basic metadata behavior, but no tests cover the path traversal / unsafe name cases above.
What’s “missing” vs Claude Code’s end-goal (non-blocking, but good to track)Compared to Claude Code’s plugin manager, this utility layer doesn’t yet cover:
If you want, I can dig further into Claude’s marketplace JSON format and caching behavior too (I used the official docs pages; there isn’t an “MCP fetch” tool available in this runner, but I can continue pulling primary-source details from the Claude docs in the same way). |
Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: openhands <openhands@all-hands.dev>
Current behavior (PR #2031)This PR adds installed plugin management utilities to the SDK, mirroring the Claude Code plugin directory shape (plugins are self-contained directories that can include Installed plugins location + metadata
Name safety / constraints
New public APIs (exported via
|
Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: openhands <openhands@all-hands.dev>
|
✅ PR Artifacts Cleaned Up The |
Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: openhands <openhands@all-hands.dev>
|
With TestLLM, the “live test” worked like this: What we were trying to proveWe wanted an end-to-end-ish smoke test that:
Why TestLLM is enough here
In the example we used: TestLLM.from_messages([
Message(role="assistant", content=[TextContent(text="Done")])
])So when the conversation calls the LLM once, it deterministically returns The important “real behavior” that still happensEven though the LLM response is scripted, the SDK still does the real steps around it:
conversation.send_message("hello")
conversation.run()the
That’s the key proof that “installed plugin → loaded plugin → skill in agent context → skill triggers in a conversation” works. What got persistedBecause
Those events include (at minimum) the user message event and the agent message event (from TestLLM). In artifact mode, we also delete What this does not testIt doesn’t test:
But it does test the installed-plugin utilities + the skill loading/activation path in a “real Conversation”. If you still want a “real-world-like” run with a cheap real model (e.g. |
Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: openhands <openhands@all-hands.dev>
There was a problem hiding this comment.
@xingyaoww In my understanding, and the agent found references, we're blurring the lines a bit here:
- a plugin has a defined format as manifest + skills/ directory + optional stuff
- an Agentskill has a defined format as a SKILL.md + optional stuff
In the latest change in this PR, we're reusing the plugin utilities to get/install/load a single Agentskill.
I think this is rather a good thing: it's convenient, it reuses the machinery but the support for the special ("flat") case is in one place; there is no "flat" case in definitions, but... maybe it should be.
Just would probably be good to be aware of it, and 🤔 maybe we could document accurately somewhere, so that the agent doesn't get fooled going forward. It's a trade-off: we would need similar implementation to just fetch a single Agentskill (which is incredibly useful), so we reuse the utilities named "plugin" to do it.
|
@OpenHands please review this PR and the full discussion in #2272 - help me revert related changes (eg 55746ba) and modify the example script so we directly load the plugins from https://github.com/anthropics/skills as described here: #2272 (comment) The expected outcome would be:
Please update the code, run the example script and make sure it works as expected before you declare victory. When you are done, attach evidence that the code actually work |
|
I'm on it! xingyaoww can track my progress at all-hands.dev |
…s/skills The previous changes added support for 'flat' SKILL.md format where a single AgentSkill directory was treated as a plugin. Per discussion in #2272, this conflates AgentSkills with Plugins and creates confusion. Changes: - Revert _load_skills() to only check skills/ subdirectory (no root fallback) - Remove flat SKILL.md tests from test_installed_plugins.py - Update example script and tests to load full anthropics/skills repository which contains proper plugins with skills/ subdirectories (document-skills plugin with pptx, xlsx, docx, pdf skills) The anthropics/skills repo marketplace.json lists 'document-skills' and 'example-skills' as the actual plugins, not individual AgentSkill folders. Co-authored-by: openhands <openhands@all-hands.dev>
Final SummaryI've completed the requested changes to PR #2031. The changes have already been committed and pushed. What was done:✅ Reverted flat SKILL.md support (as requested from commit
✅ Updated example script to load proper plugins from anthropics/skills
✅ Updated tests accordingly
✅ Verified the code works
Commit pushed:
The changes are concise and directly address the user's request to revert the flat SKILL.md support and update the example to load proper plugins from the anthropics/skills repository. |
|
@OpenHands Fetch agentskills.io and the claude code plugin definition, to understand Agentskill format and Plugin format. Read all top-level comments in this PR, with accent on the latest discussion and also read issue 2272 to see what we decided. You can see that the agent reverted single-Agentskill support. Then review the PR. Tell me what it does on plugin operations, like install/uninstall, enable/disable, list, etc. Tell me what it does (or the codebase does) on Agentskill operations, of the same kinds. Post your response as a comment to this PR. Note that it will rendered as markdown. |
|
I'm on it! enyst can track my progress at all-hands.dev |
|
I can’t directly post GitHub PR comments from this request (per the agent instructions for “question” requests), but below is a ready-to-paste PR comment in Markdown. PR review: Plugin format vs AgentSkills format + what this PR addsExternal specs (ground truth)AgentSkills (agentskills.io) skill format
Claude Code plugin format
Latest PR discussion + Issue #2272 decision (what we decided)Issue: #2272 (now closed) The key clarification from the latest discussion:
Concretely in this PR:
This aligns with the decision that future CLI UX should likely separate:
What this PR does for plugin operations (SDK)This PR adds installed plugin management utilities under:
InstallAPI: Behavior:
UninstallAPI: Behavior:
ListAPI: Behavior (important):
LoadAPI: Behavior:
GetAPI: Behavior:
UpdateAPI: Behavior:
Enable / Disable
What the codebase does for AgentSkills operations (install/uninstall/list/enable/update equivalents)OpenHands supports AgentSkills as a skill format, not as an “installed plugin container”. “Install”There is no dedicated What exists instead:
“Uninstall”
“List”
“Enable / Disable”
“Update”
Review notes / potential follow-ups (optional)
If you want, I can turn the above into a shorter version (more “executive summary”) for the PR thread, but I kept it explicit since the plugin-vs-skill boundary was the core confusion in #2272. |
|
Xingyao, I'll merge this, it's great to have it and there's no point in... loading it in context again to check what we already checked multiple times. And, it's basically a single file with install utilities. Thank you! ❤️ @OpenHands Read the entire discussion here on Plugins and Agentskills; get yourself information from agentskills.io and from claude code plugin docs about the specs of these two; then
|
|
I'm on it! enyst can track my progress at all-hands.dev |
Co-authored-by: openhands <openhands@all-hands.dev>
|
Summary of new work since the last update:
Checklist:
No further tool actions needed. |
|
@enyst thanks a ton for helping babysit this PR 🙏 This LGTM! Sorry i haven't had enough time lately to pay attention to github notification lately :( |
) Cherry-pick from upstream de65ac5
Summary
This PR adds installed plugin management utilities to
openhands-sdk, providing the SDK building blocks needed for a future CLI/plugin install ...workflow.Installed plugins are treated as self-contained plugin directories (Claude Code style) that may include
skills/,agents/,hooks/,.mcp.json, etc., and are copied into an installation root under the user’s~/.openhands/directory.Intentions / design notes
~/.openhands/skills/.~/.openhands/plugins/installed/.skills/.../skills/...nesting and leaves room for future plugin plumbing (e.g.~/.openhands/plugins/cache/).^[a-z0-9]+(-[a-z0-9]+)*$.installed.jsonunless something actually changed.Installed plugins location + metadata
~/.openhands/plugins/installed/~/.openhands/plugins/installed/<plugin-name>/...~/.openhands/plugins/installed/.installed.jsonDirectory shape:
Public API (exported via
openhands.sdk.plugin)Models
InstalledPluginInfoInstalledPluginsMetadataFunctions
install_plugin(source, ref=None, repo_path=None, installed_dir=None, force=False)uninstall_plugin(name, installed_dir=None)list_installed_plugins(installed_dir=None)load_installed_plugins(installed_dir=None)get_installed_plugin(name, installed_dir=None)update_plugin(name, installed_dir=None)Behavioral summary
install_plugin(...)fetch_plugin_with_resolution(...)plugin.name(kebab-case)installed_dir / plugin.name.installed.jsonuninstall_plugin(...).installed.jsonlist_installed_plugins(...)InstalledPluginInfoentriesupdate_plugin(...)sourcewithref=Noneandforce=TrueUsage examples
See also:
examples/05_skills_and_plugins/03_local_plugin_install/main.py(no LLM required).Non-goals (for this PR) / future work
~/.openhands/plugins/cache/.Checklist
Agent Server images for this PR
• GHCR package: https://github.com/OpenHands/agent-sdk/pkgs/container/agent-server
Variants & Base Images
eclipse-temurin:17-jdknikolaik/python-nodejs:python3.12-nodejs22golang:1.21-bookwormPull (multi-arch manifest)
# Each variant is a multi-arch manifest supporting both amd64 and arm64 docker pull ghcr.io/openhands/agent-server:65501fd-pythonRun
All tags pushed for this build
About Multi-Architecture Support
65501fd-python) is a multi-arch manifest supporting both amd64 and arm6465501fd-python-amd64) are also available if needed