Commit Graph

101 Commits

Author SHA1 Message Date
Shawn Wang 80f080b6ed fix(bot-mode): highlight selected group chat
Signed-off-by: Shawn Wang <32839114+enwaiax@users.noreply.github.com>
2026-08-18 21:52:11 -07:00
chelsealong 3809f740c6 fix(bot-mode): reseed $selectedBot on register so re-enabling never leaves it stale
nanostores' .listen() never replays the current value the way .subscribe()
does, so the $focusedBotProfile listener in register() only kept
$selectedBot current from the moment it was attached. A disable -> profile
switch -> re-enable cycle (Settings > Plugins) left $selectedBot pointed at
whichever bot was active before the plugin was disabled, so the roster
highlight fallback and Routines scoping could start from a stale bot.

Extract the sync into bindProfileSync(), which reseeds $selectedBot from
the profile store's current value before attaching the listener. This runs
on every register() call, so re-enabling the plugin always starts in sync.

Salvaged from PR #89637 (the pane-precedence portion was superseded on main
by the $focusedBotProfile design; this residual reseed gap remained).
Regression test mimics real nanostores get/listen semantics and fails
without the reseed.

Fixes-residual-of: #89625
2026-08-18 21:51:36 -07:00
David Dudok de Wit 00e9a32454 fix(desktop): keep Bot Mode routines docked 2026-08-18 21:50:56 -07:00
Jay 104ec97595 fix(bot-mode): group room preview shows the bot handle, not @default
`botHandle()` exists so that, per its own comment, "the word 'default'
never surfaces in the UI" — it presents the primary profile as `hermes`.
The roster rows, mention resolution and the group-chat prompt all route
through it. Two preview paths did not, and rendered the raw profile name:

- `GroupRow`'s room preview line built `@${last.from?.name}`, so a group
  room read `@default: …` while the bot answers to `@hermes`.
- `previewKind()` returned the raw captured name from the bot-to-bot
  delivery prefix, so the `🤖 @<name>` badge and its tooltip could show
  `@default` too.

The mismatch is presentation-only, but it reads as a routing bug: the
room says the message came from `@default` while `@default` is not a
handle the mention resolver accepts, so users reasonably conclude
bot-to-bot addressing is broken when it is working correctly.

Both paths now map through `botHandle()`. `GroupRow` passes the matching
member so a bot with a custom handle keeps it; `previewKind` maps the
lowercased sender name, which leaves every non-primary profile unchanged.

Tests: the primary profile resolves to `hermes` and a named profile keeps
its own handle (behavioural, in the existing previewKind suite), plus a
source-shape assertion for the render path matching that file's
convention. Both new assertions fail against the pre-fix source.

Fixes #89484
2026-08-18 21:50:31 -07:00
Teknium cb0fd836ac feat: Bot Mode blob avatars gain 4 new silhouettes via blobatar 2.0.0
blobatar 0.2.0 -> 2.0.0 (gen2). Ten silhouettes instead of six:
capsule, triangle, hexagon and droplet join round, organic, boxy,
nub, cloud and sun. BLOB_KIND_TRAIT repinned to gen2 band centers
(empirically verified against the published package: every pinned
value resolves to its named silhouette across seeds). The avatar
picker derives from BLOB_KINDS, so the new shapes appear there
automatically. Note: gen2 remaps most unpinned seed->face mappings
by design (upstream generation change).
2026-08-18 21:45:29 -07:00
Teknium 23c1c9815f fix(desktop): Bots sidebar highlight and Cronjobs tile now follow the chat on screen
The Bots roster highlight and the Routines (Cronjobs) tile were keyed off
host.state.profile — the gateway socket's home. Tab/tile focus moves without
swapping the socket, so opening one bot's chat while the socket was homed on
another highlighted the wrong bot and showed the wrong bot's cronjobs
(community report: Newsanalyst chat open, Hermes highlighted).

- sdk: new host.state.focusedSessionProfile — owner profile of the focused
  chat, resolved from the focused stored session's row stamp via
  rememberedSessionProfile() (same ladder as remembered navigation and the
  HUD), with the gateway profile as the draft/uncached fallback.
- hermes-bots: $focusedBotProfile = focusedSessionProfile || profile
  (feature-detected; older desktops keep prior behavior). BotRow highlight,
  RoutinesPane scope, and the $selectedBot tracker use it. Turn-busy 'work'
  mood stays keyed to the socket-home profile (only it can be mid-turn).
- tests: SDK atom behavior (vitest) + plugin source-shape suite; prewarm
  harness stubs gain the new atom.
- docs: SDK page + hermes-agent skill reference list the new atom.
2026-08-18 19:51:13 -07:00
Teknium 4ac938ddec feat(desktop): sessions/bots tabs are show/hide chrome — no close gestures, right-click + Cmd-K toggles
Builds on #89551 (@calvinnwq, cherry-picked): his showCloseButton flag
hid the hover X; this completes the model so standing chrome can never
be closed at all, only shown/hidden (#89546).

- hideOnly pane chrome (sessions + Bots): no hover X, no middle/meta
  click close, no Close verbs in the tab menu, excluded from
  close-others/right/all sweeps
- zone right-click menu gains Show/Hide rows for the strip's chrome
  tabs (Hide bots / Show sessions, localized in 6 locales)
- Cmd-K palette: auto-registered "Toggle <tab> tab" rows for every
  hideOnly pane, on-screen truth semantics, plugin panes included via
  registry subscription
- hides persist across launches (survive the enforced dock re-adopt);
  reveal intent and Layout reset clear them
- last-visible-tab guard: hiding the zone's last shown tab is refused
  with a toast, so the strip can never become an empty dead zone
2026-08-18 17:14:45 -07:00
Calvin Ng 34d1aed3f1 fix(desktop): hide close affordance on navigation tabs
Add a pane-level opt-out for the hover close button and apply it to the persistent Sessions and Bots navigation panes. Keep their existing close handlers and other tab behavior intact.\n\nFixes #89546
2026-08-18 17:14:45 -07:00
Teknium 97b41f8cf3 feat(bot-mode): group chats accept PDFs, files, and drag & drop
Completes group/1:1 attachment parity (#88983). PR #89486 covered images;
this adds the remaining half:

- The composer picker accepts any file type; kind (image/pdf/file) decides
  the staging RPC. Paste handlers accept non-image files too.
- Drag & drop anywhere on the room drops into the active composer (open
  reply box, else main), with a drop overlay naming the target.
- Member turns stage PDFs via pdf.attach (rendered per-page into vision
  tiles by the gateway) and other files via file.attach; each returned
  @file: ref is appended to that member's turn prompt so file tools can
  read the artifact. Failed attaches still degrade to text-only.
- Transcript markers distinguish [attached PDF: x] / [attached file: x] /
  [attached image: x]; room log renders non-image attachments as named
  chips, pending chips show type icons.

Tests: 3 new vm-harness tests (per-kind RPC routing across members,
@file: ref injection into turn prompts, transcript labels); 279 total pass.
2026-08-18 16:27:20 -07:00
Teknium a77ee88ce2 feat: Bot Mode avatars default to deterministic blob faces drawn from the agent's name
New agents now get a blobatar — a deterministic soft-body face generated
from the bot's name (same name, same face, forever) — as the default
shapes mode, with full manual control:

- Face follows the name live while typing in New Agent
- Randomize re-rolls the seed; Lock face pins the current one so a later
  rename can't change it (Unlock returns to name-following)
- Any of the six silhouettes (round/organic/boxy/nub/cloud/sun) can be
  pinned via frozen-per-major trait positions while the rest stays
  name-derived
- Classic geometric shapes remain one click away, and existing bots keep
  their stored looks untouched

Wiring: blobatar@0.2.0 (zero deps, ~3.7KB) exported through the plugin
SDK (blobatarSvg / Blobatar), feature-detected in plugin.js with a
legacy-shape fallback for older desktops. Blob shape strings are
'blobatar[:seed[:kind]]' inside the existing meta.shape field, so
persistence, cross-machine ui_meta sync, and the roster's PNG backfill
(data-bot-face tag preserved) all work unchanged.
2026-08-18 15:54:24 -07:00
Teknium b359db72ee feat(bot-mode): group chats accept image attachments every responding bot sees
Group rooms were text-only on the ingest side: the composer and
runGroupChatMemberTurn submitted prompt.submit {text} with no attach step,
so a user screenshot could never reach the members' models (community
report from Osiris). The gateway already ships the staging pipeline
(image.attach_bytes -> attached_images -> next prompt.submit) and the 1:1
canonical chat uses it — this wires the group surface to the same path.

- Composer + thread reply boxes: attach button, Ctrl/Cmd-V paste, pending
  chips with preview/remove, image-only sends allowed.
- Attachments are downscaled (long edge 1568px) and stored on the room-log
  entry, so reloads keep showing what members were shown.
- Turn drive stages the delta's images into EVERY responding member's own
  per-group session via image.attach_bytes before its prompt.submit —
  works cross-connection through requestForBot; a failed attach degrades
  that member to text-only rather than failing the turn. Watermarks
  guarantee an image is staged at most once per member.
- formatGroupChatLine names attachments ([attached image: name]) so the
  transcript delta and the staged pixels line up for every viewer.
- Room log renders attached images on user entries.

Tests: 6 new vm-harness tests (fan-out staging order, mention-scoped
attachment routing, image-only sends, no re-attach across turns,
transcript naming, invalid-attachment degradation); 276 total pass.
2026-08-18 15:05:15 -07:00
ethernet 1c4dc4cf5f fix(desktop): collapse the docked Bots tab with the sessions sidebar on narrow viewports
The Bots pane docks into the sessions zone but did not declare
collapsible. Below the sidebar-collapse breakpoint, the sessions pane
left the grid and the zone kept a stranded BOTS tab on screen.

The Bots pane now declares collapsible, so it leaves the grid with
its zone. The narrow edge overlay now mirrors the zone's tab strip
when the revealed pane has collapsed zone-mates. Without the strip,
only the first pane of the zone was reachable while collapsed. A
lone pane keeps the stripless overlay form.
2026-08-18 16:58:25 -04:00
Teknium 9005ad1ff7 test(desktop): pin selectable-text opt-in on group chat bodies
Follow-up to the root-cause fix: comment documenting why the opt-in
exists, plus a source-contract test proven to fail without the
attribute (sabotage run).
2026-08-18 12:37:38 -07:00
lazy-idler 56ddc036e4 fix(desktop): make group-chat message text selectable
Group-chat message bodies in the hermes-bots plugin render without the
data-selectable-text attribute, so they inherit the app-wide
body { user-select: none } and cannot be drag-selected or copied.
1:1 chat messages already carry the equivalent marker
(aui_assistant-message-content), so this aligns group chat with that
behavior by adding data-selectable-text="true" to the message body
wrapper.
2026-08-18 12:37:38 -07:00
HarishDarko 6d1e6113ac feat(desktop): add copy on Bot Mode group chat messages
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-18 12:37:38 -07:00
HarishDarko fac2b127ee test(desktop): require copy control on Bot Mode group chat lines
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-18 12:37:38 -07:00
Teknium ce751ff584 fix(desktop): resume cold-opened Bot Chats even before selection settles (#89206 class)
The salvaged gate only requested an explicit session.resume when the target
stored session was ALREADY selected — but the field failure (#89206) is the
cold open, where the persisted route points at the bot's session while
selection/runtime/transcript are all unsettled. The precondition skipped the
resume exactly when it was needed, and the hydration wait timed out into a
blank pane.

- sdk/index.ts: judge the main surface AFTER openSession() navigates, and
  request a sequenced resume whenever the surface is not healthy (selected +
  runtime bound + expected transcript present). Redundant requests are
  consumed as no-ops by the route-resume effect.
- hermes-bots plugin: widen the fix to the sibling open path — the profile
  session browser (openProfileSession) now opens with the same
  awaitHydration/expectHistory contract as canonical Bot Chats, so a stale
  main surface gets the same explicit resume instead of a silent blank pane.
- Regression test for the cold-open shape, proven failing against the
  pre-fix gate (sabotage run) and passing with it.
2026-08-18 12:22:44 -07:00
citizendev9c d758fdbce5 fix(desktop): make Bot Mode switches hydrate canonical chats 2026-08-18 12:22:44 -07:00
Teknium 4349cbbb23 feat: group chats get an editable name and room picture, at creation and after
Bot Mode group chats were named once at creation and could never be
renamed, and rooms had no picture — only the fanned member faces.

- New Group Chat dialog: optional room picture (upload from device or
  image.generate, same 256px normalize pipeline as bot avatars).
- Room header: gear button opens Group settings — rename the group or
  set/replace/remove the picture after creation.
- renameGroupChat re-keys the room record (log, watermarks, sessions,
  members, picture), swaps the name in every local member's ui_meta
  groups list, follows open views to the new name, and rejects
  collisions instead of silently suffixing. Stored member sessions keep
  resuming by sid, so no history is lost.
- Room picture persists in the durable room record, hydrates on window
  load, and renders in the roster row (over the face pile), and the
  room header.
2026-08-18 11:41:25 -07:00
Teknium d8e2386912 feat: Capabilities view configures the selected profile on its own gateway
A profile belongs to one gateway, but the Capabilities surface (Skills /
Tools / MCP) always read and wrote through the window's active backend —
scoping to a remote-owned profile silently edited the wrong machine.

- hermes.ts: capability REST helpers accept a ProfileScope
  (string | {connectionId, profile}); ambient path now also carries the
  active registry connection tag (same contract as the cron helpers,
  #87882); profileScopeKey namespaces cache keys per connection.
- SkillsView: scope selector lists (profile, device) rows from the union
  agent roster on multi-connection desktops; new fixedConnection prop
  pins the whole view to a registered connection (plugin door), with a
  probe-able SkillsView.supportsFixedConnection flag.
- MCP tab: live reload.mcp RPC withheld for cross-backend scopes (it
  rides the active gateway socket and would reload the wrong machine).
- Bot Mode: remote-target drafts now get the live Capabilities tab
  pinned to the target machine via fixedConnection, feature-detected so
  older desktops keep the staged checklists.
- Config-record/hub-action stores accept scopes; cache keys fold in the
  connection id so two gateways' same-named profiles never share rows.
2026-08-18 11:31:43 -07:00
Teknium daca386967 fix(desktop): @-mentions now autocomplete in Bot Mode group composers (#89049)
The group room's composer (new-thread box and reply-in-thread box) rendered
a plain SDK Input. The plugin's mention provider is registered against
COMPOSER_AREAS.atCompletions, which only mounts in the main chat composer —
workspace tiles never see it, so typing @ or / in a group chat did nothing
despite the placeholder advertising "@name to direct, @everyone for all".

Fix: a member-scoped GroupMentionInput wrapper in the plugin itself —
caret-aware @-token detection, popover offering @everyone/@all plus each
seated member's handle (same botHandle the parser uses), keyboard nav
(Up/Down/Enter/Tab/Escape), and insertion that produces exactly the
"@handle " strings parseGroupChatMentions resolves. Both group composers
(GroupChatWorkspace main box + thread reply box) mount it; the per-bot
composer path is untouched.

Root cause traced by the reporter of #89049 — confirmed against source.
2026-08-18 03:00:29 -07:00
Teknium a1682376ca feat(profiles): rename any agent — the default profile gets a display name (#45624)
`hermes profile rename default <name>` (and the Desktop/dashboard rename
flows) now set a presentation-only `display_name` in profile.yaml instead
of erroring. The canonical id stays "default"; resolution, comparison,
and spawn paths are untouched. Named profiles keep real renames and their
display_name survives the move.

Surfaces: profile list/show/status, /profile (text only — data.profile
stays canonical), dashboard ProfilesPage, TUI-gateway profiles.list, and
Desktop (rail, switcher, Manage page, and the Bot Mode roster via a
displayName fallback so a renamed default shows its name, not "default").

Slimmer redo of the direction in PR #87760 by @yxssxn — thanks; see PR
body for what changed vs that approach.
2026-08-18 02:27:18 -07:00
Teknium 6abe6edee6 feat(bot-mode): real Slack-style threads in group rooms — main composer starts one, reply-in-thread continues it
Supersedes the display-only conversation folding from #89030 with actual
threads, per Teknium's direction: guessing topic boundaries from user-
message timing was wrong — tasks take many user turns.

- Every room entry carries a thread id. The main composer STARTS a new
  thread with the whole group ('New Thread'); each open thread has its own
  'Reply in thread' box that CONTINUES that work. Explicit intent, no
  heuristics.
- Member turns are thread-scoped end to end: the round-robin drive filters
  the room log to the triggering thread, watermarks key on
  thread::member, and responder resolution reads only that thread — so
  parallel topics never leak into each other's deltas or eat each other's
  watermarks.
- Stranded (timed-out) replies remember their thread and harvest back into
  it; pre-thread bare-number markers still parse.
- Pre-thread logs hydrate through assignLegacyThreads(): a user message
  after a 15-min lull starts a synthetic thread, follow-ups inside the
  window stay together — one-time conversion only, live sends always mint
  real ids.
- UI: threads ordered by last activity, newest open by default, older ones
  fold to summary rows (head text, reply count, last activity) with
  expand/collapse.

Tests: thread minting, explicit-thread continuation + delta scoping (other
thread's text never reaches the member prompt), legacy hydration split
behavior, thread UI source contracts. 258/258 plugin tests pass.
2026-08-18 02:25:41 -07:00
Teknium 8505559fa9 feat(bot-mode): group rooms fold older conversations into one-line summaries (#88925)
Every USER message starts a conversation (the same boundary the turn
engine's delta scoping keys on). The latest conversation renders in full;
older ones collapse to a one-line summary row — head text, reply count,
last-activity time — that expands/collapses on click. db's 'threads per
conversation' ask, delivered as timeline folding: no log-model change, no
new persistence, cross-machine sync untouched.

groupChatConversations() splits the log (leading member run from a trimmed
log forms a headless block); rendering extracts renderEntry() unchanged.

Tests: conversation splitting (headless block, heads, startIndex) + fold
source contracts. 256/256 plugin tests pass.
2026-08-18 01:54:29 -07:00
Teknium c381aef16f fix(bot-mode): fresh-room name uniquify counts every membership, not just the legacy scalar
With multi-group membership, a bot's meta carries groups[]; the create
dialog's taken-name scan must union all of them (botGroups) or a name only
present as a secondary membership could be reused and resurrect that room.
2026-08-18 00:26:31 -07:00
Goktug Vatandas 7af25a92b0 test(bot-mode): favor behavior over source assertions 2026-08-18 00:26:31 -07:00
Goktug Vatandas 10d6faade9 test(bot-mode): verify shared-member room isolation 2026-08-18 00:26:31 -07:00
Goktug Vatandas 9bea439189 feat(bot-mode): support multiple groups per bot 2026-08-18 00:26:31 -07:00
Christopher Strnad 4bb7e4913f fix(desktop): preserve cross-machine group routing 2026-08-18 00:03:16 -07:00
Teknium a943895f2e test(desktop): prove the install_id collapse (#88828) and boot-descriptor connectionId (salvage #88697) compose without re-appending the twin-address primary 2026-08-17 23:52:54 -07:00
Teknium cb337d39ba fix: Bot Mode cronjobs panel no longer shows two empty-state markers
The empty cronjobs list rendered both a calendar-icon placeholder blurb
("Cronjobs are recurring tasks this agent runs on a schedule.") and the
Create Cronjob button — two elements saying the same thing (empty).
Per Teknium's review, drop the generic placeholder and keep only the
create button. The filter hint ("jobs exist but are hidden by the bot
filter") still renders, since it carries real information rather than
just marking emptiness.
2026-08-17 23:52:45 -07:00
Teknium 30fcf95805 fix(bot-mode): group chats no longer lose long-running member turns, remade groups start fresh (Discord bot-mode feedback)
From db's bot-mode feedback thread (Discord support, Aug 17):

- A 7-minute member run timed out at the fixed 3-minute turn deadline, read
  as a (pass), and its finished result never reached the room. The turn
  deadline now extends while the session visibly reports inflight/running
  work (bounded by a 20-minute hard cap), and a turn that still times out
  records a runtime 'stranded' baseline so the finished reply is harvested
  into the room at the next turn boundary — late, never lost. Stranded
  markers persist with the room so a reply that finishes after a window
  reload is still delivered.
- Re-creating a group under a taken name (easy: the default name is just
  the member names) silently reopened the old room with its full log.
  Create now uniquifies against live rooms and every bot's current
  grouping, so a new group is always a fresh room.
- The generic 'The room is working…' line now names the member on turn
  ('Radar is thinking…') so slow models read as thinking, not stuck.
- Room prompt no longer caps every reply at 1-3 sentences: chatter stays
  short, but results/answers/substantive work are explicitly full quality
  and length (outputs felt 'not as strong as usual').
- Room markdown gets list/pre styling (padded bullets instead of browser
  defaults flush against the pane edge).

Tests: group-chat.test.mjs +7 (harvest posts late replies and consumes
markers, late (pass) consumes without posting, stranded persistence,
deadline-extension + turn-label + fresh-name source contracts, prompt
quality rule). 245/245 plugin tests pass.
2026-08-17 21:26:10 -07:00
Teknium 47eadccdc4 fix(desktop): SESSIONS | BOTS is always a tab strip — the Bots pane re-homes beside Sessions on every boot, no heal guards
The one-time dock heal from #88788 ('sessions-tab-v1') burned its token even
when its guards skipped the move, and exempted $userPlacedPanes — so exactly
the users who had fought the old stacked layout by dragging panes stayed
stacked forever (empirically reproduced on a live desktop: burned-token and
user-placed installs both keep the Bots pane split below Sessions on current
main). Replace the heal with an enforced dock invariant (dock.enforce: true):
the Bots pane re-homes into the sessions zone's tab strip at every boot's
first adoption pass, idempotent per boot so an intra-session drag sticks
until the next launch. The retired paneDockHeals.v1 ledger is dropped on
store load.
2026-08-17 20:45:42 -07:00
hermes-seaeye[bot] 41a80d5251 fmt(js): npm run fix on merge (#88818)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-18 01:27:07 +00:00
hermes-seaeye[bot] a3995f8aed fmt(js): npm run fix on merge (#88813)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-18 01:10:42 +00:00
Teknium 133381508f feat(bot-mode): hide bots from the roster via right-click — header eye toggle reveals and unhides them
Right-click a bot row -> Hide Bot persists hidden:true through the
existing bot-meta pipeline (local ctx.storage + server profile.yaml
ui_meta via profiles.configure), so the hidden state follows the
profile across machines through mergeServerMeta.

- Hidden bots drop out of the roster list and the Active-now strip.
- A header eye toggle renders only while >=1 bot is hidden; while on,
  hidden rows render dimmed (opacity-60, eye-closed glyph) and their
  context menu offers Unhide Bot.
- Unhide writes hidden:false (never null): the server deletes None'd
  ui_meta keys while the local {...prev, ...patch} merge keeps null
  keys, and that asymmetry would let a stale truthy copy resurrect.
- Hiding the selected bot re-homes selection: first visible bot, then
  'default'; if default itself is hidden with nothing else visible the
  selection stays put so the Routines pane never chases a ghost.
- Hidden bots accumulate unread silently but never toast; the eye
  button badges when a hidden bot has unread.
- Display-only: @mentions, group chats, name-collision checks, and the
  meta/avatar/activity sweeps all keep the full roster. Known v1
  simplification: meta is keyed by bot NAME, so hiding a name hides
  every local row of that name (thin remote-source rows never read
  local meta and stay visible).
2026-08-17 18:03:44 -07:00
Teknium d89434e352 fix(desktop): Bots pane is a Sessions-zone tab again (not stacked below) and the Cronjobs pane only appears in Bots mode
- Bot Mode's pane dock changes to { pane: 'sessions', pos: 'center' }: the
  lone-pane header auto-hide trap the old 'bottom' split worked around is
  fixed (center gains pin the header shown), so the sidebar grows a
  SESSIONS | BOTS tab strip instead of two cramped stacked panes.
- One-time persisted-layout heal (dock heal tokens in the tree store):
  installs that adopted under the old split re-home into the sessions strip
  exactly once; user-placed panes are never touched and the burned token
  never re-fights a user who re-stacks afterward.
- The profile-scoped Cronjobs (routines) pane now registers only while the
  Bots pane is on screen, via the contribution disposer driven by the new
  host.paneVisibility SDK export (reactive $paneVisible). Feature-detected
  in plugin.js with the always-registered fallback for older desktops;
  the visibility listener tears down through ctx.onDispose.
2026-08-17 17:48:39 -07:00
Teknium 4014eb7d5c fix(bot-mode): sweep CLI-born Bot Mode sessions out of the global sidebar (ownership-based)
The id-based hide sweep (66221397a) only reconciled session ids the plugin
KNOWS — canonical Bot Chats from $botMeta and group rooms' member sids. But
Bot Mode sessions are also minted OUTSIDE the plugin: bot-to-bot handoffs run
`hermes -p <bot> chat -c "Agent Inbox" / -c "Bot Chat" --create-if-missing`
via CLI, and the mention-handoff path can mint a fresh "Bot Chat" beyond the
canonical one. Those ids never enter plugin state, so the sweep never touched
them and they sat visible in the global Sessions sidebar forever (user
report: five "Bot Chat" + two "Agent Inbox" leaked rows).

Add sweepBotProfileSessions(), chained from hideOwnedBotSessions() at the
same two call sites (plugin load + gateway reconnect): enumerate each roster
bot's OWN profile via session.list (visible rows only — naturally idempotent)
and session.set_hidden any row titled exactly 'Bot Chat' or 'Agent Inbox',
or prefixed 'Group: ' (the member-session title ensureGroupChatSession has
always used). Exact-title matching means a user's real conversations inside
a bot profile are never hidden, and non-bot profiles are never listed at all.
Remote-source bots route via requestForBot to their own connection. All
feature-detected and fire-and-forget per row.

The kickoff instruction text is left unchanged: the hermes CLI has no
hidden flag on `chat -c` session creation, so the sweep is the cover for
CLI-born rows.
2026-08-17 17:48:31 -07:00
Teknium 9aa1413781 feat(desktop): extend kanban native notifications to blocker/failure events
Builds on @nductien's completion-notify module (PR #87705):

- Notify on the gateway watcher's full terminal set — blocked, gave_up,
  crashed, timed_out, block_loop_detected — not just completed. A worker
  hitting a blocker while the user is away was the original community ask.
- Route all notification copy through the kanban plugin i18n bundles
  (en/ja/zh/zh-hant), with an English-bundle fallback when the translator
  isn't bound yet.
- Wire the ctx.os.notify door so events also fire a NATIVE OS notification
  while the user is away from the Hermes window (host.notify toast covers
  the foreground). OS-door failures are isolated from the toast path.
- Docs: Desktop notifications section in kanban.md, including the
  app-running coverage window.
2026-08-17 17:00:00 -07:00
nductien 1802620847 chore(desktop): remove local markers from kanban notify comments 2026-08-17 17:00:00 -07:00
nductien a30636f817 feat(desktop): surface native OS notification on Kanban task completion
When a background agent completes a Kanban task, the Desktop app now surfaces a native OS notification with the task summary and a one-click "Open Kanban" action.

Rides the existing /events WebSocket (onEventsFrame in api.ts). A
new module completion-notify.ts implements cursor-based per-board
deduplication with baseline from GET /board, fail-closed on unknown
baseline, and reconnection safety. The notification uses the existing
host.notify() and host.navigate() SDK APIs.

Files changed:
- apps/desktop/src/plugins/kanban/api.ts (+8, -1) — wire completion-notify
- apps/desktop/src/plugins/kanban/completion-notify.ts (+89, new)
- apps/desktop/src/plugins/kanban/completion-notify.test.ts (+378, new)

19 targeted tests pass (0 fail).
2026-08-17 17:00:00 -07:00
Teknium ae5c4c1672 feat(desktop): Discord-style group-chat rows in Bot Mode, opening in the main chat window
Group chats in the Hermes Bots roster were rendered as a section header
("HERMES, PAPERCLIP MANAGER" + divider + tiny "Open chat" link) with each
member bot listed underneath — reading as separate DMs, not a group — and
opening the room rendered it INSIDE the narrow bots side panel.

Roster (plugin.js):
- Each group chat is now ONE standalone roster row (GroupRow), Discord-style:
  stacked member avatars (BotFace composite, org glyph when empty), group
  name, member count, latest room line as the preview with markdown syntax
  flattened, relative last-activity time, and the "needs you" badge on the
  row itself.
- The roster is a flat list: bot rows and group rows interleaved in the SAME
  pin+recency ordering (a group's recency = its newest room-log entry).
  Bots keep their individual DM rows; the group is an additional
  independent row. The groupRoster() sectioning is removed.
- New stripPreviewMarkdown() flattens **bold**, `code`, > quotes, links,
  and headings out of BotRow and GroupRow previews (raw ** and > were
  leaking into row previews).

Main-window takeover (sdk/index.ts + plugin.js):
- New generic SDK door host.openWorkspace(id, { render, title, minWidth,
  onClose }): registers a placement:'main' pane docked center into the
  workspace zone (the same shape session tiles and previews use), wires
  registerPaneCloser so tab Close / ⌘W tears the registration down, and
  reveals it. Re-opening the same id refreshes in place and re-fronts.
  Returns a disposer.
- Clicking a group row calls openGroupChat(): on desktops with
  host.openWorkspace the room opens as a tab taking over the MAIN chat
  area; older desktops keep the exact in-panel GroupChatWorkspace fallback
  (feature-detected, established Bot Mode pattern). Disband closes the
  main-window tab too; the composer/round-robin logic is untouched and
  renders identically in both hosts.

Tests: roster-groups rewritten for the flat model + new helpers;
group-chat/create-group-chat source contracts updated to the
openGroupChat door; profile-prewarm harness stubs stripPreviewMarkdown.
2026-08-17 16:39:59 -07:00
Teknium 76ff060748 fix(desktop): clean up Plugins settings — drop Example Plugin, hide bundled built-ins, add profile scoping
- Remove the bundled Example Plugin from the desktop renderer plugins
  (apps/desktop/src/plugins/example/). Reference/demo plugins live in the
  companion hermes-example-plugins repo (already pointed to by
  src/plugins/README.md); shipping the counter demo in everyone's Settings
  doubled UI noise for no user value.
- Agent plugins section now hides ALL repo-bundled built-ins
  (source === 'bundled': browser/browserbase, cron_providers/chronos,
  model-providers/deepinfra, platform adapters, image/video backends, …).
  The section is the control panel for plugins the user installed
  (user/git/project/pip/portable); built-ins ship enabled-by-default and
  are configured from their own surfaces. The HIDDEN_KEY_PREFIXES list
  stays as a fallback for older backends. Count pill reflects the
  filtered list.
- Add an "Applies to" profile scope selector to the Agent plugins section
  (same pattern as the Capabilities scope selector): list/toggle any
  profile's plugins without switching the whole app. Backend:
  plugins.manage now accepts an optional `profile` param via the same
  set_hermes_home_override contract as cron.manage / mcp.servers.*;
  unscoped calls are unchanged, so older backends keep working.
- i18n: new settings.plugins.agent.appliesTo key (types + en + zh; other
  locales fall back through defineLocale).
- Tests: plugins-settings.test.tsx covers bundled hiding, prefix fallback,
  count pill, selector visibility, scoped list/toggle payloads; new
  tests/test_plugins_manage_profile_scope.py mirrors the cron.manage
  profile-scope tests (scoped read, unknown-profile 4064, no override
  leak, unscoped contract unchanged).
2026-08-17 16:39:47 -07:00
Teknium 91878fb3c1 fix(desktop): visible eye catchlights on dark-bodied Bot Mode avatars
The BotFace catchlight dots were hardcoded white. On dark bodies
(maroon/ink/oxblood) isDarkColor() flips the pupils to light cream
(eyeFill), so a white catchlight on a cream pupil is invisible — those
avatars looked like they had no dots in their eyes (image14 report).

Catchlight contrast now follows the pupil, not the body: dark pupils
keep the white sparkle, light pupils get a dark one (rgba(0,0,0,0.6)).
The animation clock only moves the hb-hl elements (cx/cy) and never
touches fill, so the initial-render fill carries through every frame.

Verified with a rendered before/after/control strip: BEFORE maroon has
no visible dots, AFTER shows clear dark dots in the cream eyes, light
control body unchanged with white catchlights. Plugin suite green
incl. new face-catchlight.test.mjs.
2026-08-17 16:35:12 -07:00
BkashJEE 6c84c33546 fix(bot-mode): explain hidden cronjobs in the Routines pane empty state
Partial salvage of PR #88561 (routines-filter-hint half only; the pet
gallery pagination half is out of scope for #88263 and was skipped).

When the cron store has jobs but none surface for the active bot (older
gateway without profile scoping, no [bot:<name>] tags matching), the
Routines pane now explains that cronjobs exist but are hidden, instead
of showing the generic empty state — so users don't believe their
scheduled jobs disappeared.

Port of NousResearch/Hermes-Bot-Mode#95 (repo archived, ported upstream).
2026-08-17 16:34:02 -07:00
ScaleLeanChris 799d3fe021 fix(desktop): show legacy cronjobs in default bot
Signed-off-by: ScaleLeanChris <chris@scalelean.com>
2026-08-17 16:34:02 -07:00
Andrew Walker e3c4bfe9ab fix(bot-mode): show profile-owned cronjobs 2026-08-17 16:34:02 -07:00
Teknium 399fdeae8b fix(bot-mode): plugin listener leaks, jsx key prop, OAuth poll overwrite
Three bugs in apps/desktop/src/plugins/hermes-bots/plugin.js:

1. register() subscribed host.state.profile / host.state.gateway listeners
   without capturing the unbind functions, so a plugin disable -> re-enable
   cycle stacked a duplicate listener per cycle that kept firing until app
   quit (same survives-disable class as the face clock before its
   onDispose hook). The unbinds are now captured and released via
   ctx.onDispose.

2. CreateRoutineDialog passed `key: createTarget` INSIDE the jsx() props
   object. The react/jsx-runtime silently ignores a `key` prop there (key
   is the third jsx() argument), so switching the routine owner never
   remounted the dialog and it kept stale per-bot form state. Moved the
   key to the third argument; the source-shape test now pins the correct
   shape and rejects the prop form.

3. beginOAuth() overwrote pollRef.current without clearing an existing
   interval, so a retry / double-click while a poll was live orphaned a
   2s poller that ran until unmount and could flip the phase from a stale
   OAuth session. It now clears any live poll before starting a new one.
2026-08-17 16:33:42 -07:00
Teknium aa2622128f fix(hermes-bots): show profile pics in group chat rooms
Community report: group chats showed neither profile pictures nor proper
names for members (names fixed in #88721; this adds the avatars).

- Every bot message in a group room now carries the speaker's avatar,
  resolved through the same botAppearance pipeline as the roster: custom
  uploaded/generated/pet images honored, backfilled PNGs dropped so the
  animated math face renders, deterministic shape+color for bots with no
  customization (including remote speakers with no local meta).
- The room header shows the member roster as overlapping faces (capped
  at 6) with a display-name tooltip, alongside the existing count.
2026-08-17 16:29:45 -07:00
Teknium 4b7eb02907 fix(hermes-bots): group chats never surface @default — Hermes keeps its name
Community report: in a Bot Mode group chat with the primary agent, Hermes
lost its name and rendered as @default in the room.

- Workspace speaker labels now render the roster displayName (default →
  Hermes, titles respected) instead of the raw @profile id. Clicking a
  speaker reveals the full disambiguated form — Display-gatewayname
  (@handle) — so same-named agents on two connections stay tellable
  apart on demand; naturally they just show their display name.
- Room transcripts fed to members render the default profile as Hermes
  (new groupSpeakerLabel helper).
- The per-turn prompt addresses members by @handle (@hermes for the
  primary profile) instead of @default, and parseGroupChatMentions now
  resolves @hermes back to the default member via botHandle.
2026-08-17 16:14:53 -07:00