Commit Graph

4256 Commits

Author SHA1 Message Date
David Metcalfe c06caad8f7 fix(desktop): preserve per-session chat scroll position
Rework per hermes-sweeper review (keep_open, salvageability=medium):

- Capture moves out of the render body into the session-switch layout-effect
  cleanup, so only committed switches persist state (no uncommitted render
  can write localStorage).
- Live state is fed by both scroll events and a ResizeObserver on the
  content element, so distance-from-bottom stays fresh under async
  relayout that changes height without a scroll event (the staleness gap
  #70478's own review threads flagged).
- State is distance-from-bottom (or sticky-bottom), not raw scrollTop:
  the render-budget backfill prepends older turns and main anchors by
  distance-from-bottom, so an absolute offset no longer identifies the
  same reading location after the height change.
- Restore integrates with main's hasGroups/settle/anchor lifecycle: the
  settle loop re-applies the remembered target, defers on a clamped offset
  (content still arriving), hands back locked only for sticky-bottom, and
  leaves mid-read sessions escaped at their offset.
- anchorBeforePrepend no longer records 0 mid-load, which would clobber a
  restored offset once the backfill lands; the settle loop owns the target
  every frame until settled.
- Storage is scoped per profile with the session.ts .profile.<encoded>
  key pattern (no cross-profile bleed, #67709 pattern) and LRU-capped at
  120 sessions per profile.
- Regression tests: state classification, target math, profile isolation,
  LRU eviction, corrupt/invalid payload handling.

Related to #45562 (partial; no automatic closure)

(cherry picked from commit 1d79bef93f63d7b190b471bf652b5109b4288a8e)
2026-09-07 06:14:35 -07:00
Teknium a275adefe2 fix(desktop): retain manual recovery scope through retries 2026-09-07 06:12:00 -07:00
Teknium 5f02693fe0 fix(desktop): reconnect only the active gateway route 2026-09-07 06:12:00 -07:00
Teknium 2bd6fe0dde fix(desktop): keep gateway reconnect available on open transports 2026-09-07 06:12:00 -07:00
Teknium 6c0750e7b7 fix(desktop): cancel MCP auth when scoped Skills tabs leave their owner 2026-09-07 06:10:28 -07:00
Teknium 09229312aa test(desktop): trim MCP relay coverage to ownership and cleanup invariants 2026-09-07 06:10:28 -07:00
Filipe Bezerra 5cf8d7451e fix(desktop): preserve explicit local MCP OAuth without callback bridge 2026-09-07 06:10:28 -07:00
Filipe Bezerra e3ba651b6d fix(desktop): relay MCP OAuth through client-local callbacks 2026-09-07 06:10:28 -07:00
Teknium f83ff89780 fix(desktop): reveal complete slash help across the window
Preserve producer descriptions without identity wrappers and size the existing themed tooltip to the viewport. Replace skipped and structural tests with two behavioral invariants. Native Electron before/after hover, click and keyboard verification passed; campaign suite validation remains queued.
2026-09-07 06:10:15 -07:00
Konstantin Khlopkov 00bb169d1b feat(desktop): full command description hover on slash autocomplete rows (#104729) 2026-09-07 06:10:15 -07:00
Teknium 8db9f49cec fix(desktop): show failed project loads separately from empty sessions
Salvage #104301 error presentation and translations. Keep drill-in outcome local to its mounted scope, ignore stale responses, retain existing rows, and show retry even when cached lanes exist.

Co-authored-by: elvindu <dumanxiang@qq.com>
2026-09-07 06:07:00 -07:00
Teknium ee172900d5 test(desktop): use typed profile fixtures and scope transport stubs 2026-09-07 05:56:17 -07:00
Teknium 6eb4c0803b fix(desktop): limit credential selector to API keys and cover target changes 2026-09-07 05:56:17 -07:00
By JTT 0eabb58e90 fix(desktop): honor Settings profile selector on Providers page
Providers → API Keys was reading the active gateway profile instead of
the shared Settings 'Applies to' override used by Model and Tools & Keys.
Navigating from Model (profile B) to Providers silently switched back to
active profile A, making credential configuration ambiguous.

Now passes $settingsRequestProfile to useEnvCredentials and renders
SettingsProfileScope for visual parity with KeysSettings.

Fixes #103993
2026-09-07 05:56:17 -07:00
Teknium 513c0b2e3c fix: follow focused session panes in desktop sidebar
Reuse the focused pane derivation for visible sidebar activity rather than trusting a stale workspace route. Live Electron reproduction: Kanban stayed highlighted while a resumed session tab was visible; the highlight now clears without changing the retained route. Adapted the rendered sidebar invariant and focus derivation from MarcoFernstaedt's PR #88691 for #88517. Directory suite queued under the campaign test lock.
2026-09-07 05:56:02 -07:00
Teknium 202128f28f fix(desktop): satisfy updater prerequisite lint rules 2026-09-07 05:55:26 -07:00
Teknium 6caf37b6bd fix(desktop): check Windows handoff files before stopping backends 2026-09-07 05:55:26 -07:00
hermes-seaeye[bot] 0d08cd295f fmt(js): npm run fix on merge (#105041)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-09-07 12:09:16 +00:00
Robert Borkowski 9d9bfd6a75 fix: keep secondary profile approvals routable without cached bindings
Preserve profile ownership proven at the secondary socket boundary rather
than trusting arbitrary wire profile fields. Retire transient local owners
with the profile pool, while keeping durable and exact remote ownership first.

Salvage #103774 with two invariant tests and routing documentation. The
profile-only fallback was also identified in the earlier #103770; this
version retains the producer provenance and retirement boundary.

Live Desktop renderer with two isolated serve backends: Reject previously
failed after clearing durable bindings, leaving approval pending. With this
change, the same action sends deny to the owning backend and pending clears.
Existing-binding controls pass on both sides. No vendor inference used.

Fixes #103755
Salvaged-from: 8a3c545e255b66b6ca4bc4b99cd725c5f7a08632
2026-09-07 05:06:54 -07:00
Teknium f6a4a5adde fix: keep multiline delegation goals out of Desktop report bodies
Recognize the full producer task header when a batch goal spans lines,
so the next task goal and preceding transcript footer cannot become part
of the displayed result. Extend the existing invariant and real SQLite
producer probe with that case.

Make the live probe artifact path explicit, run Chromium headlessly, and
document its synthetic fixtures and integration fidelity limits.
2026-09-07 05:06:39 -07:00
Teknium 8c10ffba55 fix: preserve Desktop async report bodies during hydration
Keep compact completion labels while rendering only result and job output bodies, including legacy and batch deliveries. Credit Gyarados4157's #101083 investigation; avoid rendering its full model instruction envelope.
2026-09-07 05:06:39 -07:00
hermes-seaeye[bot] a51912c25b fmt(js): npm run fix on merge (#105038)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-09-07 12:03:25 +00:00
Teknium 55ad5afa12 fix(desktop): retain Markdown boundary and code-copy whitespace 2026-09-07 04:57:11 -07:00
Teknium 3e1b7af21b fix(desktop): preserve Markdown whitespace through media extraction
Remove whole-document whitespace cleanup that erased hard breaks and fenced-code spacing across all five ingestion paths. Limit generated-image gap joining to the removed image itself. Keep soft newlines as soft breaks.

Investigated #97117 and the approaches in #97431 (@wooyongbin3-cpu) and #97175 (@Jackal991); both leave fenced-code whitespace and sibling ingestion paths exposed, so remove the destructive normalization instead.
2026-09-07 04:57:11 -07:00
Teknium dc51977799 fix: keep desktop voice choice when storage is full 2026-09-07 04:56:52 -07:00
liuhao1024 938a3dc8f9 fix(desktop): keep read-aloud independent of gateway auto-TTS
Salvage #99095 (e7ea53074ab2b64a1530641659399d9f1bb4b435), completing one-time migration for both boolean values and using the existing storage helpers. Hydration and local toggles never edit backend configuration. Fixes #99076.
2026-09-07 04:56:52 -07:00
Teknium 42f8389987 fix: restore persisted assistant replies alongside tools and reasoning 2026-09-07 04:56:22 -07:00
Teknium e412727e72 fix: keep task-scroll node tests out of Vitest discovery 2026-09-07 04:56:05 -07:00
Teknium 80e585fad3 test: verify desktop task tails remain reachable in Chromium 2026-09-07 04:56:05 -07:00
Sylvester Kaczmarek d5ad0c2be2 fix(desktop): keep composer status card scrollable 2026-09-07 04:56:05 -07:00
kshitijk4poor 40da71dbb4 docs(kanban): name the legacy-attachments cutoff (#35395) in the type comment
Desktop AGENTS.md requires a compat fallback to be 'tied to an identified
older runtime' so a future cleanup knows when it can be deleted. The
original comment said only 'older backend plugins'; name the actual
boundary: backends before #35395 (May 2026) omit the attachments key.

Review follow-up on the salvage of #104529.
2026-09-07 13:07:32 +05:30
Gille 0d7af8962f fix(desktop): tolerate legacy Kanban task attachment responses 2026-09-07 13:07:32 +05:30
kshitijk4poor 8b9a9f8c23 refactor(desktop): one sessionBucketId for the filter rule and the overview overlay; shorter resolver header 2026-09-07 01:09:00 +05:30
kshitijk4poor 88d0a740c9 refactor(desktop): one live-filter resolver; keep the Home row rule from #102465
#96269 and #102465 fixed the same blank-sidebar symptom with the same
"narrow the persisted filter to ids the live tree names" mechanism.
resolveLiveProjectFilter (the earlier PR) is the single resolver; the
duplicate sanitizeProjectFilter is removed. What #102465 adds beyond it stays:
detached rows file under NO_PROJECT_ID, so filtering to Home keeps Home's rows.
2026-09-07 01:09:00 +05:30
finn763 28c508cce0 fix(desktop): restore sidebar sessions Closes #97762 2026-09-07 01:09:00 +05:30
liuhao1024 fd54282678 fix(desktop): a cross-profile or stale project filter no longer empties the sidebar (#96246)
The sidebar's persisted project filter is a membership whitelist over tree node
ids. Ids that the active profile's tree does not resolve (picked in another
profile, or left over after a project was deleted / an update rebuilt the tree)
used to filter every row out — headers rendered, zero sessions, until Local
Storage was cleared. Narrow the persisted filter to ids the live tree resolves;
dead ids are inert, never fatal. Memo-only, never written back.

Salvage of #96269 (three commits: fix + two lint passes, folded).
2026-09-07 01:09:00 +05:30
kshitijk4poor d86627a7f3 docs(desktop): trim the ssh token comments to the WHY 2026-09-07 00:58:49 +05:30
joaomarcos 690bd8af09 fix(desktop): keep the ssh session token in the v2 connection registry (#103795)
`persistSshConnectionToken()` writes the per-serve session token adopted for
an SSH connection onto its v2 registry entry, but the registry never read it
back: `normalizeRegistry()` rebuilt an `kind === 'ssh'` entry from
`normalizeSshConfig()` alone, which describes only the DIAL (host, user, port,
keyPath, remoteHermesPath, remoteProfile). The sibling remote/cloud branch
preserves `entry.token`; the ssh branch did not.

The token therefore survived only in the mtime-keyed in-process cache. On the
next cold read — an app restart, or any process that re-parses
connections.json — it was silently dropped, so `resolveRemoteBackend()`
decrypted an empty value and dialed with `reuseToken = ''`. That fails the
`Boolean(reuseToken)` clause of remote-lifecycle's `reusable` gate, so a
HEALTHY owned backend was classified not-reusable, reaped by `cleanupStale()`
and respawned on a new port behind a new tunnel — while the renderer kept
dialing its cached `wsUrl?token=` at the old credential and got 403 forever.

`normalizeConnectionInput()` had the same omission: `saveRegistryConnection()`
resolves the surviving envelope via `resolvePersistedRemoteToken()` and passes
it in, but the ssh branch dropped it, so a plain label rename wiped the live
backend's reuse credential and re-armed the same loop.

Both branches now carry the token exactly the way the remote branch does.
There is no auth-mode choice on an ssh entry that could invalidate the
envelope, so no drop condition is needed.

Fixes #103795
2026-09-07 00:58:49 +05:30
hermes-seaeye[bot] cb9546c055 fmt(js): npm run fix on merge (#104539)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-09-06 19:22:04 +00:00
kshitijk4poor 644d00b960 refactor(desktop): keep the codex_message_items hydration fallback; drop the placeholder half
- The placeholder ("assistant response not persisted") is removed: it turned a
  wholly-empty persisted row into TEXT, which in reconcileResumeMessages could
  replace a text-only live stream row at the same ordinal until the turn
  settled. The sidecar fallback alone covers the #68321 field repro.
- Phase filter matches the backend (codex_responses_adapter _OutputScan._message):
  commentary AND analysis are reasoning-channel narration, not the reply.
- Tests trimmed to the two contracts that are red on main; six that passed on
  main (pre-existing reasoning-part behaviour) are gone. File renamed for what
  it now covers.
2026-09-07 00:45:11 +05:30
salch-cred 9e8adb10bb lint: fix import ordering in chat-messages.reasoning-survival.test.ts
Add blank line between @/ alias imports and relative imports per perfectionist/sort-imports convention.
2026-09-07 00:45:11 +05:30
sal 4a5ee5510e style: separate vitest import from local imports (perfectionist/sort-imports) 2026-09-07 00:45:11 +05:30
sal 0b87e89d40 fix(desktop): assistant rows whose text persisted to sidecars no longer vanish on rehydrate (#68321)
Field-level reproduction (2026-09-02, v0.21.0, in the issue thread): an
assistant row with content length 0 whose user-visible response exists
only in reasoning / reasoning_content / codex_message_items renders
live, then disappears from the transcript after a session/profile
switch-back. DB intact - the row is still there on every re-read; only
the rehydrated render loses it. Six independent confirmations across
macOS and Windows since 2026-07-22; all prior fixes (#68329 envelope
normalization, closed implemented_on_main; #77644 mid-turn reconcile;
#101470 compaction-display projections) addressed adjacent producers,
not this one.

Two producers in toChatMessages hydration, both pinned by the new
chat-messages.reasoning-survival.test.ts (fails 9/9 on current main):

1. codex_message_items never read. Responses-API turns can persist with
   `content` empty while the reply the user saw lives only in the
   message-items sidecar ({type:'message', role:'assistant', phase,
   content:[{type:'output_text', text}]}). The live stream painted that
   text; hydration ignored the sidecar, so the rehydrated bubble came
   back blank - and the blank chatMessageText at the same role-ordinal
   then made reconcileResumeMessages drop the cached row's parts
   (sameText/extension/isLiveTailRow all fail), erasing the reply on
   every switch-back. Fix: codexMessageItemText() extracts
   non-commentary assistant output_text as the bubble's text when
   content and reasoning produced no parts. Persisted content still
   wins when present; commentary narration is never promoted.

2. the zero-parts drop. An assistant row hydrating to no parts at all
   returned early and vanished from the transcript entirely - the
   "all assistant messages gone; user messages remain" shape. Fix: a
   wholly empty assistant row (display_kind != 'hidden') paints a
   _(assistant response not persisted)_ placeholder instead of
   disappearing. Hidden scaffolding rows keep dropping as designed
   (pinned by a test).

Also adds codex_message_items to the SessionMessage type (the gateway
has shipped it since the branch-copy lane; the type never declared it).

Verification (native Windows, node 24.17):
- new file: 9 passed (fails 9/9 without the hydration changes)
- src/lib full dir: 1243 passed
- reconcile suites (utils + resume-structural-parts): 115 passed
- streaming/timeline reasoning-part suites: 25 passed
- tsc --build tsconfig.json: clean

Fixes #68321
2026-09-07 00:45:11 +05:30
Teknium e27b8c5b92 test(desktop): trim session-control suites to behaviour contracts
session-control.test.tsx 1277→381 lines (32→8 cases): legacy-vs-structured
precedence, action dispatch, send continuation (idle + busy-queue), failure
banner, heartbeat countdown. store/session-control.test.ts 648→472 lines
(26→16): ordering/stale-token invariants, method-not-found downgrade, and a
gateway-switch wipe test replacing the removed rebind seam tests; the
setTimeout-spy 'no timer' test dropped.
2026-09-06 09:21:45 -07:00
Teknium efc30169c3 fix(desktop): queue goal-resume continuation when busy; wipe session controls on gateway switch
- session-control-goal.tsx: a 'send' dispatch against a busy session now
  parks the kickoff on the composer queue (the backend already resumed the
  goal) instead of reporting continuationFailed. Shared helper
  queueKickoffIfSessionBusy() extracted from slash.ts so both paths agree.
- gateway-switch.ts: wipeSessionListsForGatewaySwitch clears
  $sessionControlBySession (runtime-id keyed; new backend re-mints ids).
  Dead resetSessionControlAfterGatewayRebind removed; clearSessionControl
  now called from the session delete path beside clearQueuedPrompts.
- session-control.tsx: read/hydration failures use controlUnavailable copy,
  not actionFailed.
- i18n: heartbeatDueWaitingForIdle added to ja/ru/zh-hant; new
  continuationQueued/continuationBusy/controlUnavailable in all locales.
2026-09-06 09:21:45 -07:00
Jerry Gooch 45b7dabfb9 fix(desktop): run and surface Heartbeats reliably
(cherry picked from commit 77af78f1d8b7cb736d5d86e4fc7722449528a269)
2026-09-06 09:21:45 -07:00
Jerry Gooch 4f008c36bb fix(desktop): preserve session control action state
(cherry picked from commit 0efb410ed49ed6977352b313a639ff3f9d9375c1)
2026-09-06 09:21:45 -07:00
Jerry Gooch dffd8d62c2 feat(desktop): add session automation controls
(cherry picked from commit 8a61c2bcba8e4c7b3adcc172d4e5457721f76990)
2026-09-06 09:21:45 -07:00
Jerry Gooch bfddf556bf feat(desktop): hydrate structured session controls
(cherry picked from commit fec4bab6a191c474456956a860d86957d435552b)
2026-09-06 09:21:45 -07:00
Teknium fe49acb670 refactor(desktop): import-session entry is a sidebar nav row; browser module named as a foreign_sessions sibling
- Sidebar: the entry joins SIDEBAR_NAV (same chrome, active state, data-tour
  handle as the other rows) instead of a one-off Button below the rail;
  i18n moves to sidebar.nav['session-import'].
- Reuse common.retry / common.refresh / common.back instead of duplicating them
  under sessionImport in six locales.
- hermes_cli/foreign_session_browser.py -> foreign_sessions_browser.py so it
  sorts as a sibling of the foreign_sessions module it extends.
2026-09-06 09:09:41 -07:00