Commit Graph

22877 Commits

Author SHA1 Message Date
Nicolas Formenton
5cf6122e87 chore: map contributor email 2026-08-15 01:21:40 -07:00
Nicolas Formenton
c75d835555 feat(desktop): mark a session as unread/read with a persisted watermark 2026-08-15 01:21:40 -07:00
OpenClaw Agent
71e8c35285 fix(desktop): clear unread for the whole conversation family, don't re-light read sessions
The sidebar lights the finished-unread dot for every alias of a
conversation lineage (branch children + compression root), but reading
a session cleared only the exact row id — a branched/compressed
conversation kept dots lit on sibling rows no matter how often they
were opened. And any settled completion re-lit the dot even when the
user had already viewed the session since it finished.

- setSelectedStoredSessionId now clears unread for the whole family via
  lineageAliases, not just the selected id
- handleTransition only re-arms unread when the completion settles
  strictly after the user's last read of that session (new last-read
  baseline), so an already-viewed completion never re-lights
- openSession marks read at the very top, before any focus
  short-circuit, so re-clicking an already-visible session clears its
  dot (the original gap the sidebar click could not reach)

4635 desktop tests pass (incl. new family-clear, read-baseline, and
openSession-short-circuit cases); tsc typecheck clean.
2026-08-15 01:21:40 -07:00
gitong
1abeddacc1 fix(desktop): clear unread dot on tile open + add mark-as-read actions
The green 'finished-unread' dot only cleared when a session was opened via
main-thread resume (setSelectedStoredSessionId). Opening a session in a
tab/tile (middle-click / Cmd-click / tile strip) never cleared it, so the
dot stayed while the user was actively reading the session in a tile.

With a remote hermes serve backend the effect is amplified: session.info
transitions for every backend session (CLI/cron/kanban) mark unread in the
desktop client, so unread dots accumulate from sessions the user never
opened.

Changes:
- openSessionTile now calls markSessionRead(), so tile/tab open marks the
  session read (same as main-thread resume)
- new markSessionRead / markAllSessionsRead helpers in store/session,
  reused by setSelectedStoredSessionId
- 'Mark as read' per-row action in the session context menu (shown only
  while the row is unread)
- 'Mark all as read' header action in the recents sidebar (shown only
  when unread sessions exist)
- i18n: markRead (row scope), markAllRead (sidebar scope) in en/zh + types
2026-08-15 01:21:40 -07:00
Zeus-Deus
26b4315e09 fix(desktop): profile-scope persisted unread and stop cold-boot storage clobber
Address review on the persisted unread dots, plus a latent data-loss bug
in the shared persistence helper that the restart e2e exposed.

Review findings:

- Session ids are caller-supplied and each profile backend is its own
  namespace, while the desktop's lists routinely mix profiles (cron and
  messaging slices are always cross-profile; recents are too in
  all-profiles mode). Both persisted records are now bucketed per
  profile - nested records keyed by the ROW's own profile
  (normalizeProfileKey, absent -> default), never the live gateway's,
  except the live busy->idle edge with no loaded row, which can only
  come from the active gateway. Same-id sessions in different profiles
  no longer share watermarks or markers.
- Markers are now bounded (200 per profile, oldest evicted) and cleaned
  up when a session leaves the user's world: forgetSessionUnread() is
  wired into removeSession, archiveSession, and the settings
  permanent-delete path (which bypasses the other two).

Cold-boot clobber (found by the restart e2e after the refactor):

- persistentAtom wrote its value back to storage immediately at
  creation. On a cold boot the bundle can evaluate against a storage
  snapshot that has not caught up yet, so that echo overwrote real
  records with the fallback. Creation is now read-only; only actual
  changes persist. Regression-tested in persisted.test.ts.
- The read side of the same race is handled in session-unread.ts: the
  first list arrival re-reads both records from storage (readable by
  then) and merges them under the in-memory state, so a boot that
  seeded empty atoms adopts the disk state instead of re-seeding every
  row and burying the unread gap. Unread listeners are also disabled in
  secondary windows - their partial list view must not write the
  primary's whole-record state (same isolation rule as session tiles).

Tests: cross-profile same-id regression, live-edge profile fallback,
forgetSessionUnread cleanup, marker cap, persistentAtom creation
read-only; the restart e2e passes again end to end.
2026-08-15 01:21:40 -07:00
Zeus-Deus
d2c2b0b6d4 fix(desktop): persist sidebar unread dots across app restarts
The green "finished — unread" session dot lived only in the transient
$unreadFinishedSessionIds atom, written by a live busy->idle edge the
renderer had to witness. Closing and reopening the app grayed out every
dot, and a session that finished while the app was closed could never
be flagged at all.

Add a persisted layer (session-unread.ts), ported from the webui's
proven design:

- Seen watermarks (hermes.desktop.sessionSeenCounts): the message_count
  last acknowledged per session, keyed by the durable lineage id (same
  rule as session colors). A row whose live count exceeds its watermark
  paints unread on every list refresh - this reconstructs dots after a
  restart AND surfaces sessions that finished while the app was closed.
  First sight of an unknown session seeds the watermark so a fresh
  install doesn't light up every row.
- Explicit finish markers (hermes.desktop.unreadFinishedSessions): the
  live edge, persisted, covering the gap before the sidebar list
  refreshes its counts.

Opening a session acks both; the selected session's watermark tracks
its live count so on-screen activity never reads as unread. Chat and
cron rows get full watermark treatment; messaging rows keep explicit
markers only, so inbound messages don't paint false completion dots.
Profile switches keep persisted markers (keyed by durable id) and only
wipe the transient paint layer, so a round-trip repaints them.

Covered by store unit tests and an e2e spec that boots the app three
times: dot appears on a background finish, survives a restart, clears
on open, and stays cleared after another restart.
2026-08-15 01:21:40 -07:00
Jefftree
a5b50437e4 fix(desktop): key the completed-unread dot on the focused session, not the selected one 2026-08-15 01:21:40 -07:00
hermes-seaeye[bot]
77be513de1 fmt(js): npm run fix on merge (#86813)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-15 08:13:01 +00:00
Teknium
fbe4d73051 test: type the WebSocket mock to satisfy docker-build tsc strictness 2026-08-15 01:05:39 -07:00
Teknium
180bd4b5a3 chore: map salvage contributor emails for attribution audit 2026-08-15 01:05:39 -07:00
Moisés Valero
fef9c537d7 fix(cli): convert Alt key shortcuts to sequence tuple for prompt_toolkit (#74169) 2026-08-15 01:05:39 -07:00
John Lussier
b3447c2129 test: align submit bindings with multiline default 2026-08-15 01:05:39 -07:00
John Lussier
2ae7884ffa fix: make CLI multiline shortcuts work by default 2026-08-15 01:05:39 -07:00
null-runner
24af6685b0 fix(tui): word-delete forward and transactional cut
The dashboard maps Ctrl+Delete to ESC d for delete-word-forward, but the
composer had no binding for it: hermes-ink decodes ESC d as meta+'d', which
fell through to the printable path and typed a literal "d" instead of
deleting the next word. Add a meta+d branch that mirrors the existing
Ctrl+W delete-word-backward, sharing a deleteWordForward helper with the
Delete+word path.

The new cut() wrote the clipboard fire-and-forget and removed the selected
text immediately, so on a headless/SSH box with no clipboard backend the
write fails and the text is lost with no copy to paste back. Make cut
transactional via cutSelection(): await the write and only remove the
selection when it succeeds; on failure the selection stays intact. The
removal also re-checks the selection to avoid slicing with stale offsets
after the awaited write.
2026-08-15 01:05:39 -07:00
null-runner
4e6d863d0e fix(dashboard): gate shortcut sends on PTY state
The word-delete branches sent ^W / ESC d whenever the socket was OPEN,
bypassing the shouldBlockPtyInput gate that term.onData applies to
every other keystroke, so a reconnecting/closed session could still
receive shortcut bytes. Route both branches through a shared guarded
sender that applies the identical socket + PTY state check, and cover
the non-open PTY states in the lib tests.
2026-08-15 01:05:39 -07:00
null-runner
be250390db fix(tui): restore copy and cut shortcuts 2026-08-15 01:05:39 -07:00
null-runner
2108d4bfda fix(dashboard): paste on Ctrl+V and word-delete on Ctrl+Backspace/Delete
The dashboard chat is an xterm.js terminal in a browser tab, so several
editing keys never reached the input:

- Bare Ctrl+V fell through to the TUI, whose server-side clipboard read
  can't see the browser/OS clipboard, so it reported "No image found in
  clipboard". Route Ctrl+V through the same navigator.clipboard path as
  Ctrl+Shift+V (image-or-text). Fixes #24860.
- Ctrl+Backspace / Ctrl+Delete never sent a word-delete: xterm.js emits
  bare DEL regardless of modifier. Send ^W (0x17) and Alt+d (ESC d) to the
  PTY so readline / prompt_toolkit delete the previous / next word.

Ctrl+W itself stays unavailable in a browser tab: it is a reserved
shortcut (close tab) that preventDefault cannot suppress. Ctrl+Backspace
covers word-delete there; the Electron desktop app can bind Ctrl+W.
2026-08-15 01:05:39 -07:00
Teknium
c21dc294b2 chore(contributors): map salvaged author emails 2026-08-15 01:04:19 -07:00
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
Chen Jin
4a663dd693 fix(desktop): sort imports to satisfy perfectionist lint (#84508) 2026-08-15 01:04:19 -07:00
Chen Jin
1dbeb47497 fix(desktop): timestamp every desktop.log / RECENT LOGS line
rememberLog prepended only '[hermes] ' to each line, so desktop.log and
the in-app RECENT LOGS view carried no timestamps while agent.log and
gateway.log (Python logging) did.

Extract the line format into a small pure helper (desktop-log-line.ts)
and prefix each line with an ISO-8601 UTC timestamp shared per chunk,
matching the Python-side convention. Regression tests assert the shape
contract: timestamp + [hermes] tag + verbatim message. Fixes #84405.
2026-08-15 01:04:19 -07:00
liguoyu
c9d0cbaba3 fix(desktop): show exact timestamps in tooltips (#70450) 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
doncazper
6a375a24a5 fix(gateway): timestamp shared stderr output 2026-08-15 01:04:19 -07:00
doncazper
1db9273584 fix(gateway): timestamp launchd error log lines 2026-08-15 01:04:19 -07:00
coe0718
7f7aefe5cb fix: restore complete message timestamp coverage 2026-08-15 01:04:19 -07:00
Tuck
03636ab33f test: add message_metadata unit tests 2026-08-15 01:04:19 -07:00
Tuck
79a41c1d32 fix: convert remaining messages.append() calls to append_message() 2026-08-15 01:04:19 -07:00
Tuck
261ed6e855 fix: stamp timestamps in model_metadata, turn_finalizer 2026-08-15 01:04:19 -07:00
Tuck
d61558793e fix: stamp user message timestamps in turn_context 2026-08-15 01:04:19 -07:00
Tuck
ca77157639 fix: create message_metadata module and update chat_completion_helpers 2026-08-15 01:04:19 -07:00
Teknium
aaaea589f6 test+style: align session.new binding pin with #76185 and eslint --fix 2026-08-15 01:04:06 -07:00
Teknium
100098ee2d chore: map salvage contributor emails for attribution audit 2026-08-15 01:04:06 -07:00
Teknium
a9361cfaae fix(desktop): add zh locale strings for the Disable F12 toggle
zh.ts is typed strictly against Translations, so the new
disableF12Title/Desc keys must exist there (other locales fall back via
defineLocale).
2026-08-15 01:04:06 -07:00
Teknium
dacb99d5b8 fix(desktop): keep the deliberate mod+N chord for session.new
#76185 removed both defaults; only the bare shift+n chord hijacks normal
typing (uppercase N / IME input outside an input field). Cmd/Ctrl+N is a
deliberate two-key chord that matches every browser and chat app, so it
stays. Follow-up narrowing of the salvaged fix.
2026-08-15 01:04:06 -07:00
kas-cor
3b5d5e48d1 fix: address review feedback on Disable F12 DevTools PR
- Add disableF12Title/disableF12Desc to i18n/types.ts contract
- Use focused BrowserWindow from menu click callback
- Persist and restore disable-f12 from main process (cold-launch)
2026-08-15 01:04:06 -07:00
Hermes Agent
d4fa3f7352 feat(desktop): add option to disable F12 DevTools shortcut
- Replace built-in menu role 'toggleDevTools' (which had F12 accelerator)
  with explicit menu item using Ctrl+Shift+I / Cmd+Opt+I only
- Add f12Blocked flag in main process, controllable via IPC
- Add 'Disable F12 DevTools' toggle in Settings → Advanced
- F12 still opens DevTools by default; toggle blocks it
- Ctrl+Shift+I (or Cmd+Opt+I on Mac) always works regardless
2026-08-15 01:04:06 -07:00
Brian Sweatt
89f9375bc0 feat(desktop): archive the current session via hotkey and ⌥+⇧-click
Adds a rebindable 'session.archive' keybind action (shipped unbound, like
session.togglePin) plus an ⌥+⇧-click gesture on sidebar session rows,
extracted into a pure, unit-tested click resolver so modifier precedence
(⌥⇧ archive vs ⇧ pin vs ⌘/⌃⇧ new window) stays correct.

Salvaged from #59759. Closes #59308.
2026-08-15 01:04:06 -07:00
273-B_L0
34f484542e fix(desktop): show platform Kanban modifier 2026-08-15 01:04:06 -07:00
273-B_L0
f61fb52f94 fix(desktop): show platform layout modifier 2026-08-15 01:04:06 -07:00
273-B_L0
a590321de2 fix(desktop): show platform commit shortcut 2026-08-15 01:04:06 -07:00
pierrenode
245efdaaa3 fix(desktop): dispose the HUD snap shortcut on native window close
9c75e4863f added the global ⌘⇧G snap-to-cursor shortcut and wired its
dispose() into closeHudWindow() and before-quit. It missed the HUD's
own 'closed' listener (spawnHudWindow's win.on('closed', ...)), which
fires when the window is closed from its own side — e.g. ⌘W — without
going through closeHudWindow() first.

After a ⌘W close, the shortcut stays registered with no HUD left to
apply it to: harmless (applyHudSnapToPointer guards on a destroyed/null
hudWindow) but it keeps CommandOrControl+Shift+G claimed until the HUD
is reopened (register() releases first) or the app quits.

dispose() is idempotent (guards on its own `active` chord), so calling
it unconditionally in the 'closed' handler is safe even on paths where
closeHudWindow() already released it.
2026-08-15 01:04:06 -07:00
Vladimir Rogozhin Assistant
101dfd782d Add PageUp and PageDown keybind support 2026-08-15 01:04:06 -07:00
BAS Lam
13acf7759e fix(desktop): remove session.new keybind hijacking typed N keys
The session.new default keybindings included 'mod+n' and 'shift+n',
which fired when users typed uppercase N (Shift+N) or accidentally
pressed Ctrl+N while using an IME to type Chinese — silently creating
new sessions mid-conversation.

Remove both combos so New Session is only reachable via the sidebar
button. Ctrl+Shift+N (session.newWindow) is unaffected.
2026-08-15 01:04:06 -07:00
Teknium
1c9de87ffb fix: drop duplicate composingRef declaration from rebase weave 2026-08-15 01:03:57 -07:00
Teknium
32efae941f chore: map contributor emails for salvaged commits 2026-08-15 01:03:57 -07:00
Teknium
89df391580 fix(desktop): keybinds skip IME composition keys; pin draft survival across Settings navigation
- use-keybinds: bail out of the global keydown dispatcher while an IME
  composition is active. Windows Chinese IMEs use Ctrl+, as their
  punctuation toggle, which also matched nav.settings and navigated away
  mid-word — destroying the unsent composer draft (#41079).
- use-composer-draft.test: regression-pin the unmount-stash/remount-restore
  contract so route navigation (Settings) can never again drop an unsent
  draft with the React tree.
- use-composer-actions.test: the bounded-preview pipeline keys attachments
  to the durable path and resolves thumbnails asynchronously; the dropped-
  screenshot test now asserts the durable-path contract instead of the
  retired full-res previewUrl field.
2026-08-15 01:03:57 -07:00
Jakub Wolniewicz
d0642e61e5 fix(desktop): preserve legacy attachment replacements 2026-08-15 01:03:57 -07:00
Jakub Wolniewicz
e2a2fba4aa fix(desktop): preserve in-flight attachment replacements 2026-08-15 01:03:57 -07:00