Skip to content

feat(plugins): generic session execution targets, hook identity, setup and Desktop session viewers - #104567

Draft
Zeus-Deus wants to merge 11 commits into
NousResearch:mainfrom
Zeus-Deus:feat/bundled-agent-realms
Draft

Zeus-Deus wants to merge 11 commits into
NousResearch:mainfrom
Zeus-Deus:feat/bundled-agent-realms

Conversation

@Zeus-Deus

@Zeus-Deus Zeus-Deus commented Sep 6, 2026 •

Copy link
Copy Markdown

Update 2026-09-28: ported onto current main. Merged origin/main (no rebase). Upstream had split plugins_cmd.py into plugins_cmd_* modules and moved enable/install/picker onto the PM admission transaction, so setup consent now runs inside that path. plugins enable, install, the picker, packs, the dashboard/RPC toggle and the new catalog-card switch all go through the reviewed setup gate. The setup lock is taken only for plugins that declare setup:; other plugins keep the admission transaction's own concurrency. Conflicts were also resolved in the terminal/file/process routing (upstream's task-key qualification and container-visible cwd are kept alongside the routed-target cwd), in the computer-use backend (a session-owned target never falls back to the terminal sandbox), and in the Plugins tab, install modal, status stack and agent-plugins store. Tests now use platforms(...) marks and ruamel YAML, matching main. No behaviour change intended.


Generic plugin session surfaces

This PR adds the generic core surfaces that let a plugin own a private execution environment for a session. It contains no product-specific plugin; every surface is plugin-neutral and tested with a small neutral test plugin (sample-target).

It supersedes #103690, which is folded in here, so there is one core PR.

What it adds

  • Execution targets. A plugin can register a named terminal target (register_terminal_target_resolver) and a computer_use target resolver.
    • Tools route to a target only when a call names it explicitly, e.g. terminal(target="<name>", ...). Ordinary tools, files and cwd are unchanged.
    • Every provider needs a non-provisioning selector. Selection reads state only and starts no compute.
    • After approval, the same target and authority are revalidated before start or execution. Stale actions are refused, never retargeted.
    • Routed cwd, background processes and the process registry follow the target. The code-execution sandbox refuses a named target. computer_use backend admission retries are bounded.
  • Session hook identity. Plugins receive a stable session identity (on_session_identity, plus session start/finalize/reset) and can deliver an idle continuation into the owning session (on_session_idle).
  • Plugin setup. A consented, cancellable setup lifecycle for plugins that need prerequisites (setup: entrypoint in plugin.yaml, setup_consent in plugins.manage, default_enabled), with progress that survives UI remounts and timeout cleanup of the whole process group.
  • Plugin session viewers in the Desktop SDK. Plugins can contribute a per-session viewer:
    • status and session-tile contributions;
    • authenticated view-only / take-over viewer windows, including Pop out;
    • keep-alive and settings slots.
    • Viewer popups reuse the existing reveal fallback so they appear on Wayland.
  • Docs: session-execution-context-api.md, session-hook-identity-api.md, plugin-idle-continuations.md, desktop-plugin-sdk.md, plugins/index.md and apps/desktop/docs/plugin-session-viewers.md.

Where the example plugin went

The optional Realms plugin that drove this work now ships as a standalone plugin, per CONTRIBUTING.md ("Ship as a Standalone Plugin"): https://github.com/Zeus-Deus/hermes-realms (v0.2.0). Its catalog entry will be proposed separately after this PR merges. Core no longer bundles, packages or special-cases it, and pyproject.toml and uv.lock are unchanged from main.

Verification

  • Rebased on current main; the 35 affected test files (608 tests) pass after the rebase. Full Python suite via scripts/run_tests.sh: no failures unique to this branch. The remaining local failures also fail on plain main locally (no DNS / host-specific) or were load flakes that pass on rerun. tests/e2e matches main.
  • Desktop typecheck, the changed vitest files and ESLint pass. The gateway contract --check, ruff, the lint.yml gates and uv lock --check pass.
  • Native check on an isolated private Desktop build of this branch. The standalone plugin was installed with hermes plugins install (v0.2.0, not bundled; its desktop half loaded from the install) and drove a private VM target: a targeted command ran inside the VM, while gh auth status and git ran normally on the host. The plugin declares requires_hermes >=0.22, so the private build carried a local version bump for this check only.
  • Diff and commit messages are privacy-scrubbed.

CI for this fork PR still needs maintainer approval to run.

@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/plugins Plugin system and bundled plugins comp/desktop Electron desktop app (apps/desktop/*) comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/cli CLI entry point, hermes_cli/, setup wizard comp/gateway Gateway runner, session dispatch, delivery comp/dashboard Web dashboard / control panel UI (dashboard/, landing) area/nix Nix flake, NixOS module, container packaging needs-decision Awaiting maintainer decision before any implementation labels Sep 6, 2026
@Zeus-Deus

Zeus-Deus commented Sep 7, 2026 •

Copy link
Copy Markdown
Author

Realms status-polling fix — automated verification update

This follow-up fixes the flashing “Realm unavailable” sidebar badges and “Realm status unavailable · Retry” thread row when the desktop UI is enabled but a session has no confirmed realm.

  • Failed lookups no longer invent realm badges for ordinary sessions.
  • Known realm badges retain their last-known count through transient failures and disclose stale status on hover.
  • Historical sessions with wholly unregistered identities return an empty realm list without registering ownership. Conflicting, partial and malformed ownership remains rejected; Watch authorization is not relaxed.
  • Clarifies that the desktop toggle enables viewing controls, while the agent plugin is enabled per profile.

Verification: regression tests cover polling through pending/error transitions, cached-badge recovery and ownership boundaries. A separate native Electron DEV instance reproduced the flashing against a real disabled backend; the fixed build stayed quiet with both disabled and enabled backends. No provider credentials or model turns were used.

This is status/UI regression verification, not a new end-to-end validation of compositor creation, Watch/Pop out or takeover. The existing draft state and prerequisite dependency are unchanged.

Pushed in a5837e0. Focused checks on the PR branch: 9 frontend tests and 15 Python tests passed, plus Desktop typecheck, targeted ESLint and Ruff.

GitHub verification is now complete for this commit: CI, Docker, and Nix all passed. The PR remains a draft pending its existing prerequisite.

@Zeus-Deus

Copy link
Copy Markdown
Author

Follow-up: keep Watch connected

Commit f3c630e595 fixes viewer disconnection at the five-minute ticket expiry.

  • The authenticated Desktop owner renews authorization while the original viewer document or owned popup remains live, including mounted background tabs. Guest crash, removal, replacement, navigation, closure and disposal stop renewal.
  • Expired/revoked tickets and stale realm generations cannot be renewed. Copied viewer links cannot renew themselves.
  • Transient disconnects retry inside the same viewer with bounded backoff, always returning to view-only mode rather than automatically retaking control.

Verification:

  • Full Desktop UI/Electron suite: 9,854 passed, 6 skipped.
  • Targeted backend/packaging suite: 53 passed.
  • Typecheck, focused lint/format checks and Desktop build passed.
  • Disposable native Electron + private labwc realm: Watch remained connected while parked, Pop out had zero disconnects before intentional close, and closing stopped renewal. The 380-second run crossed the original expiry, recorded six preview renewals and made zero model calls.
  • Separate real REST/WebSocket transport soaks crossed the original expiry for both realm-kind bindings and verified revocation. These used display/liveness fixtures; they were not live Omarchy VM tests.
  • Independent review cleared the live-document authorization fix.

Only worktree source and disposable test resources were changed; no installed Desktop build or existing agent/VM was updated or restarted. Diff and commit text were scrubbed for private paths/identifiers; no raw rig logs or screenshots are attached.

GitHub sees the new head, but all three workflow runs currently report action_required; hosted CI is not green yet.

@Zeus-Deus

Zeus-Deus commented Sep 23, 2026 •

Copy link
Copy Markdown
Author

CI for the current head (dd23bd67c5) hasn't run yet:

  • Docker Build and Nix flake check are waiting for maintainer approval (action_required), because this is a fork PR.
  • The ci.yaml run failed before any job started (0 jobs). Several other PRs failed the same way around 14:24 UTC today, so it looks like a transient GitHub-side failure rather than this branch. The workflow files match main.

Could a maintainer please approve the workflows and re-run CI?

Heads-up on placement: following CONTRIBUTING and the new plugin catalog, I'm moving Realms out of this tree. Realms itself will ship from its own repo (Zeus-Deus/hermes-realms) with a plugin-catalog/ entry. This PR will shrink to the generic plugin surfaces it needs, with no Realms-specific code in core:

  • per-session execution targets for terminal / computer_use;
  • session hook identity;
  • plugin setup entrypoints;
  • plugin session viewers in the Desktop SDK.

I'll update the title and description when that lands. If you'd rather keep it bundled, say so and I'll stop.

Give plugin hooks, slash commands and pre_tool_call a trusted owner identity
(runtime/stored session ids, profile, hermes_home, source/surface and
session_origin) and publish it through a new on_session_identity hook from
the CLI, gateway and TUI/Desktop lifecycles. Hooks run in the owning profile.
Add a session execution context API so a plugin can route a session's
terminal, file, process and computer-use work into an environment it owns.
Named terminal/computer_use targets are registered by plugins and require an
explicit, non-provisioning selector; selections are revalidated before use,
background processes keep their routed cwd, the code-exec sandbox refuses a
named target instead of falling back, and computer-use backend admission
retries are bounded. load_config_readonly(home=...) reads another profile
without writing to it.
Plugins may declare a setup entrypoint that runs only after the user reviews
and consents to a profile- and revision-bound proposal (CLI, dashboard and
plugins.manage). Bundled plugin rows report default_enabled separately from
status. A new on_session_idle hook lets a plugin submit one owner-pinned
native turn at an idle boundary to continue work it was asked to do.
Let desktop plugins contribute session-scoped UI (status stack, session tile,
sidebar row), open sandboxed isolated preview viewers and native viewer
windows with keep-alive renewal, render a plugin settings slot, and review
native plugin setup before enabling. Viewer popups are revealed without
focus stealing on Wayland.
@Zeus-Deus
Zeus-Deus force-pushed the feat/bundled-agent-realms branch from dd23bd6 to bf4dd5e Compare September 24, 2026 05:51
@Zeus-Deus Zeus-Deus changed the title feat(plugins): optional per-agent Linux desktops with Realms feat(plugins): generic session execution targets, hook identity, setup and Desktop session viewers Sep 24, 2026
…-Watch

A plugin viewer bootstraps from a one-time #ticket= fragment that it
strips on load. The workspace-edit auto-reload treated the loopback
viewer like a dev server and reloaded it ticketless, which ended the
view and its keep-alive. Isolated viewer tabs are now never
auto-reloaded.

Isolated tabs were also reused only on an exact URL match, so every
Watch (fresh ticket) opened another tab and left dead ones behind.
Reuse now matches the viewer location without the fragment, so a new
Watch replaces the old tab. The new ticket's keep-alive binds to the
rebuilt guest document, never the one it replaced.
…ager

Route the catalog card's agent switch through the reviewed setup-consent
toggle, take the setup lock only for plugins that declare native setup so
the admission transaction keeps its own concurrency, and move the branch's
tests onto the platforms(...) marks, ruamel YAML and the current plugin-set
helpers.
# Conflicts:
#	apps/desktop/src/store/preview.ts
#	tools/terminal_tool.py
# Conflicts:
#	hermes_cli/plugins_manifest.py

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/nix Nix flake, NixOS module, container packaging comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/cli CLI entry point, hermes_cli/, setup wizard comp/dashboard Web dashboard / control panel UI (dashboard/, landing) comp/desktop Electron desktop app (apps/desktop/*) comp/gateway Gateway runner, session dispatch, delivery comp/plugins Plugin system and bundled plugins needs-decision Awaiting maintainer decision before any implementation P3 Low — cosmetic, nice to have type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants