Commit Graph

745 Commits

Author SHA1 Message Date
Teknium 77fcc2ea31 feat(display): honor display.timestamps across desktop transcript and TUI
One config key everywhere (#41531): the same display.timestamps that stamps
[HH:MM] on classic-CLI labels now gates the desktop transcript's timeline
timestamps and renders dim [HH:MM] labels on TUI user/assistant rows.

- desktop: $displayTimestamps store fed from config.yaml via
  use-hermes-config; TimelineTimestamp renders nothing while the key is off
  (the default). Hover tooltips with the exact time stay ungated (#70450).
- TUI: tui_gateway forwards each persisted row's timestamp in the display
  projection; toTranscriptMessages threads it as Msg.createdAt; live rows
  are stamped at append (the #82840 rule); MessageLine shows a dim [HH:MM]
  above user/assistant rows when display.timestamps is on.
- No new config keys, no HERMES_* env vars; display-only, prompt-cache safe.
2026-08-15 01:04:19 -07:00
Nick Morales 5f2a15a669 fix(desktop): separate system timestamp text 2026-08-15 01:04:19 -07:00
Nick Morales a896322b46 feat(desktop): show detailed transcript timestamps 2026-08-15 01:04:19 -07:00
Teknium 1c9de87ffb fix: drop duplicate composingRef declaration from rebase weave 2026-08-15 01:03:57 -07:00
xrwang8 47970a9e4c fix(desktop): clear edit-composer submitting latch after 200ms cooldown
- Add submitting state + IME composition guard to prevent double-submit
- submitEdit() sets latch, then clears after 200ms timeout
- handleKeyDown() guards on composing so IME Enter doesn't submit
- Shift+Enter inserts newline without submitting
- Test validates Enter calls onEdit, latch clears for second session

Fixes #70771

Signed-off-by: xrwang8 <xrwang8@gmail.com>
2026-08-15 01:03:57 -07:00
mollusk 652ae4877a fix(desktop): let the edit composer scroll long prompts
The inline edit composer caps height at max-h-48 but had no overflow
rule, so long prompts were clipped with no way to reach the tail.
Add overflow-y-auto to match the main composer editor.
2026-08-15 01:03:57 -07:00
hermes-seaeye[bot] af585de28e fmt(js): npm run fix on merge (#86801)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-15 07:46:57 +00:00
chillerno1 c6ec8a95e6 test(desktop): cover docked zone chevron direction
(cherry picked from commit 95358428f95175ef57376da334b69279618cce6a)
2026-08-15 00:34:57 -07:00
chillerno1 ac0d0f8897 fix(desktop): flip zone collapse chevron to action direction
Collapsed tool zones (terminal/logs) kept a down chevron after minimize,
so the restore affordance looked like another collapse. Point the icon in
the action direction — down when expanded, up when collapsed — matching
master-detail collapsible detail headers. Same fix for floating panes.

(cherry picked from commit 3392aeb9dba0ed9e9cabfb96b6010e46fa453ca1)
2026-08-15 00:34:57 -07:00
Isak du Plessis 10bf145e30 feat(desktop): collapse thinking by default
(cherry picked from commit 14fd1aace2c76c31f3b48e4b9bf9943c74582d21)
2026-08-15 00:34:57 -07:00
Teknium 587788405a fix(desktop): extend stale-flag self-heal and keyCode 229 guard to the edit composer
Widen the composer-side IME fixes to the inline edit composer: the same
missed-compositionend wedge and post-compositionend keyCode 229 Enter
apply to its handleKeyDown path.
2026-08-15 00:33:40 -07:00
izumi0uu 3d51c40997 fix(desktop): prevent IME submit in inline edit composer 2026-08-15 00:33:40 -07:00
Takumi Sato 20beee774c fix(desktop): prevent Enter from submitting during IME composition
Check event.nativeEvent.isComposing in Enter-to-submit branches so CJK (Japanese, Chinese, Korean) and other compositional input methods commit the candidate instead of firing the send handler.

Applied to all five sites in the desktop renderer that currently handle Enter with no composition guard:

- chat composer main submit and trigger popover (apps/desktop/src/app/chat/composer/index.tsx)
- message edit composer submit and trigger popover (apps/desktop/src/components/assistant-ui/thread.tsx)
- onboarding API key and auth code inputs (apps/desktop/src/components/desktop-onboarding-overlay.tsx)
- session rename input (apps/desktop/src/app/chat/sidebar/session-actions-menu.tsx)

Fixes #37483
2026-08-15 00:33:40 -07:00
Gorde Minchel e6708af1f2 feat(desktop): confirm before deleting a session
Deleting a session in the desktop app fired instantly on click — the CLI
path (hermes sessions delete) asks y/N by default, so one misclick (Archive
and Delete sit right next to each other) permanently destroyed a conversation
with no dialog and no undo (#61470).

Route every delete entry point (sidebar rows, tab menus, the chat header,
context menus — all share useSessionActions) through a shared
DeleteSessionDialog built on ConfirmDialog. ConfirmDialog gains an
onOpenAutoFocus prop so dialogs with no input keep focus off the close
button (a11y).

Tests: menu delete now asks; cancel keeps the session; Enter confirms;
Escape cancels; delete item disabled without onDelete; the same guard
applies via SessionContextMenu.
2026-08-15 00:33:32 -07:00
Teknium aa8e92f516 test: pin document.hasFocus in status timer test (useViewedInterval gating) 2026-08-15 00:32:53 -07:00
AlexDev_ bee0d45ddf perf(desktop): pause background UI work while unfocused 2026-08-15 00:32:53 -07:00
Teknium 4f29374662 fix(gateway): send-once spritesheet semantics for pet.info (#54730)
pet.info accepts knownRevision; when it matches the active sheet's
revision the multi-MB spritesheetBase64 is elided and
spritesheetUnchanged=true is returned. The desktop floating pet passes
the revision it already holds and keeps its cached bytes, so backstop
refreshes no longer resend ~3.2MB frames over the WS (write-loop stalls,
disconnect storms). Legacy callers omitting knownRevision get the full
payload unchanged.
2026-08-15 00:32:44 -07:00
Adolanium 5eb1d2b0aa fix(desktop): restore pet.info backstop after live-sync regression
Event-capable backends no longer polled pet.info, so a cold-start
fail-open enabled:false left the mascot hidden until Settings re-seeded
the store. Keep a slow backstop and short startup retries.
2026-08-15 00:32:44 -07:00
fanfan343 d474ba5615 test(desktop): cover bicubic smoothing in pet sprite 2026-08-15 00:32:44 -07:00
fanfan343 fd0872214b fix(desktop): smooth-scale pet sprite frames
Petdex spritesheets are 192x208px illustration frames, not pixel art.
The desktop canvas drew them with imageSmoothingEnabled=false
(nearest-neighbour), so zoomed pets looked blocky. Enable bicubic
smoothing so scaled frames stay clean at any zoom.

High-DPI backing-store sizing is addressed separately in #83276.
2026-08-15 00:32:44 -07:00
NorethSea 34e05c32ec fix(desktop): render pet sprite at devicePixelRatio for sharp HiDPI output
DPR-sized canvas backing store separated from CSS footprint, tracking
zoom/display changes live. Rendering-fix subset of PR #75307; the
overlay-placement feature portion is out of scope here.

Covers the devicePixelRatio half of #83216.
2026-08-15 00:32:44 -07:00
hermes-seaeye[bot] 0807673e1f fmt(js): npm run fix on merge (#86751)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-15 06:22:02 +00:00
Hermes Agent 7d74375b0b fix(desktop): keep session-list scrollbar clickable beside pane sash
The pane-resize sash's 9px grab band was centered on the split boundary,
so ~4.5px reached into the leading pane and sat exactly on top of the
session list's 4px scrollbar — the pointer always hit the sash (cursor
flipped to col-resize) and the scrollbar thumb was unclickable/undraggable.

Make the grab band asymmetric: 1px into the leading pane, 7px into the
trailing one. The scrollbar regains its full hit area while the sash stays
an easy 8px target; the hairline and hover strip are repositioned onto the
actual boundary.

Fixes #79157
2026-08-14 20:40:15 -07:00
Nicky Molina 6f64a2c631 fix(desktop): reanchor transcript on window focus 2026-08-14 20:40:15 -07:00
hermes-seaeye[bot] 01512ca000 fmt(js): npm run fix on merge (#86631)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-15 03:31:07 +00:00
Sora-bluesky 89fa829802 test(desktop): pin the tail-only contract for the loading and stall indicators
Regression coverage for #68634. The indicator family mounts only on the
thread's any-role tail (ba756333): a running bubble that is not the tail
stays silent, even when only a user or system row trails it, and the
optimistic-placeholder flow renders exactly one status row, the
placeholder's own.

Mutation-checked: relaxing the mount gate to a last-assistant walk fails
the two silence cases, and removing the gate fails all five.
2026-08-14 20:24:42 -07:00
Pink 6fcc202687 feat(desktop): show collapsed todo running indicator 2026-08-14 20:24:42 -07:00
Teknium 73bcfddb3d fix(desktop): replay pending clarify prompts after reconnect
Clarify prompts share the same emitted-while-detached failure class the
pending-approval replay fixed: `clarify.request` rides `_block()`'s pending
registry, so a client whose transport was down when the event fired never
sees the question and the agent thread stays parked until timeout.

Widen the resume snapshot the same way:

- tui_gateway/server.py: `_live_session_payload` now carries
  `pending_clarify` — a read-only snapshot of the clarify prompt still
  blocking the session, scoped to the owning runtime sid. The registry stays
  authoritative; the embedded request_id resolves via clarify.respond.
- Desktop resume paths (`use-session-actions`) restore the parked clarify
  into the clarify store (multi_select preserved) and flag needsInput,
  mirroring restorePendingApproval on both the activate and resume paths.
- pending_approval replay now also forwards the queue-injected request_id so
  the restored prompt responds with exact-request correlation.
- Tests: server-side replay + scoping test; harmonized the #82087 replay
  test with the request_id `_ApprovalEntry` now injects.
2026-08-14 20:24:32 -07:00
Thomas Hudspith-Tatham 1134d2c998 fix(desktop): stage keyboard multi-select choices 2026-08-14 20:24:32 -07:00
Thomas Hudspith-Tatham 6cbe5a35b6 fix(desktop): support multi-select clarifications 2026-08-14 20:24:32 -07:00
VooDoo Pixels f703e70618 fix: make desktop approval routing reliable
Correlate approval requests, reject stale responses, replay pending approvals after reconnect or session resume, and preserve fail-closed timeout behavior.
2026-08-14 20:24:32 -07:00
Teknium 1c80085f04 fix(desktop): widen terminal-status handling to sibling sites
Two sibling sites still treated only 'completed'/'failed' as terminal:

- delegate-model.ts settled result rows as 'completed' for ANY status other
  than 'failed', so a delegate result row with status 'timeout' or 'error'
  (the statuses tools/delegate_tool.py actually emits on child timeout or
  crash) rendered behind a green check. Settled rows now map ok/completed
  to completed and everything else to failed.
- subagents.ts asStatus accepted a literal 'queued' payload status even on
  a subagent.complete event, leaving the row active forever. The fail-closed
  branch now runs before the queued fallback, so completion events always
  settle.
2026-08-14 20:23:57 -07:00
Aleks Clark 62eefff697 perf(desktop): bound long-running app resource use
Persist backend ownership for reliable cleanup, park inactive panes, and
evict unreferenced transcripts so Desktop stays responsive over long sessions.

💘 Generated with Crush

Assisted-by: Crush:gpt-5.6
2026-08-14 20:23:07 -07:00
nanami7777777 957a7c20cd fix(desktop): keep text navigation keys in composer 2026-08-14 20:22:56 -07:00
Brooklyn Nicholson cd104ed49e fix(desktop): let you type spaces and arrows in the session rename dialog
Renaming a session from its row menu opened the rename dialog, but the
menu's close restored focus to its trigger — the session row's own
<button> — instead of leaving it in the dialog input. Focus sat on the
row, so Space toggled the row (selecting/deselecting the session) and the
arrow keys moved the list rather than the text caret; you couldn't type a
space or move within the name.

Thread onCloseAutoFocus through the shared ActionsMenu / ActionsContextMenu
primitives and suppress that one focus-restore when the rename item is the
action that closed the menu, so the dialog input keeps focus. Every other
action leaves the restore untouched. Mirrors the project menu's existing
appearance-popover guard.
2026-08-14 15:40:23 -05:00
Teknium 17d6a7d426 fix(desktop): avatar misses expire after 30s instead of caching forever (#85908)
resolveAgentAvatar cached null permanently (window lifetime), so a bot
whose avatar reached the asset store moments after its first notice
rendered — freshly created bots, art backfills in flight — kept the 🤖
glyph until an app restart even though profiles.get_asset had the pfp
(user report: brand-new bots' notices never picked up their faces).
Hits stay cached for the window; misses re-probe after 30s. Same
dedupe/inflight behavior otherwise.
2026-08-14 00:42:02 -07:00
hermes-seaeye[bot] bee9ef375b fmt(js): npm run fix on merge (#85898)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-14 07:19:47 +00:00
Teknium 0842655382 feat(desktop): sender-side delivery notices — 'Messaged X' / 'Message from X' (#85888)
The sending bot's chat showed inter-agent deliveries as raw terminal
tool rows (the hermes -p … chat … command + output transcript) —
plumbing, not conversation. When a terminal call matches the delivery
convention (-p <agent> chat … -q "Message from …", the shape from
#85855), it now renders as compact centered notices instead:
'Messaging <agent>…' while running, 'Messaged <agent>' on completion,
and — when the quiet run returns the recipient's reply — a 'Message
from <agent>' notice with the text behind a 'show message' expander.
Avatar resolution reuses the #85855 helper (now exported); glyph
fallback everywhere it can't resolve. Failed commands keep the real
terminal row (debuggable). Ordinary terminal calls untouched.

Sender + receiver now speak one visual language: the exchange is a
pair of timeline events on both ends (Grok-bots parity), with #85884
collapsing the receiving side's reply.

agent-delivery tests 6/6; thread suite 20 files / 117 tests green.
2026-08-14 00:10:07 -07:00
Teknium bd22451f0d feat(desktop): collapse inter-agent exchange replies (Grok-bots parity) (#85884)
The recipient's reply to an inter-agent delivery rendered as a full
assistant message, so the receiving bot's chat read like a normal
human conversation. The exchange is an EVENT in that bot's timeline,
not conversation content: when the immediately preceding user message
is an inter-agent delivery (AGENT_MESSAGE_RE, shipped in #85855), the
reply now renders as a compact centered 'Replied to <sender>' notice
with the full text behind a 'show reply' expander — mirroring the
delivery notice above it. Never collapses while streaming (progress
stays visible); ordinary assistant messages untouched.

Thread suite 19 files / 111 tests green.
2026-08-14 00:02:26 -07:00
hermes-seaeye[bot] efad7c9161 fmt(js): npm run fix on merge (#85867)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-14 06:33:35 +00:00
Teknium 7a9634568c feat(desktop): agent-to-agent messages render as attributed cards, not user bubbles (#85855)
* feat(desktop): render agent-to-agent messages as attributed cards, not user bubbles

Bot-to-bot deliveries arrive on the user role (alternation requires
it) but are not the human speaking — they rendered as if the user
typed them. Detect the delivery prefix ('Message from 🤖 <sender>: …',
emoji-less, and the legacy bracket form) and render an attributed
inter-agent card: left-aligned, robot + sender header, 'agent message'
label, body through the same minimal markdown pipeline. Anchored regex
cannot fire mid-prose. Same pattern as ProcessNotificationNote.
Presentational only; content/roles/caching untouched.

* reshape: inter-agent card -> Grok-style compact timeline notice

Per maintainer screenshot: the delivery renders as a subtle centered
'🤖 Message from <sender>' notice (ProcessNotificationNote's shape),
with the delivered text behind a 'show message' expander instead of a
full-width card. The recipient's reply remains a normal assistant
message below it.

* feat: sender avatar on the inter-agent notice

The delivery prefix may carry the sender's profile handle —
'Message from 🤖 <Display Name> (@<handle>): …'. The notice resolves
it through profiles.list (has_avatar) + profiles.get_asset and renders
the sender's actual avatar in place of the 🤖 glyph, with module-level
memoization (one resolution per sender per window) and inflight
de-dup. Glyph fallback covers handle-less prefixes, older gateways
without profiles.*, avatarless profiles, and failures. 'hermes'
resolves to the primary profile by convention. Tests 6/6.

* lint: sort the $gateway import (perfectionist/sort-imports)
2026-08-13 23:19:04 -07:00
Teknium 9504edbaea fix(desktop): agent mentions insert as plain @name, not an @simple: chip (#85841)
Picking a colon-less completion row (agent profile mentions from
complete.path, e.g. '@mr-tester') fell through serialize()'s typed-
reference branch: classify() marks colon-less entries type 'simple'
with insertId = text, so the serializer minted '@simple:`@mr-tester`'
— which rendered as a weird chip in the composer AND broke downstream
@mention routing (the backtick-quoted form no longer parses as a bare
mention). Colon-less rawText now inserts verbatim, matching what @diff
and @staged already did via the empty-insertId path.

Test: mention rows serialize to plain @name and commit to the editor
without an @simple: wrapper (directive-label 5/5).
2026-08-13 22:24:20 -07:00
unsupportedpastels ad8365d533 fix(desktop): preserve multi-pane plugins when closing panes
Closing a pane contributed by a plugin used to disable the entire plugin,
unloading every one of its contributions. For a plugin that owns several
independent panes (e.g. Bot Mode's Cronjobs pane alongside its Bots roster
and composer middleware), closing one pane silently killed the rest.

Now: closing one pane of a multi-pane plugin dismisses only that pane; the
plugin stays enabled and its other panes/commands/middleware keep working.
Reset layout restores dismissed contributed panes. A single-pane plugin
keeps the existing symmetric behavior (Close disables the plugin, with
Settings -> Plugins as the recovery path).

Adds regression coverage for both cases.
2026-08-13 22:18:42 -07:00
Brooklyn Nicholson fa92aa1984 fix(desktop): keep a markdown render failure inside its own message
MessageRenderBoundary re-throws anything that is not the transient
assistant-ui lookup race, by design, so a RangeError raised inside
Streamdown's render unwinds all the way to the workspace boundary and
replaces the entire app with "workspace failed to render". The message
is replayed from the session on every reload, so Retry lands on the same
content and fails the same way — the app is bricked, not glitching.

Wrap the markdown surface itself, so one bad message degrades to the
existing HugeTextFallback (readable, already used for oversized text)
while the rest of the transcript stays alive. The boundary sits on
MarkdownTextSurface rather than any single caller because the crash is a
property of the content, not of which part carries it: the same payload
arrives as an assistant answer, as reasoning, or in tool output, and all
of them render through here.

Tests drive the real component with both known overflow shapes and fail
with the reported RangeError when either half of the fix is reverted.
The depth clamp handles the raw-HTML cause; deeply nested block
structure recurses in mdast-to-hast where no HTML guard can reach it,
which is why the boundary is not redundant.

Co-authored-by: Gille <helix4u@users.noreply.github.com>
2026-08-13 13:36:54 -05:00
Teknium 94be919411 feat: rename Codex OAuth provider label to "ChatGPT or Codex Subscription"
Renames the openai-codex provider's display label across the CLI
(hermes model picker, provider labels), the dashboard OAuth accounts
catalog, and the Desktop onboarding + settings provider pickers.
Slug, aliases, and auth flows are unchanged.
2026-08-13 03:06:08 -07:00
Teknium ae56c97c60 feat(desktop): show full session title in a tooltip when it truncates
Hovering a sidebar session row (one-line or card) now shows the complete
title in a styled tooltip — but only when the title actually overflows
its label, so fully visible titles never grow a redundant tip.

New OverflowTip primitive in ui/tooltip.tsx: a controlled Tip that
measures scrollWidth vs clientWidth on pointerenter and arms a 600ms
deliberate-hover delay only when the content is truncated. Community
request via @fhreire on X.
2026-08-13 02:38:32 -07:00
hermes-seaeye[bot] 1ef0a366da fmt(js): npm run fix on merge (#85098)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-13 06:44:04 +00:00
Brooklyn Nicholson 91a30705eb refactor(desktop): generalize the composer suggestion pills into a provider bus
The pill strip from the inline-MCP work is worth more than one source, so
the MCP-specific store splits into two layers with the same UX contract
(session-scoped, capped, self-limiting, one-click with narrated
idle→working→done):

- store/composer-suggestions.ts — the bus. Draft providers register into
  the existing debounced sampler; event providers push/withdraw directly.
  Offerings merge (event before draft), dedupe by provider-namespaced key,
  and keep reference identity on no-ops.
- store/suggestion-providers/mcp.ts — the founding provider, behavior
  unchanged: directory keyword/host matching, configured-server exclusion,
  one-click connect with OAuth cancel + config rollback.
- composer/suggestion-pills.tsx — the generic strip; phases and cancel
  live here, action/rollback/toasts stay with the provider's invoke.

No new pills yet — this is the seam for them.
2026-08-13 01:22:35 -05:00
Brooklyn Nicholson fd6af8f832 feat(desktop): render the clarify (Recommended) label in tertiary text
The card reads the labelled choices off the gateway request rather than
the raw tool args -- the backend applies the label there, and the card
only mounts once the request exists, so the args are a hydration-race
fallback. RECOMMENDED_LABEL and bareChoice live in the clarify store so
the component and the choice-length guard share one definition; without
the guard a long option could be dropped for length the label added.
2026-08-13 01:19:42 -05:00
Brooklyn Nicholson 6ef0fc4f62 fix(desktop): cancel MCP OAuth flows server-side so a retry doesn't 409 2026-08-13 01:06:51 -05:00