Files
hermes-agent/tests/fixtures/plugin_component_detection.json
Tekniumanddandacompany 41e6cddfa5 fix(plugins): an agent half is a manifest with something to load
A plugin.yaml alone does not make an agent half: desktop-only catalog
plugins (agent-log, githermes, deepseek-whale) ship one beside
desktop/plugin.js as metadata, and the previous commit's rule would plan
an agent install for them that loads nothing. The old __init__.py
requirement was what kept them desktop-only.

Both inspectors now call a plugin.yaml folder an agent half when it has
an agent payload beside the manifest: __init__.py, a dashboard tab or
backend (dashboard/manifest.json), or a manifest-only language pack
(provides_locales plus locales/, the loader's own rule). plugin.json
stays an agent half on its own. The shared detection fixture gains the
cron-bulletin shape, a dashboard-only package, the desktop-only shape and
three language-pack cases; its existing cases are unchanged.

Co-authored-by: dandacompany <128420625+dandacompany@users.noreply.github.com>
2026-10-28 07:27:12 -07:00

115 lines
5.5 KiB
JSON

{
"schema": 1,
"doc": "Which halves a plugin folder ships. Both inspectors read these cases: the gateway's (hermes_cli/plugins_cmd_inspect.py detect_components) and native Desktop's (apps/desktop/electron/desktop-plugin-install.ts detectPluginComponents). A rule changed in one must change in both, and here. Each case is a folder named 'root' holding 'files'; agent_name falls back to that folder's name.",
"cases": [
{
"name": "yaml manifest with a package is an agent half named by the manifest",
"root": "folder-name",
"files": { "plugin.yaml": "name: from-manifest\nversion: 1.0.0\n", "__init__.py": "" },
"expect": { "agent": true, "desktop": false, "agent_name": "from-manifest" }
},
{
"name": "plugin.yml counts as the yaml manifest",
"root": "yml-plugin",
"files": { "plugin.yml": "name: yml-name\n", "__init__.py": "" },
"expect": { "agent": true, "desktop": false, "agent_name": "yml-name" }
},
{
"name": "a quoted manifest name is read without its quotes",
"root": "quoted",
"files": { "plugin.yaml": "name: \"quoted-name\"\n", "__init__.py": "" },
"expect": { "agent": true, "desktop": false, "agent_name": "quoted-name" }
},
{
"name": "a yaml manifest without a name falls back to the folder name",
"root": "unnamed-plugin",
"files": { "plugin.yaml": "version: 1.0.0\n", "__init__.py": "" },
"expect": { "agent": true, "desktop": false, "agent_name": "unnamed-plugin" }
},
{
"name": "a yaml manifest without __init__.py is not an agent half",
"root": "manifest-only",
"files": { "plugin.yaml": "name: manifest-only\n" },
"expect": { "agent": false, "desktop": false, "agent_name": null }
},
{
"name": "a manifest-only package with a dashboard backend and a desktop half installs both halves",
"root": "bulletin-repo",
"files": { "plugin.yaml": "name: bulletin\n", "dashboard/manifest.json": "{\"name\": \"bulletin\", \"api\": \"plugin_api.py\"}", "dashboard/plugin_api.py": "", "desktop/plugin.js": "export default {}\n" },
"expect": { "agent": true, "desktop": true, "agent_name": "bulletin" }
},
{
"name": "a manifest-only dashboard package without a desktop half is an agent half",
"root": "dashboard-only",
"files": { "plugin.yaml": "name: dash\n", "dashboard/manifest.json": "{}" },
"expect": { "agent": true, "desktop": false, "agent_name": "dash" }
},
{
"name": "a desktop plugin whose plugin.yaml is metadata stays desktop-only",
"root": "titlebar-widget",
"files": { "plugin.yaml": "name: titlebar-widget\nkind: standalone\n", "desktop/plugin.js": "export default {}\n" },
"expect": { "agent": false, "desktop": true, "agent_name": null }
},
{
"name": "a manifest-only language pack is an agent half",
"root": "lang-pack",
"files": { "plugin.yaml": "name: hermes-lang-xx\nprovides_locales:\n - id: xx\n endonym: Xx\n", "locales/xx.yaml": "common.save: Xx\n" },
"expect": { "agent": true, "desktop": false, "agent_name": "hermes-lang-xx" }
},
{
"name": "declared locales without a locales folder are not an agent half",
"root": "lang-no-folder",
"files": { "plugin.yaml": "name: lang-no-folder\nprovides_locales: [xx]\n" },
"expect": { "agent": false, "desktop": false, "agent_name": null }
},
{
"name": "an empty provides_locales declares no language pack",
"root": "lang-empty",
"files": { "plugin.yaml": "name: lang-empty\nprovides_locales: []\n", "locales/xx.yaml": "common.save: Xx\n" },
"expect": { "agent": false, "desktop": false, "agent_name": null }
},
{
"name": "a portable plugin.json is an agent half named by it",
"root": "portable",
"files": { "plugin.json": "{\"name\": \"portable-one\"}" },
"expect": { "agent": true, "desktop": false, "agent_name": "portable-one" }
},
{
"name": "a portable plugin.json without a name falls back to the folder name",
"root": "portable-unnamed",
"files": { "plugin.json": "{}" },
"expect": { "agent": true, "desktop": false, "agent_name": "portable-unnamed" }
},
{
"name": "the yaml manifest names the plugin when a plugin.json is present too",
"root": "both-manifests",
"files": { "plugin.yaml": "name: yaml-wins\n", "__init__.py": "", "plugin.json": "{\"name\": \"json-loses\"}" },
"expect": { "agent": true, "desktop": false, "agent_name": "yaml-wins" }
},
{
"name": "plugin.js at the root is a desktop half",
"root": "ui-plugin",
"files": { "plugin.js": "export default {}\n" },
"expect": { "agent": false, "desktop": true, "agent_name": null }
},
{
"name": "desktop/plugin.js beside an agent half makes a mixed package",
"root": "mixed",
"files": { "plugin.yaml": "name: mixed\n", "__init__.py": "", "desktop/plugin.js": "export default {}\n" },
"expect": { "agent": true, "desktop": true, "agent_name": "mixed" }
},
{
"name": "plugin.js in any other folder is not a desktop half",
"root": "elsewhere",
"files": { "ui/plugin.js": "export default {}\n" },
"expect": { "agent": false, "desktop": false, "agent_name": null }
},
{
"name": "a folder with neither half ships nothing",
"root": "readme-only",
"files": { "README.md": "nothing here\n" },
"expect": { "agent": false, "desktop": false, "agent_name": null }
}
]
}