Commit graph

334 commits

Author SHA1 Message Date
Brooklyn Nicholson
4aa672b23e fix(desktop): stop cold-start resume homing focus to the workspace tab (⌘R tab persistence)
The layout tree persists the active tab, but every reload landed on main
anyway: boot's route resume sets $selectedStoredSessionId, and the
selection listener in store/session-states.ts treats every selection
change as a navigation — noteActiveTreeGroup(null) +
revealTreePane('workspace') — fronting the workspace tab over the
persisted active tile and then persisting that clobber.

A cold-start restore is a re-attachment, not a navigation, so
use-route-resume now arms a one-shot (markSelectionRestore) before
dispatching the window's FIRST resume; the selection listener consumes
it and skips homing exactly once. Every later resume — sidebar click,
route change, reconnect — homes as before, and starting on /new
consumes boot status too so a subsequent session open still homes.
2026-07-29 20:57:04 -05:00
Brooklyn Nicholson
3c5da5307c feat(pairing): give pairing its own change signal
The poll this page's pairing block rode was retired hours earlier by
f8e07a332, which moved Messaging onto platforms.changed. That signal is the
mtime of gateway_state.json — where the gateway persists connect/disconnect
health — and a new pairing request moves none of it. So on an event-capable
backend a pending row stayed invisible until something unrelated
reconnected, and the count badge with it.

Adds pairing.changed to the change watcher, signed off the pending/approved
ledgers across the global store and every profile's own. _rate_limits.json
is deliberately excluded: it moves on every unauthorized DM, including ones
that produce no new row, so signalling on it would refetch for nothing.

The page now refreshes platforms and pairing on their own signals rather
than one combined call, and the legacy visible-tab poll (older backends)
covers both.
2026-07-29 18:50:09 -05:00
brooklyn!
5c07ba2f3a
Merge pull request #74422 from NousResearch/bb/platforms-changed
Desktop: platforms.changed broadcast retires the Messaging page's 6s status poll
2026-07-29 18:37:35 -05:00
Brooklyn Nicholson
fb120f850d refactor(desktop): move the main-is-occupied check into the session door
newSessionOpensTab answers a question that isn't specific to the sidebar
"+" — is there a conversation on main that must not be discarded — and the
palette needs the same answer. Fold it into open-session as
mainChatOccupied so both callers share one definition.
2026-07-29 18:16:14 -05:00
Brooklyn Nicholson
f8e07a332e feat(desktop): platforms.changed broadcast retires the Messaging page's 6s status poll
The change watcher (#73673) missed one always-on-while-mounted timer: the
Messaging page polled /api/messaging/platforms every 6s for connection
status. The gateway already persists platform connect/disconnect/health to
gateway_state.json, so watch that file's mtime and broadcast
platforms.changed (floored to 5s — the gateway also rewrites the file for
in-flight-count bookkeeping), route it through live-sync like its
siblings, and refresh the page on the tick. Older backends keep the
legacy visible-tab poll verbatim.

Finishes the always-on poll sweep for #73618.
2026-07-29 17:33:35 -05:00
brooklyn!
a4973c3f11
Merge pull request #74363 from helix4u/fix/windows-wake-input-device
fix(wake): keep desktop ownership and select input devices
2026-07-29 15:44:13 -05:00
hermes-seaeye[bot]
e23d158f48
fmt(js): npm run fix on merge (#74360)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-29 20:30:47 +00:00
Gille
94d1dff50d fix(wake): route desktop control and select input devices 2026-07-29 14:04:21 -06:00
brooklyn!
c8f911112c
Merge pull request #74033 from NousResearch/jb/session-detail-profile-stamp
fix(sessions): always stamp owning profile so cross-profile open works toward default
2026-07-29 12:39:09 -05:00
Teknium
df093bf33c feat(voice): calm ambient "thinking" sound while the agent works in voice chat
Long thinking/tool stretches in a voice conversation are dead air — the
user cannot tell whether the agent is alive. New: quiet, repeating soft
bubble blips while the agent works and no speech audio is flowing.

- tools/voice_mode.py: numpy-synthesized blips (no binary assets) — two
  alternating low pitches (G4/E4) with pitch glide + smooth attack/decay
  envelopes, ~0.8-1.2s randomized spacing, volume = voice.beep_volume * 0.5.
  start_thinking_sound(should_play=...) / stop_thinking_sound() daemon-loop
  lifecycle; macOS-TCC-safe (sounddevice output gated there → silent skip,
  no per-second afplay churn). New mark_audio_output_active()/
  is_audio_output_active() ref-count wraps play_audio_file and the
  streaming OutputStream sentence writes so "audio is flowing" is accurate.
- Config: voice.thinking_sound (default true) off-switch.
- cli.py: starts when a voice-mode turn begins, per-blip gate skips while
  TTS speaks / mic records / barge capture owns the mic; stopped in the
  chat() finally on every exit path.
- tui_gateway/server.py: same lifecycle around _run_prompt_submit turns
  (voice mode on), gated on is_audio_output_active + continuous capture.
- Desktop: renderer owns voice-conversation audio, so a matching WebAudio
  implementation (src/lib/thinking-sound.ts, same envelope/pitches) runs
  while conversation status === "thinking"; honors voice.thinking_sound
  (via config store) and the shared sound-mute toggle; stops instantly on
  speaking/listening/end.
2026-07-29 08:24:00 -07:00
Teknium
6fdfdc1597 feat(voice): "Say <stop-phrase> to end the voice chat" notice on voice-mode start (CLI/TUI/desktop, i18n)
One owner for the wording: voice_stop_hint() in tools/voice_mode.py —
sources the phrase from voice.stop_phrases (first entry) so a custom
phrase renders correctly, and returns "" when the feature is disabled
(stop_phrases: []) so no surface shows a hint.

- CLI: printed in /voice on output (style-matched dim notice).
- TUI: voice.toggle action=on now carries stop_hint; the Ink client
  renders it in the "Voice mode enabled" block (older gateways omit
  the field — no hint, no crash).
- Desktop: the renderer voice loop never touches tools/voice_mode.py,
  so the phrase is read from config (voice.stop_phrases → $voiceStopPhrase
  store, seeded in use-hermes-config) and shown as an info toast when a
  voice conversation starts. i18n: en/ja/zh/zh-hant/ar.
2026-07-29 08:24:00 -07:00
yoniebans
a8ec50f681 fix(sessions): always stamp owning profile so cross-profile open works toward default
Session rows served without ?profile= carried no profile field, so in
multi-profile desktops the default profile's sessions circulated unowned:
resolveStoredSession cached them profile-less, resolveSessionProfile returned
undefined, and session.resume targeted whichever gateway was active -- opening
a default-profile session from a non-default window failed with
"session can't be found" while the reverse direction worked (#67603 family).

Server: GET /api/sessions/{id} and GET /api/sessions now stamp profile/
is_default_profile unconditionally -- the serving profile is always known
(_cron_default_profile() when the request is unscoped).

Renderer: resolveStoredSession treats a profile-less $sessions cache hit as
unresolved when >1 profile exists (falls through to the stamped by-id ladder)
and back-fills the active profile on bare by-id hits from older backends, so
unowned rows are never re-cached.

Verified via CDP against a live 4-profile renderer: bare by-id GET returned
hasProfileField:false and the stale cache rows matched; with the fix both
lookups return the owning profile and the resume routes to the right backend.
2026-07-29 13:54:12 +02:00
Brooklyn Nicholson
8830f22c9d fix(desktop): scope the queued-drain binding check to fromQueue
The binding check landed unscoped, so it fired for every caller passing a
sessionId/storedSessionId pair — not just queue drains. A slash skill
dispatch into a fresh ⌘T tab passes exactly that shape (sessionId=tab
runtime, storedSessionId=tab stored) with no central binding recorded yet,
so the check nulled the target and the kickoff dropped instead of landing
in the tab.

Only a drain pairs identifiers from two different clocks; every other
explicit-target caller resolves both ids in the same tick and is
authoritative by construction. Gate on fromQueue and add the scoping
invariant as a test.

Also refresh the two drain tests for `queued: true`, which prompt.submit
started sending for queued drains in ab68c5efe after this work branched.

Co-authored-by: theone139344 <theone139344@users.noreply.github.com>
2026-07-29 00:12:12 -05:00
theone139344
8879b9e9b2 fix(desktop): keep queued drains out of the foreground session on session switch
A queue drain pairs two identifiers from different clocks: the queue key
(flips with the route) and the explicit runtime id (lags a resume behind).
Mid-switch the composer can fire a drain with storedSessionId=B but
sessionId=A-runtime, and prompt.submit then lands B's queued prompt — and
its whole answer turn — inside session A.

Make the central runtime binding authoritative for queued sends: when the
explicit runtime id no longer matches the binding recorded for the target
stored session, adopt the binding (or drop to the stored-id resume path
when none exists yet). The identity pair (storedSessionId === sessionId)
is the fresh-chat fallback and stays untouched.

Tests: re-home-via-resume and rebind-to-central-runtime; existing
background-drain and sleep/wake cases declare central bindings explicitly.
Null-fallback guard already on main; this PR is the remaining half.
2026-07-29 00:03:52 -05:00
Brooklyn Nicholson
254aeda122 fix(desktop): dedicated reader for large remote attachments
Keep Settings-configurable preview/image loads on readFileDataUrl, and
route remote non-image attach through a 256 MiB IPC so uploads are not
stuck on the 16 MiB default after #73221.

Co-authored-by: Börje <borje@dqsverige.se>
2026-07-28 19:20:14 -05:00
Brooklyn Nicholson
7d5b92cdfb fix(desktop): bump session list order on user send
Recents were hard-sorted by started_at and only refreshed last_active after
turn complete — so reviving an old thread stayed buried until the assistant
finished. Stamp last_active on prompt seed, keep it monotonic across mid-turn
refreshes, and sort agent recents by activity.
2026-07-28 18:53:33 -05:00
Brooklyn Nicholson
fe64cafa33 feat(desktop): event-driven live sync — change broadcasts replace the always-on polls
One controller (store/live-sync.ts, the workspace-events twin for gateway
state): gateway-event.ts routes pet.changed / cron.changed /
sessions.changed into tick atoms, gated on the active profile like
skin.changed; gateway.ready's change_events flag arms them; a gateway wipe
resets the capability.

Consumers subscribe to ticks and demote their polls to slow backstops
(legacy cadence is kept verbatim against older backends):

- session.active_list 1.5s → sessions.changed + 30s backstop
- cron list 30s → cron.changed + 5min backstop
- messaging lists 10s → trailing sessions.changed refresh + legacy poll
  only when events are unavailable
- open messaging transcript 5s → sessions.changed + 30s backstop
- pet.info 3s/15s → pet.changed, NO timer on event-capable backends
  (users with no pet used to poll hardest); enabled=false broadcasts
  clear the mascot with zero round-trips
- cron runs page/peek 8s → cron.changed + 60s backstop

Idle renderer traffic drops from ~89 req/min to the backstops (~5/min).
Part of #73618.
2026-07-28 17:21:58 -05:00
hermes-seaeye[bot]
66e20786d0
fmt(js): npm run fix on merge (#73666)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-28 21:55:38 +00:00
Atakan
15317e1fd7 fix(desktop): upload Windows attachments to Linux backends 2026-07-28 12:36:01 -07:00
hermes-seaeye[bot]
2faac36866
fmt(js): npm run fix on merge (#73552)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-28 18:30:19 +00:00
Adolanium
b5dc471152
feat(desktop): make attachment data-URL size limit configurable (#73221)
Hard 16 MB cap on readFileDataUrl blocked larger local attaches with no way to raise it. Settings -> Chat now has a free-form MB field. Main process owns the persisted value and clamps only absurd inputs.
2026-07-28 09:10:21 -04:00
brooklyn!
30526baab5
Merge pull request #73262 from NousResearch/bb/dedupe-main-tile
fix(desktop): drop a session's tile when it loads into the main tab
2026-07-28 04:23:02 -05:00
Brooklyn Nicholson
ab741ed331 fix(desktop): drop a session's tile when it loads into the main tab
A session is meant to be either the main thread or a tile, never both.
openSessionTile enforced this from the tile side (refusing to tile the
selected session), but the main side never dropped an existing tile — so
any path that routes main to a session that's already tiled (cold-start
remembered-session restore, a pasted/Cmd-K route, a notification jump)
painted the same transcript twice: the workspace pane from the route and
the tile pane in parallel, both fighting one runtime.

resumeSession is the single chokepoint every load-into-main path funnels
through, so close the redundant tile there once the session becomes the
selection. The warm cache/runtime binding survives for main to reuse.
2026-07-28 04:16:28 -05:00
Brooklyn Nicholson
b3ba5570c9 Merge origin/main into bb/skill-chip-only
Keep skill-invocation projection helpers alongside the auto-continue
legacy display typing from #73250.
2026-07-28 04:06:32 -05:00
Brooklyn Nicholson
42fc6227f6 fix(ci): sort desktop imports and stub skill display in TUI test
Eslint wants @hermes/shared before react, and the slash handler only
passes a display override when command.dispatch includes one.
2026-07-28 04:03:48 -05:00
Brooklyn Nicholson
e682a9c0a7 refactor: drop the shared subpath export, keeping package.json untouched
The subpath entry existed only so the TUI could import the client-side
projection fallback. The TUI spawns its gateway from this same checkout and
cannot version-skew with it, so that fallback was dead weight — it reads
`display` directly now. With its last caller gone the dispatch helper folds
back into the desktop, where an older backend is genuinely reachable.

apps/shared/package.json is byte-identical to main again.
2026-07-28 03:54:49 -05:00
Brooklyn Nicholson
20b2022b8c fix(desktop): render a skill send as its invocation everywhere it surfaces
The bubble, the queue panel, and the queue editor all showed a queued or
sent /skill turn's expanded body. Thread the invocation through submit and
the queue entry, and chip a leading slash command the way a mid-prose one
already chips.
2026-07-28 03:44:31 -05:00
Brooklyn Nicholson
341f95b249 feat(desktop): cache slash completions and list skills on a bare /
The composer re-asked the gateway for the command catalog on every open
and for complete.slash on every keystroke. Both scan the skills dir on
the backend, so opening the menu cost a round trip against data that
only moves when a skill is added, removed, or toggled.

Hold both behind a one-hour cache keyed per query, and invalidate it
where the skill set actually changes: hub install/uninstall/update, a
Capabilities toggle, bulk toggle, archive, the agent's own skill_manage
call, and a profile switch. A cached query also skips the debounce and
the spinner, so a warm menu opens in the same frame.

The bare-slash list showed no skills at all: the backend categorizes
registry commands but appends skills to the flat pairs list only, and
the popover prefers the categorized layout. Re-add the uncategorized
leftovers under a Skills header, so /clean and /work are visible on /
and not just after typing enough of the name to match.
2026-07-28 03:19:51 -05:00
Brooklyn Nicholson
ab68c5efec fix(gateway): a queue drain never becomes a live-turn correction
The desktop's queue promises "run this AFTER the current turn", but the
promise broke on a race the user can't see: a drain that fired when the
client observed idle while the server was still unwinding the turn landed
in _handle_busy_submit, which applied busy_input_mode — redirecting or
interrupting the live turn with text the user explicitly queued. That's
why force-sending the queue felt like a dice roll: the same gesture
steered, interrupted, or queued depending on a millisecond settle race.

prompt.submit now carries queued:true on every fromQueue drain (composer
auto-drain, send-now, background drain), and the gateway's busy path
honors it by forcing queue semantics — never steer, never redirect,
never interrupt. Lose the race and the text simply waits its turn, which
is what queueing meant all along.

Covered on both sides: a gateway test proving a queued drain cannot touch
redirect/steer/interrupt on the live agent, and the desktop drain tests
now assert the flag rides every prompt.submit shape (direct, background,
resume-retry).
2026-07-28 01:48:08 -05:00
Brooklyn Nicholson
0b0e53cee1 fix(desktop): retire the drafting label when the model moves on
`tool.generating` names the tool whose arguments are streaming, and nothing
ever closed that claim: there is no stop-drafting event, and a draft can be
abandoned without reaching `tool.start` when a mid-stream retry drops a
partial call or a guardrail blocks the tool. Enumerating the ways a draft
ends left those holes open, so "Editing" sat under the transcript for the
rest of a multi-iteration turn.

Invert the rule — the claim only covers what the model is emitting right
now, so any other output from that session retires it. Stopping the turn
clears it too, and a `tool.generating` that arrives after the stop is
ignored on the same condition `mutateStream` already drops late tool rows.
2026-07-27 19:34:54 -05:00
Brooklyn Nicholson
f08b0e5606 Merge remote-tracking branch 'origin/main' into bb/desktop-activity-grouping 2026-07-27 18:42:49 -05:00
Brooklyn Nicholson
a3c46ac09e refactor(desktop): give live tool runs a one-line ticker
A live run was the settled view under a CSS max-height: the real rows,
capped at ~6.75rem, with an escape hatch that let an open diff lift the cap
entirely. So it was neither a single line nor an honest expanded block, and a
run could balloon mid-turn.

Split the two presentations instead. Live, a run is its summary plus a
one-line reel that slides each finished action up and out, so a turn touching
thirty files reads as one line ticking over in place. Settled, the summary is
the whole of it until opened. Cards — file edits, clarify, image_generate —
leave the run entirely and stay on screen where they happened, since the diff
or the question IS the point of the turn.

Drops useToolWindow, the bounded-window threshold, the scroll container, the
fade mask and the :has([data-tool-open]) escape hatch, all of which existed to
make "the real rows, but shorter" work.

Also unifies transcript scaffolding on one colour: thinking headers painted
--ui-text-secondary and tool summaries --ui-text-tertiary under a shared
opacity, which is two greys for one kind of line. Both now render through
ScaffoldRow at a token pitched between them.
2026-07-27 18:42:46 -05:00
hermes-seaeye[bot]
96db67b849
fmt(js): npm run fix on merge (#72967)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-27 23:12:24 +00:00
Brooklyn Nicholson
96999b116b refactor(desktop): put every preview on one rail tab list
The right rail held two things at once: a list of file tabs, and a
privileged "live preview" slot with a hardcoded `preview` tab id backed by
a separate session-keyed registry. The two were written under different
session-id rules and reconciled against each other, so an `open_preview`
from a session whose stored id hadn't landed yet was set and then
immediately cleared — the pane flashed and vanished. Artifacts arrived as
a third list with their own pane and renderers.

Now everything the rail can show is a `PreviewTarget` in `$previewTabs`,
and `openPreview` is the only way in. `$previewTarget` is a computed read
of the active tab, the session registry and its reconciler are gone, and
artifacts render in the real preview pane through the shared mode switcher
and source view instead of a parallel one. Artifact tabs stay memory-only
since the registry rebuilds from the transcript.
2026-07-27 17:55:27 -05:00
Gille
dbc18c6d62
fix(desktop): preserve live model after settings save (#72903) 2026-07-27 17:52:37 -04:00
alelpoan
5ecc666146 test(desktop): cover workspace pane reveal in selectSidebarItem
Adds a regression test for #72602: navigating to a sidebar route now calls navigate() and fronts the workspace pane (noteActiveTreeGroup(null) + revealTreePane('workspace')).

Mocks @/components/pane-shell/tree/store to assert the calls without depending on real pane-tree DOM state.
2026-07-27 12:54:20 -05:00
alelpoan
9cef5496eb fix(desktop): front workspace pane when navigating sidebar routes
Capabilities/Messaging/Artifacts (and other full-page workspace routes) rendered their content correctly on navigate(), but the workspace pane itself stayed behind an active session tile in the pane tree if one was focused. Clicking the same sidebar item again after switching to a session tile appeared to do nothing.

Session switches already call revealTreePane('workspace') + noteActiveTreeGroup(null) to front the pane (store/session-states.ts), but sidebar/keybind/command-palette navigation to full-page routes did not have an equivalent.

Add navigateToWorkspacePage() helper in routes.ts that wraps navigate() and fronts the workspace pane for non-overlay, non-chat views. Apply it at all 5 call sites that navigate to full-page workspace routes: selectSidebarItem, use-keybinds (nav.skills/messaging/artifacts), command palette 'go', Command Center's onNavigateRoute, and cold-start route restore.

Fixes #72602
2026-07-27 12:54:20 -05:00
hermes-seaeye[bot]
846b14ab01
fmt(js): npm run fix on merge (#72703)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-27 14:52:58 +00:00
alelpoan
820a8083d4
fix(desktop): Branch in new chat drops the question and loses the branched session on restart (#71960)
* fix: Branch in new chat loses the question and the branched session (#issue)

- session.branch on the backend now accepts a count param to truncate
  the parent history to the clicked message, instead of always forking
  the entire transcript. Also returns stored_session_id/messages/info
  so the frontend has parity with session.create's response shape.
- branchCurrentSession (open live chat) now slices history from 0
  instead of from the clicked message index, so the question preceding
  an assistant reply is no longer dropped when branching.
- forkBranch now calls session.branch (not session.create) when
  branching an open live chat, since session.create only persists a DB
  row lazily on first prompt - a branched chat that nobody types into
  never got saved, and vanished as 'session not found' on the next
  app restart. branchStoredSession (branching from the sidebar, no
  live runtime) keeps using session.create as before.

* test: cover session.branch count truncation and open-chat branching

- backend: assert session.branch with a count param only persists the
  first N messages of the live history to the new session.
- frontend: BranchHarness now exposes branchCurrentSession; assert
  branching an open chat from a middle message calls session.branch
  with the parent session id and the correct trimmed count, instead of
  session.create.
2026-07-27 10:45:00 -04:00
hermes-seaeye[bot]
215ec101be
fmt(js): npm run fix on merge (#72522)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-27 07:00:22 +00:00
brooklyn!
825069004a
Merge pull request #72504 from NousResearch/bb/desktop-real-session-perf
perf(desktop): 60fps on real sessions — reflow-gated pins, adaptive flush, stream-aware backfill
2026-07-27 01:50:41 -05:00
Brooklyn Nicholson
2c867b05ce perf(desktop): 60fps sash drag on real sessions — height-gate the RO pins
Driving HER real instance (real profile, real transcripts, streams live)
via CDP instead of synthetic tiles finally exposed the remaining stall.
The timeline on a real 60-frame sash drag:

  style recalc 2736ms | script 1027ms | layout 89ms
  top callsite: pin @ fallback.tsx — 927ms

Two pin-to-bottom ResizeObservers (the bounded tool window's and the
reasoning preview's) pinned on EVERY resize delivery. A sash drag changes
every message's WIDTH once per frame, so each frame ran scrollTop write ->
scrollHeight read across every tool group: a forced write-read reflow
cascade that the render counters could never see (zero React involvement).

Both pins are now height-gated off the RO entry (reflow-free): only
content GROWTH pins. Width-only deliveries return immediately.

Measured on the live app, same drag, before -> after:
  fps      11.5 -> 59-60
  p95      101ms -> 18ms
  slow>33  60/60 -> 1/60

Also in this batch (each was verified live before the next was attempted):
- thread/list: split messageSignature into STRUCTURAL (ids/roles — keys
  boundaries + row identity) and WEIGHT (part counts — budget only), and
  memoize groups + row JSX. A streamed part-append re-rendered every
  turn's boundary via its resetKey prop; explain() measured 540-865
  wasted Block renders per drag/stream sample, now {}.
- message-render-boundary: document the structural-only resetKey contract.
- tool/fallback: memoize ToolFallback's part object + ToolEntry/ToolTitle/
  ToolGlyph (151 renders each, 100% wasted, on real transcripts).
- use-message-stream: ADAPTIVE flush floor — next flush waits 3x the
  measured cost of the last one (33ms floor, 250ms cap), so multi-stream
  load degrades text update rate instead of input latency.
- tree-split: preview sash drags with inline flex on the two seam
  wrappers, committing the store ONCE on release (fixed-zone sides get
  flexBasis only, so a hidden sidebar can't leave a phantom gap).
- debug/: perf-live LoAF long-frame attribution, explain() cascade walker
  with changed-hook indices, diag-real-loop/key-latency/switch-trace
  probes that drive the real app over CDP.

Typing during 2 live streams: keystroke->paint p50 3.3ms, p95 18.4ms,
zero frames over 33ms. Session switch p50 ~35ms settled; the remaining
~1.3s outlier tail is streaming-session switches (React work-loop, not
style/layout) — next target.
2026-07-27 01:07:48 -05:00
teknium1
2be2464d25 fix(desktop): satisfy eslint in approvals-command rebase 2026-07-26 21:13:03 -07:00
Teknium
f9cd577915 feat(approvals): add cross-surface mode command
Co-authored-by: luxiaolu4827 <227715866+luxiaolu4827@users.noreply.github.com>
2026-07-26 21:13:03 -07:00
teknium1
f8918391d9 fix(desktop): drop now-unused clearSessionSubagents import in gateway-event
The message.start site swapped to pruneFinishedSessionSubagents; the old
import survived the rebase and trips unused-imports + sort-imports lint.
2026-07-26 20:37:37 -07:00
Sophia
14c754cda4 fix(desktop): remove unused imports from gateway-event.ts
Per hermes-sweeper review on PR #67005:
- Remove `broadcastSessionsChanged` import (unused)
- Remove `setSessionTodos` import (unused)
- Keep `clearActiveSessionTodos` and other active imports

No behavioral change, just dead code removal.
2026-07-26 20:37:37 -07:00
Sophia
8a32426300 fix(desktop): preserve live background subagents across message.start (rebased onto main, #64015)
Cherry-pick of b4d5ba3e onto current main. Original commit's target file
apps/desktop/src/app/session/hooks/use-message-stream.ts has since been
moved into apps/desktop/src/app/session/hooks/use-message-stream/gateway-event.ts.
The fix is unchanged: replace clearSessionSubagents() with
pruneFinishedSessionSubagents() at the message.start boundary so that
still-running subagent rows survive new turns.

Per @teknium1's review of #64038 on 2026-07-16: 'mergeable_state=dirty'
because the dispatcher moved. Resolved by retargeting the call.

Tests: 3 new cases in subagents.test.ts (unchanged from original PR).
Failing-first verified in original PR (3/3 fail on unfixed, 3/3 pass on fix).
2026-07-26 20:37:37 -07:00
hermes-seaeye[bot]
8c36cd4670
fmt(js): npm run fix on merge (#72411)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-27 02:52:53 +00:00
Brooklyn Nicholson
c74f48b62e fix(desktop): null-guard the rotation signal fired from ensureSessionState
The salvaged no-op-publish guard moved the compression-rotation signal
into ensureSessionState, where storedSessionId is string|null. A cleared
stored id is a detach, not a rotation — firing the event with a null
next id would send the route-follow effect chasing nothing (and tsc
rejects it). Guard on a real next id.
2026-07-26 21:05:55 -05:00
Andy
651a313d2b fix(desktop): suspend decorative work when inactive 2026-07-26 21:05:55 -05:00