Commit Graph

11 Commits

Author SHA1 Message Date
Teknium ed653d3434 fix(gateway): profile editors hide opt-in toolsets the profile hasn't enabled (#86239)
Follow-up to #86227: _DEFAULT_OFF_TOOLSETS entries (a2a, spotify,
discord, video, x_search, ...) and the region-specific yuanbao are
global opt-ins configured via `hermes tools`/Settings; showing them
unchecked in every per-profile capabilities editor is noise, and
showing a2a at all confuses users who never enabled agent-to-agent
serving (tester report). Enabled ones still show — hiding an ACTIVE
toolset would misrepresent the profile.
2026-08-14 10:49:55 -07:00
Teknium cc1c125cf3 fix(gateway): honest capability surfaces for profile editors (#86227)
Three fixes for what profiles.describe & friends expose (tester
report with screenshot):

1. describe's toolsets used the RAW registry (get_all_toolsets) —
   leaking internal platform composites (hermes-discord, hermes-cron,
   feishu_drive, discord_admin, desktop_ui, ...) that are gated,
   platform-restricted, or deliberately hidden from users — and
   reported everything enabled whenever the profile had no pin.
   Now: the same filtered universe the `hermes tools` checklist
   offers (_get_effective_configurable_toolsets, platform-filtered),
   with enablement resolved via _get_platform_tools like the runtime.

2. skills.manage accepts optional `profile`: list/install scoped to
   that profile's skills dir via the home override, so editors can
   manage a bot's skills (incl. hub installs) from the main window.
   search/browse/inspect (the hub catalog) unchanged.

3. New mcp.catalog method: the bundled MCP catalog with per-profile
   installed/enabled state + required env keys, so capability UIs can
   offer the full menu and route un-setup entries through setup
   instead of silently listing dead servers.

E2E: describe now returns only user-facing toolsets with honest
enabled flags; mcp.catalog returns the 5-entry catalog; skills list
scopes to the named profile.
2026-08-14 10:29:43 -07:00
Teknium 0a8765a236 fix(gateway): model inheritance gated on the model section, not config.yaml existence (#86212)
profiles.create inherits the launch profile's provider+model when the
caller doesn't pin one — but the gate was 'config.yaml doesn't exist
yet'. Voice-section mirroring (#85755) runs FIRST and legitimately
creates config.yaml (tts/stt), so inheritance silently skipped for
every non-clone profile since: the bot's editor showed 'Inherit
(launch profile)' while the profile actually had NO model section,
and the first message failed with 'No inference provider configured'
even though the main agent was authenticated and working (Bot Mode
tester report, screenshots).

Gate on what we actually care about: the profile's own raw config
lacking a complete model section (provider+default). Clones bring
their own section and stay untouched; explicit pins unchanged.

E2E: create receipt now model_inherited=true and the fresh profile's
config.yaml carries the launch profile's provider/model.
2026-08-14 10:12:08 -07:00
Teknium 4bbc6f258a feat(gateway): share_auth on profiles.create + MCP servers in describe/configure (#85963)
Three widenings for capabilities UIs (Bot Mode's bot builder):

1. profiles.create share_auth (default false): skip the auth.json
   COPY so the new profile reads OAuth/token state through the
   existing global-root fallback and refreshes write through to it.
   A copy forks token state — the first refresh on either side
   invalidates the other for single-use refresh tokens; sharing keeps
   ONE live token pool for the main profile and every bot. Static
   .env keys still copy (no refresh semantics). Receipt:
   mirrored.auth = 'shared'.

2. profiles.describe reports mcp_servers
   [{name, enabled, transport}] from the profile's config.

3. profiles.configure accepts enabled_mcp_servers (replace
   semantics): toggles via the standard disabled flag; enabling a
   server the profile lacks copies its definition from the launch
   profile's catalog (names never invented). Launch catalog read
   BEFORE the home override flips config resolution.

E2E: describe keys include mcp_servers; create with share_auth ->
mirrored.auth='shared' + no auth.json in the profile dir; configure
applied.mcp_servers=true.
2026-08-14 02:08:16 -07:00
Teknium 6c40300f93 feat(gateway): roster previews show the latest message, not the first (#85905)
profiles.list's last_session.preview reused list_sessions_rich's
shared preview, which is the session's FIRST user message — right for
session lists (recognition), wrong for a messaging-style roster where
the line under each agent should track the conversation ('Hey, tell
me about yourself!' forever, per user report). Override with the
newest active user/assistant text (same query shape and lock
discipline as SessionDB.latest_message_row_id); best-effort, falls
back to the first-message preview on any failure.

E2E: live profiles.list now shows each bot's latest exchange.
2026-08-14 00:33:51 -07:00
Teknium 486f4ace20 fix: voice dictation broken in profiles created via profiles.create (missing stt/tts config) (#85755)
* fix: mirror voice config (stt/tts/voice) into profiles created via profiles.create

Desktop dictation is profile-scoped: /api/audio/transcribe resolves the
stt section inside the TARGET profile's home. Profiles created through
profiles.create got only a model section, so dictation and TTS silently
fell back to defaults (local whisper, often not installed) — 'voice
dictation doesn't work in bot mode but is fine in regular mode'.

Mirror the launch profile's stt/tts/voice sections (key-wise, never
overwriting sections the clone already has) under the same
mirror_credentials flag that gates .env/auth mirroring, and report it
as mirrored.voice in the receipt.

* guard: route voice-config mirror through canonical loaders

read_user_config_raw (write-back round-trip; load_config would merge
DEFAULT_CONFIG and no-op the mirror) + save_config under the target
profile's HERMES_HOME override — same mechanism as _write_profile_model.
Satisfies test_config_read_guard.
2026-08-13 19:01:11 -07:00
Teknium 7afac122ef feat: profile asset store (profiles.set_asset/get_asset) for avatars (#85530)
ui_meta (#85440) syncs compact roster metadata but is 64KB-capped
because it rides every profiles.list — image avatars stayed per-client.
set_asset writes a validated image (data URL or base64; PNG/JPEG/WebP
by magic bytes, 2MB cap, atomic write) to assets/avatar.<ext> in the
profile dir; get_asset returns it as a data URL on demand; profiles.list
gains a cheap has_avatar flag so rosters know to fetch without probing.
Server-side, so every client machine paints the same profile picture.
2026-08-13 11:36:40 -07:00
Teknium 2ffed55c32 feat: server-side ui_meta on profiles.list/configure (#85440)
* feat: server-side ui_meta on profiles.list/configure

Roster UIs built on profiles.* have per-profile presentation state
(avatar, accent color, display title, pet) with nowhere server-side to
live — client plugin storage paints a different roster on every
machine. profiles.configure now accepts ui_meta (merged key-wise into
profile.yaml's ui_meta block via the existing atomic_yaml_write path,
null deletes a key, 64KB cap since it rides every roster paint) and
profiles.list returns the block per row. Consumers namespace under
their own key. No new files or config; profiles without the block are
unchanged.

* test: stop primary-runtime-restore tests probing live endpoints

_make_agent left the compressor's lazy context-length resolution
unmocked; for reachable base_urls (the nous portal test) the endpoint's
32K answer for the empty test model trips agent_init's 64K floor and
fails the suite on network behavior. Pin get_model_context_length in
the fixture.
2026-08-13 10:07:39 -07:00
Teknium f795812c16 feat: profiles.describe/profiles.configure ws RPC for profile editors (#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.
2026-08-13 03:12:10 -07:00
Teknium 9460cc11d4 fix: profiles.create mirrors launch credentials so new profiles can run (#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.
2026-08-13 00:25:04 -07:00
Teknium 89a84e1ae6 feat: profiles.list/profiles.create ws RPC + plugin session-navigation doors (#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.
2026-08-12 23:33:58 -07:00