Skip to content

feat: profiles.list/profiles.create ws RPC + plugin session-navigation doors - #85093

Merged
teknium1 merged 1 commit into
mainfrom
feat/profiles-rpc-and-plugin-session-doors
Aug 13, 2026
Merged

teknium1 merged 1 commit into
mainfrom
feat/profiles-rpc-and-plugin-session-doors

Conversation

@teknium1

Copy link
Copy Markdown
Collaborator

What

Widens the generic plugin surface so profile-shaped desktop plugins are possible — the "plugins live at the edges; if a plugin needs more, widen the generic plugin surface" path from AGENTS.md.

Two additions, both with an immediate concrete consumer (a Grok Bot-style "Bots" roster plugin: one persistent chat per agent profile, a New Agent dialog, cross-profile chat opens):

1. profiles.list / profiles.create ws JSON-RPC (tui_gateway/methods_profiles.py)

Desktop plugins reach the backend exclusively through host.request (ws JSON-RPC), but profile enumeration/creation only existed on the dashboard REST router (hermes_cli/web_routers/profiles.py), which plugins cannot reach.

  • profiles.list — every profile (name, path, model/provider, description, skill_count) plus an optional last_session preview per profile (include_sessions: false to skip). The per-profile state.db probe mirrors session.list's kanban/tool deny-list and is best-effort: a missing/locked db degrades that profile's preview to null instead of failing the call.
  • profiles.create — ws twin of POST /api/profiles: name, description, clone_from, clone_all, no_skills, plus optional soul (SOUL.md content) and a best-effort model + provider pin. Mirrors the CLI/REST create flow (seed bundled skills for fresh profiles, safe alias wrapper).

Both registered in the RPC pool set — list_profiles() walks skill trees and create_profile() copies bundles, so neither belongs on the WS reader thread. Handler helpers are nested inside the handler body (the method_ctx.py install seam rebinds __globals__ onto server.py, so module-level helpers are invisible — noted in a comment for the next author).

2. SDK session-navigation doors (apps/desktop/src/sdk/index.ts)

  • host.openSession(id, { profile?, intent? }) — open a stored session the way core surfaces do (focus existing tile/main, else load into main), soft-swapping to the owning profile's backend first via ensureGatewayProfile — the same gesture the unified sidebar uses for cross-profile resumes.
  • host.newChat(profile?) — fresh chat draft pointed at a named profile (the sidebar per-profile "+" door, newSessionInProfile).

Without these, a plugin could only host.navigate('/<sessionId>'), which breaks for any session owned by a non-active profile.

Verification

  • profiles.list / profiles.create exercised end-to-end against the real registry (server._methods[...] dispatch): list returns the live roster with last_session previews; create built a real profile (dir + SOUL.md + skills), verified on disk, then deleted via hermes profile delete.
  • Renderer compiles with the SDK additions (vite build green in a full desktop npm run pack).
  • Docs updated: website/docs/developer-guide/desktop-plugin-sdk.md documents all four surfaces.

No new model tools, no config keys, no env vars; prompt caching and message alternation untouched. Sweep of open issues/PRs found no duplicates for this surface.

…n doors

Desktop plugins reach the backend exclusively through the generic ws
JSON-RPC door (host.request), but profile enumeration/creation only
existed on the dashboard REST router, which plugins cannot reach — so
anything 'one chat per agent profile'-shaped (bot rosters, profile
pickers, team panes) was impossible to build as a plugin.

- tui_gateway/methods_profiles.py: new @method handlers
  * profiles.list — profiles + optional last_session preview per profile
    (mirrors session.list's kanban/tool deny-list; best-effort per-profile
    state.db probe degrades to null instead of failing the call)
  * profiles.create — ws twin of POST /api/profiles (clone_from/clone_all/
    no_skills/description), plus optional SOUL.md content and a best-effort
    model+provider pin; mirrors the CLI flow (seed skills, safe alias)
  Both run on the RPC pool, not the WS reader thread (list_profiles walks
  skill trees; create copies bundles).
- SDK: host.openSession(id, { profile, intent }) — open a stored session
  the way core surfaces do, soft-swapping to the owning profile's backend
  first (ensureGatewayProfile), and host.newChat(profile) — fresh draft in
  a named profile (same door as the sidebar's per-profile '+').
- Docs: desktop-plugin-sdk.md gains both surfaces.

First consumer: a Grok Bot-style 'Bots' roster plugin (one persistent
chat per agent profile with a New Agent dialog) built on exactly these
four doors.
@github-actions

github-actions Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

૮ >ﻌ< ა ci review

ran on da118f6 — feat: profiles.list/profiles.create ws RPC + plugin session-

⚠️ Warnings

CI timings · View report · View job

Wall time 7m8s vs 5m37s (+27.0%). 24 job(s) slower, 11 faster, 1 unchanged.

  • JS & TS checks / web / check: +32.0s
  • Python tests / Run tests slice 3/12: +31.0s
  • Python tests / Run tests slice 2/12: -29.0s
  • JS & TS checks / ui-tui/packages/hermes-ink / check: -28.0s
  • Python tests / Run tests slice 4/12: +24.0s

OSV vulnerability scan · View job

3 known vulnerabilities found in pinned dependencies.

How to fix:

Review the findings in the Security tab. Update the affected dependencies if a patched version is available.

@teknium1
teknium1 merged commit 89a84e1 into main Aug 13, 2026
55 checks passed
@teknium1
teknium1 deleted the feat/profiles-rpc-and-plugin-session-doors branch August 13, 2026 06:34
teknium1 added a commit that referenced this pull request Aug 13, 2026
…un (#85111)

A profile created through the headless ws door (profiles.create, #85093)
was born with no inference provider: create_profile() seeds a comment-only
.env, never copies auth.json, and a fresh profile has no config.yaml. Its
first message failed with 'No inference provider configured' and the flow
has no interactive setup step to recover with.

New mirror_credentials param (default true): copy the launch profile's
.env (only over the seeded stub — never clobber cloned secrets) and
auth.json (only when absent), both chmod 600, and inherit
model.provider/model.default when the caller gave no explicit pin and no
config was cloned. mirror_credentials:false preserves the old isolated
behavior byte-for-byte. Result gains a mirrored:{env,auth,model_inherited}
receipt. CLI and REST create paths untouched.
teknium1 added a commit that referenced this pull request Aug 13, 2026
…#85216)

profiles.list/create (#85093) let plugins enumerate and create profiles
but not read or modify an existing profile's configuration over ws.
profiles.describe returns the full editor snapshot (description, SOUL.md,
model pin, per-skill enablement via the disabled-list model, per-toolset
enablement via the tools.enabled_toolsets pin); profiles.configure
applies any subset (description via write_profile_meta, soul, model via
_write_profile_model, disabled_skills replace-semantics via
save_disabled_skills, enabled_toolsets replace-semantics with empty-list
clearing the pin) independently and best-effort, reporting per-section
results. Both scoped via the HERMES_HOME override and pool-dispatched.
skappafrost pushed a commit to skappafrost/hermes-agent that referenced this pull request Aug 15, 2026
…n doors (NousResearch#85093)

Desktop plugins reach the backend exclusively through the generic ws
JSON-RPC door (host.request), but profile enumeration/creation only
existed on the dashboard REST router, which plugins cannot reach — so
anything 'one chat per agent profile'-shaped (bot rosters, profile
pickers, team panes) was impossible to build as a plugin.

- tui_gateway/methods_profiles.py: new @method handlers
  * profiles.list — profiles + optional last_session preview per profile
    (mirrors session.list's kanban/tool deny-list; best-effort per-profile
    state.db probe degrades to null instead of failing the call)
  * profiles.create — ws twin of POST /api/profiles (clone_from/clone_all/
    no_skills/description), plus optional SOUL.md content and a best-effort
    model+provider pin; mirrors the CLI flow (seed skills, safe alias)
  Both run on the RPC pool, not the WS reader thread (list_profiles walks
  skill trees; create copies bundles).
- SDK: host.openSession(id, { profile, intent }) — open a stored session
  the way core surfaces do, soft-swapping to the owning profile's backend
  first (ensureGatewayProfile), and host.newChat(profile) — fresh draft in
  a named profile (same door as the sidebar's per-profile '+').
- Docs: desktop-plugin-sdk.md gains both surfaces.

First consumer: a Grok Bot-style 'Bots' roster plugin (one persistent
chat per agent profile with a New Agent dialog) built on exactly these
four doors.
skappafrost pushed a commit to skappafrost/hermes-agent that referenced this pull request Aug 15, 2026
…un (NousResearch#85111)

A profile created through the headless ws door (profiles.create, NousResearch#85093)
was born with no inference provider: create_profile() seeds a comment-only
.env, never copies auth.json, and a fresh profile has no config.yaml. Its
first message failed with 'No inference provider configured' and the flow
has no interactive setup step to recover with.

New mirror_credentials param (default true): copy the launch profile's
.env (only over the seeded stub — never clobber cloned secrets) and
auth.json (only when absent), both chmod 600, and inherit
model.provider/model.default when the caller gave no explicit pin and no
config was cloned. mirror_credentials:false preserves the old isolated
behavior byte-for-byte. Result gains a mirrored:{env,auth,model_inherited}
receipt. CLI and REST create paths untouched.
skappafrost pushed a commit to skappafrost/hermes-agent that referenced this pull request Aug 15, 2026
…NousResearch#85216)

profiles.list/create (NousResearch#85093) let plugins enumerate and create profiles
but not read or modify an existing profile's configuration over ws.
profiles.describe returns the full editor snapshot (description, SOUL.md,
model pin, per-skill enablement via the disabled-list model, per-toolset
enablement via the tools.enabled_toolsets pin); profiles.configure
applies any subset (description via write_profile_meta, soul, model via
_write_profile_model, disabled_skills replace-semantics via
save_disabled_skills, enabled_toolsets replace-semantics with empty-list
clearing the pin) independently and best-effort, reporting per-section
results. Both scoped via the HERMES_HOME override and pool-dispatched.
bobaba76 pushed a commit to bobaba76/hermes-agent that referenced this pull request Aug 27, 2026
…n doors (NousResearch#85093)

Desktop plugins reach the backend exclusively through the generic ws
JSON-RPC door (host.request), but profile enumeration/creation only
existed on the dashboard REST router, which plugins cannot reach — so
anything 'one chat per agent profile'-shaped (bot rosters, profile
pickers, team panes) was impossible to build as a plugin.

- tui_gateway/methods_profiles.py: new @method handlers
  * profiles.list — profiles + optional last_session preview per profile
    (mirrors session.list's kanban/tool deny-list; best-effort per-profile
    state.db probe degrades to null instead of failing the call)
  * profiles.create — ws twin of POST /api/profiles (clone_from/clone_all/
    no_skills/description), plus optional SOUL.md content and a best-effort
    model+provider pin; mirrors the CLI flow (seed skills, safe alias)
  Both run on the RPC pool, not the WS reader thread (list_profiles walks
  skill trees; create copies bundles).
- SDK: host.openSession(id, { profile, intent }) — open a stored session
  the way core surfaces do, soft-swapping to the owning profile's backend
  first (ensureGatewayProfile), and host.newChat(profile) — fresh draft in
  a named profile (same door as the sidebar's per-profile '+').
- Docs: desktop-plugin-sdk.md gains both surfaces.

First consumer: a Grok Bot-style 'Bots' roster plugin (one persistent
chat per agent profile with a New Agent dialog) built on exactly these
four doors.
bobaba76 pushed a commit to bobaba76/hermes-agent that referenced this pull request Aug 27, 2026
…un (NousResearch#85111)

A profile created through the headless ws door (profiles.create, NousResearch#85093)
was born with no inference provider: create_profile() seeds a comment-only
.env, never copies auth.json, and a fresh profile has no config.yaml. Its
first message failed with 'No inference provider configured' and the flow
has no interactive setup step to recover with.

New mirror_credentials param (default true): copy the launch profile's
.env (only over the seeded stub — never clobber cloned secrets) and
auth.json (only when absent), both chmod 600, and inherit
model.provider/model.default when the caller gave no explicit pin and no
config was cloned. mirror_credentials:false preserves the old isolated
behavior byte-for-byte. Result gains a mirrored:{env,auth,model_inherited}
receipt. CLI and REST create paths untouched.
melon-xf added a commit to melon-xf/hermes-agent that referenced this pull request Sep 3, 2026
…n doors (NousResearch#85093)

Desktop plugins reach the backend exclusively through the generic ws
JSON-RPC door (host.request), but profile enumeration/creation only
existed on the dashboard REST router, which plugins cannot reach — so
anything 'one chat per agent profile'-shaped (bot rosters, profile
pickers, team panes) was impossible to build as a plugin.

- tui_gateway/methods_profiles.py: new @method handlers
  * profiles.list — profiles + optional last_session preview per profile
    (mirrors session.list's kanban/tool deny-list; best-effort per-profile
    state.db probe degrades to null instead of failing the call)
  * profiles.create — ws twin of POST /api/profiles (clone_from/clone_all/
    no_skills/description), plus optional SOUL.md content and a best-effort
    model+provider pin; mirrors the CLI flow (seed skills, safe alias)
  Both run on the RPC pool, not the WS reader thread (list_profiles walks
  skill trees; create copies bundles).
- SDK: host.openSession(id, { profile, intent }) — open a stored session
  the way core surfaces do, soft-swapping to the owning profile's backend
  first (ensureGatewayProfile), and host.newChat(profile) — fresh draft in
  a named profile (same door as the sidebar's per-profile '+').
- Docs: desktop-plugin-sdk.md gains both surfaces.

First consumer: a Grok Bot-style 'Bots' roster plugin (one persistent
chat per agent profile with a New Agent dialog) built on exactly these
four doors.
melon-xf added a commit to melon-xf/hermes-agent that referenced this pull request Sep 3, 2026
…un (NousResearch#85111)

A profile created through the headless ws door (profiles.create, NousResearch#85093)
was born with no inference provider: create_profile() seeds a comment-only
.env, never copies auth.json, and a fresh profile has no config.yaml. Its
first message failed with 'No inference provider configured' and the flow
has no interactive setup step to recover with.

New mirror_credentials param (default true): copy the launch profile's
.env (only over the seeded stub — never clobber cloned secrets) and
auth.json (only when absent), both chmod 600, and inherit
model.provider/model.default when the caller gave no explicit pin and no
config was cloned. mirror_credentials:false preserves the old isolated
behavior byte-for-byte. Result gains a mirrored:{env,auth,model_inherited}
receipt. CLI and REST create paths untouched.
melon-xf added a commit to melon-xf/hermes-agent that referenced this pull request Sep 3, 2026
…NousResearch#85216)

profiles.list/create (NousResearch#85093) let plugins enumerate and create profiles
but not read or modify an existing profile's configuration over ws.
profiles.describe returns the full editor snapshot (description, SOUL.md,
model pin, per-skill enablement via the disabled-list model, per-toolset
enablement via the tools.enabled_toolsets pin); profiles.configure
applies any subset (description via write_profile_meta, soul, model via
_write_profile_model, disabled_skills replace-semantics via
save_disabled_skills, enabled_toolsets replace-semantics with empty-list
clearing the pin) independently and best-effort, reporting per-section
results. Both scoped via the HERMES_HOME override and pool-dispatched.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant