Commit Graph

23 Commits

Author SHA1 Message Date
Teknium
63f5e0ea45 feat(computer-use): pluggable single-select computer-use providers
Third-party drivers (e.g. a Codex-style computer-use reimplementation) could
only replace cua-driver by patching core. computer_use now follows the
single-select provider model Hermes uses for memory providers, context
engines and cron schedulers:

- plugins/computer_use/__init__.py discovers bundled
  plugins/computer_use/<name>/ then user $HERMES_HOME/plugins/<name>/
  (marker: register_computer_use_provider / ComputerUseProvider) and loads
  ONLY the provider computer_use.backend names (default "cua"), read per
  call so each profile gets its own. Unselected providers are never
  imported; listing reads plugin.yaml only. An unknown/unloadable name
  returns an error naming it and the installed providers; no fallback.
- The built-in cua-driver is an ordinary bundled provider at
  plugins/computer_use/cua/ (driver implementation stays in
  tools/computer_use). tools/computer_use/backend.py gains
  ComputerUseProvider (create_backend(permission_mode=...), is_available(),
  optional doctor()).
- The tool's check_fn and backend construction go through the selected
  provider; the model-facing schema is unchanged for every provider
  (prompt-cache invariant). The test-only HERMES_COMPUTER_USE_BACKEND env
  switch is gone (tests patch _new_backend).
- General plugin discovery treats computer-use providers as exclusive
  (not eagerly imported, not listed as toggleable) and skips the bundled
  computer_use/ category, same as memory/, context_engine/, cron_providers/.
- hermes tools and the Desktop toolset panel list installed providers next
  to cua; computer-use doctor/status/permissions and the Desktop status
  payload skip cua-driver/TCC probes when another provider is selected.
2026-10-05 17:20:30 -07:00
Alex Fournier
19f9554188 Merge origin/main into chore/relay-0.9
Signed-off-by: Alex Fournier <afournier@nvidia.com>
2026-09-28 09:22:11 -07:00
JoaoMarcos44
d5672e4f55 fix(gateway): read a parked profile's config without importing its plugins (#123386, salvage #123397)
The multiplex migration preflight (and the duplicate-credential check doctor / gateway
status share) loads every profile's gateway config in that profile's scope, and
load_gateway_config() discovers plugins there. For a PARKED profile that imported its
plugins into the live host gateway on every boot and ran their register() side effects
(threads, DB handles) for a profile the operator took out of the host.

Decision (the thread's needs-decision): keep parked profiles in the inventory AND the
duplicate-credential guard — test_parked_profile_remains_in_migration_inventory stays as
written (parking must not hide a conflict) — but read a parked home's config under a new
suppress_plugin_discovery() contextvar that makes discover_plugins() a no-op without marking
the scope discovered, so the first real consumer after unparking still loads its plugins.
Trade-off recorded in _profile_gateway_config: only a parked profile's plugin-defined
platforms fall out of the guard (their credentials cannot be evaluated without importing the
plugin); builtin bot tokens still collide.

PR #123397 (JoaoMarcos44) located the site; it skipped parked homes from config loading
entirely, which flipped the tested guarantee, so this is a slim redo with credit.

(cherry picked from commit 0267e14a2c1d8bcbebaefa48011c43ec6b86c46f)
2026-09-28 04:04:23 -07:00
Alex Fournier
f5448560e9 Merge origin/main into chore/relay-0.9
Signed-off-by: Alex Fournier <afournier@nvidia.com>
2026-09-27 11:52:04 -04:00
Alex Fournier
440fe3ff24 Merge branch 'main' into chore/relay-0.9
Signed-off-by: Alex Fournier <afournier@nvidia.com>
2026-09-22 14:43:35 -04:00
ethernet
c13287c915 Merge remote-tracking branch 'origin/main' into ethie/pm-clean
# Conflicts:
#	apps/desktop/electron/main.ts
#	hermes_cli/backup.py
#	hermes_cli/config.py
#	hermes_cli/plugin_catalog.py
#	hermes_cli/plugins_cmd.py
#	hermes_cli/plugins_cmd_catalog.py
#	hermes_cli/plugins_discovery.py
#	hermes_cli/profiles.py
#	hermes_cli/update_cmd_deps.py
#	pyproject.toml
#	tests/gateway/test_dm_topics.py
#	tests/hermes_cli/test_config.py
#	tests/hermes_cli/test_plugins_cmd.py
#	tests/hermes_cli/test_update_autostash.py
#	tests/tools/test_lazy_deps.py
#	tools/lazy_deps.py
#	tools/skill_ledger.py
#	utils.py
#	website/docs/user-guide/security.md
2026-09-22 05:16:50 -04:00
teknium1
643dea487b fix(plugins): canonical activation keys + remove/toggle bookkeeping across CLI, dashboard and RPC
Symptoms fixed (all live-reproduced on origin/main in a fake HERMES_HOME):
- `hermes plugins disable photon-platform` wrote `platforms/photon` while the loader keyed the
  bundled adapter `photon-platform`, so the disable never applied (#27548). The loader now keys
  bundled platforms `platforms/<dir>` like every other category (one call site in
  plugins_discovery.py); the manifest name stays an accepted alias through gate_manifest.
- `plugins.manage toggle` / dashboard toggle wrote the raw identifier: enabling by bare leaf or
  manifest name returned ok while a stale canonical key in plugins.disabled kept the plugin off.
  dashboard_set_agent_plugin_enabled resolves the canonical key and purges every alias from the
  opposing list (shared _activate_key, also used by cmd_enable/cmd_disable); the RPC reports the key.
- remove/uninstall (CLI, dashboard, RPC) left the plugin in plugins.enabled/disabled and
  plugins.entries (#54336), left memory.provider dangling so the next agent init re-cloned the
  provider from the catalog (uninstall silently reverted), and, for a symlink inside the plugins
  dir, deleted the TARGET plugin and its metadata while the link stayed dangling. _remove_user_plugin
  is the shared tail: unlink a link only, then forget config under every alias and reset
  memory.provider (reported as cleared_memory_provider).
- `hermes plugins list` / dashboard hub / TUI hub called bundled backends, bundled platforms and the
  live memory.provider "not enabled" (#73131, #82898): _plugin_status mirrors gate_manifest.
- A user-installed memory provider parked in plugins.disabled kept loading: load_memory_provider
  honours the deny-list (name, dir name or manifest name) and says so once.
2026-09-22 00:13:50 -07:00
ethernet
d0d4e91434 Merge remote-tracking branch 'origin/main' into ethie/pm-clean
# Conflicts:
#	hermes_cli/plugins_cmd.py
#	hermes_cli/plugins_cmd_catalog.py
#	tests/hermes_cli/test_web_plugins_catalog.py
#	website/docs/reference/cli-commands.md
#	website/docs/user-guide/features/plugin-catalog.md
2026-09-22 02:59:10 -04:00
teknium1
07347414ed fix: keep cron-provider plugins out of the general PluginManager
With `chronos` in plugins.enabled every start warned
"Failed to load plugin 'chronos': 'PluginContext' object has no attribute
'register_cron_scheduler'": collect_directory_manifests skipped the bundled
memory/context_engine/model-providers categories but not cron_providers, and a
user-installed cron provider under ~/.hermes/plugins/<name> was never
auto-coerced to kind=exclusive. Cron providers activate through
plugins.cron_providers (cron.provider config), so the general manager must only
record them, exactly like memory providers.

Fixes #62951
credit: @wdmason #100362 (slim redo; #100396 is the consolidated duplicate)
2026-09-21 23:38:16 -07:00
liuhao1024
c60693278a fix(plugins): skip foreign-harness manifest dirs during discovery
Multi-harness plugin repos (e.g. obra/superpowers) ship one plugin.json
per OTHER agent harness inside .claude-plugin/, .codex-plugin/,
.cursor-plugin/, .devin-plugin/ and .kimi-plugin/. Those manifests can
never satisfy the Agent Plugins v1 schema, so every discovery pass
rejected each one and logged a warning — ~1,500 warnings/day on the
reporter's install (#101962).

Skip these well-known per-harness convention directories during
directory scanning. A plugin's real Hermes manifest
(.hermes-plugin/plugin.yaml or a top-level plugin.yaml/plugin.json) is
unaffected, and genuinely broken portable manifests still warn.

Fixes #101962
2026-09-21 23:26:19 -07:00
Christopher
d0c1498a0d fix(plugins): skip dunder dirs without POSIX chmod in the scanner test
Name-based dunder skipping does not need an unreadable __pycache__.
Drop the chmod(0) setup so the test is portable, and log skipped
dunder paths at debug.
2026-09-21 23:26:19 -07:00
Christopher
f8dfa68fb3 fix(plugins): skip dunder dirs and contain plugin-scan OSErrors
Walking __pycache__ and other dunder children let an unreadable
directory raise out of plugin discovery and fail unrelated tool
calls. Skip those names and treat iterdir/is_dir OSError as an
empty scan for that node.

Fixes #86996
2026-09-21 23:26:19 -07:00
teknium1
aeff56aa35 fix(plugins): loader gates read the running version, survive SystemExit, quarantine deps everywhere
- requires_hermes compared against stale editable-install dist metadata (0.21.0) while the
  checkout ran 0.21.4, skipping plugins that required the release in use; hermes_cli.__version__
  is now the source of truth, dist metadata only a fallback.
- PEP 440 pre/post suffixes glued to a segment (99.0.0rc1) made a clause permissive and an rc
  running version disabled every gate; the segment parser drops the suffix.
- A plugin calling sys.exit() at import or in register() propagated SystemExit out of
  discovery: the whole registry emptied and `hermes chat` exited 3 with no output. Load
  isolation now covers SystemExit (KeyboardInterrupt still propagates).
- uv reads [tool.uv] exclude-newer from the cwd project only, so lazy/plugin dep installs
  launched from $HOME, a gateway service or the Desktop backend were never quarantined; the uv
  tier now runs from the checkout root when one exists.
- A flat user/project manifest naming a bundled key from a differently named directory no longer
  displaces the bundled plugin (warn + skip); a same-named override is logged at INFO.
2026-09-21 23:26:19 -07:00
teknium1
77f80d31ee fix(plugins): catalog provenance is installer-owned; kill list covers update/enable/load; re-pin keeps user files
Catalog trust bugs from the 2026-09-21 plugin audit (lane 3, F1-F6, F9):

- F1 (high): a URL-installed repo shipping its own .hermes-catalog.json rendered
  as catalog:official everywhere and marked the real entry "installed". Provenance
  now lives on the installer-owned .install-metadata.json record (catalog block
  written by _install_plugin_core, sha = checked-out commit); read_catalog_sidecar
  never reads the tree. Pre-fix installs are adopted once when the installer
  record agrees (pinned at the sidecar sha, cloned from the entry's repo).
- F2: removed.yaml bypassed by git@/ssh:///http:///www. spellings. _normalize_repo
  canonicalises to host/owner/repo (scheme, user, www., .git, slashes dropped).
- F6: removed.yaml consulted for INSTALLED plugins too: git-pull update, enable and
  gate_manifest (load) refuse recalled plugins, offline (in-tree + cached live
  list). --allow-removed is recorded on the install record and exempts it.
- F5: install NAME --ref X recorded the catalog pin, so list/TUI/update claimed
  the reviewed pin while HEAD differed. Recorded sha is the checked-out one.
- F3: re-pin replaced the whole tree, losing the installer-created config.yaml,
  data and user patches with no warning. Untracked/ignored files are carried
  into the new tree; edits to tracked files are copied to
  <HERMES_HOME>/plugins-backup/<name>-<sha8>/ with a warning.
- F4: a manifest rename between pins left the OLD dir installed and enabled.
  The stale dir is removed and the enabled flag follows the new name.
- F9: dashboard payload `removed` list now includes live removals.

repin_catalog_plugin returns RepinResult(sha, changed, installed_name, warnings);
CLI, dashboard and TUI callers surface the warnings and the new name.
2026-09-21 23:17:05 -07:00
Alex Fournier
ab8a99c730 fix(relay): honor ambient plugin configuration
Signed-off-by: Alex Fournier <afournier@nvidia.com>
2026-09-21 14:02:05 -07:00
ethernet
b4a294fff9 Merge origin/main; keep PM as plugin dependency owner
Reconcile plugin declarations and validation through PM's atomic generation publication; preserve external runtimes, target markers, and conflict refusal. Keep one source-update completion owner and port upstream lifecycle changes to the PM desktop/runtime paths.
2026-09-17 13:52:05 -04:00
teknium1
22716fab2a fix(plugins): one unreadable plugin dir no longer aborts loader or list discovery
`scan_directory` (the PluginManager sweep every CLI/gateway/Desktop backend
runs at startup) and `plugins_cmd._scan_level` (`hermes plugins list`, the
dashboard plugins hub, the TUI plugin picker) probed `plugin.yaml` with
`Path.exists()` outside any error handling. `stat()` raises instead of
returning False when the plugin directory itself is unsearchable — Windows
ACLs (WinError 5, the #111804 report) or a POSIX mode-000 folder — so a
single bad plugin folder took every other plugin down with it and the
Desktop backend exited before announcing its port.

Both scans now warn and skip that one directory, matching the dashboard
manifest scan fixed in the preceding (salvaged) commit.

Part of #111804
2026-09-15 18:48:59 -07:00
Teknium
2651e735d0 refactor(hermes_cli): compact plugin manifest/loader/discovery/ledger/state helpers (-100 LOC) 2026-09-03 00:09:39 -07:00
Teknium
a26d3b0012 refactor(hermes_cli): compact plugins_manifest/discovery/ledger/state helpers (shared name-set gating, ledger comprehensions) 2026-09-02 21:28:43 -07:00
Teknium
7f02cb7e1e refactor(cli/plugins): trim re-exports to referenced names; drop isort blanks after in-function imports 2026-09-02 15:57:40 -07:00
Teknium
d7c64815a7 refactor(cli/plugins): repack multi-line statements to <=100 cols (AST-identical) across plugins*.py 2026-09-02 15:53:53 -07:00
Teknium
c7d7e669c9 refactor(cli/plugins): unify manifest.key-or-name into manifest_key()/PluginContext.plugin_id (33 sites) 2026-09-02 15:44:28 -07:00
Teknium
1fcc26e2ba refactor(cli/plugins): extract manifest parsing, discovery scanning/gating, and plugin state into plugins_manifest/plugins_discovery/plugins_state 2026-09-02 15:32:17 -07:00