Commit Graph

115 Commits

Author SHA1 Message Date
Teknium 6851841112 fix(bot-mode): group chat opens as one room pane, not two (#89788)
Opening a Bot Mode group chat painted the room twice — once as a main-window
workspace tab (host.openWorkspace) and once as the in-panel fallback, because
the Bots pane rendered off $groupChatWorkspace alone. Two live panes with
independent drafts drove one shared engine, and the roster disappeared behind
the duplicate.

The in-panel room is the fallback surface, not a second copy: it now renders
only while no main tab owns the group. The selection atom stays set either way
so the roster row still highlights, and desktops without the door — or whose
door throws — keep the in-pane room.

Consolidates #89881, #90274 and #90398, which fixed the same bug.

Closes #89788

Co-authored-by: helix4u <helix4u@users.noreply.github.com>
2026-08-19 23:12:25 -05:00
Owenz-creator 8408edcfb5 fix(bot-mode): protect ordinary sessions from hide sweep 2026-08-19 19:30:28 -07:00
Teknium d604ba6585 fix(bot-mode): running kanban/tool workers now light the Bots roster (#90268)
Worker sessions are deny-listed out of every conversation list, so a
profile grinding through a 30-minute kanban task read idle ('3 hr ago')
with no ACTIVE NOW entry the entire run.

- tui_gateway/methods_profiles.py: profiles.list rows gain worker_session
  — the newest kanban/tool row (id, source, title, last_active). Workers
  heartbeat last_activity_at every <=60s while running (#72016), so the
  field stays fresh exactly while work is happening. last_session keeps
  its deny-list contract; include_sessions:false omits the field; older
  clients ignore it.
- hermes-bots plugin: workerActiveAt() (150s window, one missed heartbeat
  of slack) feeds ACTIVE NOW, the row pulse dot ('Working on a task right
  now'), and the row age label while a worker runs. Chat semantics are
  untouched when no worker is live.
- Tests: 4 new pytest (real SessionDB on temp HERMES_HOME), 2 new node
  behavior tests; sabotage-verified.

Session-list visibility of workers (the issue's first half) is left as-is
by design — auto-resume and shared lists must keep excluding workers; the
roster signal was the actionable gap.
2026-08-19 19:30:12 -07:00
EndeavorYen 2367b90b9f fix(desktop): keep Sessions workspace when opening a Bot Chat
Bot Mode passed keepAllProfilesScope:false, which re-homed the sidebar
onto the bot profile. That profile forever-chat is hidden, so Sessions
and the roster looked empty. Opening a bot is navigation, not a workspace
switch. Also restore all-profiles when the bot backend is already live.

Related: #89789
2026-08-19 20:01:40 -05:00
chelsealong 6ec4aa8c3a fix(desktop): move the hydration-timeout retry into host.openSession
A review of the previous commit found that retrying at the plugin layer
(openStoredBotChat catching and re-calling host.openSession) didn't fix
the reported bug: host.openSession's own catch block unconditionally
calls setResumeExhaustedSessionId on a hydration timeout before
rethrowing, and only an explicit resumeSession() (the manual Retry
button) clears that latch for the currently-routed session. A
plugin-side retry is a different code path that can hydrate the
transcript fine while the full-screen "Couldn't load this session"
overlay stays latched over it.

host.openSession now takes a retryHydrationTimeoutOnce option and
retries the open+hydration-wait internally, before the latch is ever
set, so a successful retry never arms the overlay. openStoredBotChat
just opts in via that option.
2026-08-19 20:01:40 -05:00
Teknium 271e49a8ff fix(bot-mode): accept both host.connections() shapes in the Create-on picker normalize
The SDK now returns the registry rows per its documented contract
(salvaged #89893), while desktops predating the SDK unwrap resolve the
raw registry envelope. The plugin normalize accepts both, so the picker
works across the transition; regression test updated to pin the
dual-shape normalize.
2026-08-19 16:10:10 -07:00
Teknium 010c9925e3 fix(bot-mode): roster age, pulse, unread, and sort now see canonical Bot Chat activity
The canonical Bot Chat is hidden from session lists by design, so
profiles.list's last_session never advances when you message a bot there.
PR #88690 moved the roster PREVIEW to preferred_session but left every
activity signal on last_session — a bot you just messaged showed '6d ago',
never pulsed, never badged, and sorted below stale bots.

New botActivitySession(bot) helper returns the fresher of preferred_session
(the pinned Bot Chat, resolved precisely by the backend) and last_session
(newest visible conversation). All four activity sites key off it now:

- row age label (relativeTime)
- active-now pulse dot + activeBots strip
- unread watermark + activity toast preview
- roster recency sort (activityOf)

Older gateways without the preferred_session resolver degrade to
last_session exactly as before. Backend untouched.

Tests: extracted the real helper into the vm harnesses (no stub drift),
5 new behavior tests; sabotage-verified they fail against the old code.
2026-08-19 16:04:13 -07:00
Tom 7b25941b0e fix(bot-mode): unwrap connections registry object so 'Create on' picker renders
host.connections() resolves the IPC handler hermes:connections:list, which
returns the registry OBJECT ({version, primary, connections: [...]}) — not a
bare array. CreateAgentDialog did setConnections(Array.isArray(value) ? value
: []), so on a multi-connection desktop the picker gate
(Array.isArray(connections) && connections.length > 1) never fired and the
'Create on' picker stayed hidden, making cross-machine bot creation
impossible despite the multi-connection feature being documented.

Any new agent is still created on the active gateway (unchanged behaviour);
the picker is the only path that regressed. The built-in Connections UI
(refreshConnectionsRegistry) consumes the same registry object, so the IPC
handler contract is left untouched.

Adds a regression test asserting the unwrap and that the IPC contract is
preserved. Plugin suite: 309/309 pass.
2026-08-19 15:06:57 -07:00
David Dudok de Wit 0a36fa7fbd fix(desktop): keep remote bot identity visible 2026-08-18 22:03:25 -07:00
amanning3390 8e5f55f83b feat(hermes-bots): add a collapsible group activity view
Bot Mode group rooms only showed a single "is thinking…" line while bots
ran, and nothing after a turn settled or failed — no way to see what the
room did without reading the whole transcript.

- Runtime-only, bounded activity feed per room (GROUP_ACTIVITY_LIMIT),
  recording truthful turn events: queued, working, replied, passed,
  timed-out, failed, cancelled, settled, delivered.
- Every event is tagged with the room epoch it belongs to; the view shows
  only the CURRENT run, so a superseding send (or a rename that re-keys
  the room) can never surface stale activity.
- Quiet disclosure in the room header: collapsed by default, the collapsed
  row shows the latest event summary; expanding lists the current run's
  events newest-first with per-state glyphs and tones.
- Never persisted and never hydrated — the transcript stays the only
  durable record, so activity cannot be replayed as history.

Tests: 8 new behavioral + source-contract cases in
tests/group-activity.test.mjs (settled arc, failed turn, supersede/cancel,
epoch filtering, bounded feed, runtime-only guarantee, labels, disclosure
a11y contract).
2026-08-18 22:03:02 -07:00
pierrenode 589bee99cf fix(bot-mode): never resubmit into a still-running stranded group member
runGroupChatRounds' responder selection had no awareness of the stranded/
harvest state the previous round's harvest pass just confirmed. A member
whose turn timed out (marked stranded) but is STILL genuinely running
could be re-selected as a responder in a later round of the same
invocation — resolveGroupResponders has no busy filter, and a member's
watermark is bumped past the stranded timeout regardless of outcome, so a
fresh delta re-qualifies them.

Re-selecting them fires another prompt.submit into their live session.
tui_gateway's _handle_busy_submit treats that as a normal busy mid-turn
prompt: by default it either redirects the live turn in place or, for
older agents, hard-interrupts it and queues the new text as the next
turn. Either way the member's original in-flight work — exactly what the
stranded/harvest mechanism exists to protect — gets abandoned or killed,
undermining the "never lost, just late" guarantee.

Filter responders against the room's current stranded map (freshly
confirmed by this round's own harvest pass) before selecting who speaks.
A member with a live stranded marker is skipped; the next harvest pass
picks their reply up once it actually lands.

Added a regression test exercising runGroupChatRounds end-to-end with a
member confirmed still-busy: without the guard the round loop resubmits
into their session (asserted via prompt.submit call count); with the
guard it never does, and the marker survives untouched. Mutation-verified:
temporarily reverted the filter and confirmed the new test fails
(2 !== 0) fast, without a real wall-clock wait.

Full hermes-bots plugin suite: 246/246 pass (47 files). node --check
clean on both changed files.
2026-08-18 22:02:32 -07:00
Teknium d440891204 test(desktop): update canonical-chat-identity expectation for salvaged PR #89031 2026-08-18 21:52:41 -07:00
marketing2981 ed47d41dc9 fix(desktop): bot navigation no longer forces the all-profiles sidebar on
Every host.openSession call in the Bot Mode plugin omitted
keepAllProfilesScope, so the SDK applied its default and flipped
$showAllProfiles back on whenever the target session belonged to a
different profile than the live gateway (sdk/index.ts:
options.keepAllProfilesScope !== false => setShowAllProfiles(true)).

For anyone running more than one profile this silently undid the sidebar
profile filter: narrow Sessions to one profile, click any other bot, and
the unified all-profiles list came back.

Bot navigation is an explicit context switch into that bot's profile, so
pass keepAllProfilesScope: false at every openSession call site (4 on
current main after the plugin.js refactor consolidated the original 7).

Salvaged from PR #89031 onto current main; includes contributor mapping.
2026-08-18 21:52:41 -07:00
Teknium af3373f7c8 test: follow-up for salvaged PR #89020 — focused-highlight contract includes the !activeGroup guard 2026-08-18 21:52:11 -07:00
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