Commit Graph

46811 Commits

Author SHA1 Message Date
Brooklyn Nicholson
57cad6c2cd chore: map contributor email 2026-10-01 04:50:03 -05:00
hernanda-git
65db662577 fix(desktop): stop logging a handler stack for expected session-probe 404s
resolveStoredSession() is a probe ladder — cache, then a by-id GET on the
active backend, then the same GET scoped to each other profile until one
hits. A miss on rungs 2 and 3 is expected and already handled: the backend
answers 404 {"detail":"Session not found"} and the renderer falls to the
next rung.

But ipcMain.handle logs "Error occurred in handler for 'hermes:api'" with a
full stack for every rejected invoke, with no per-handler opt-out — so each
designed fall-through printed a multi-line stack on startup and on every
session switch, burying real hermes:api failures.

Extract the handler body, and for that one expected case resolve with a
sentinel instead of rejecting; preload — our own code on the other side of
the same seam — rethrows it as an Error with the identical '404: <body>'
message. The renderer contract is byte-identical (the ladder and its tests
are untouched); only Electron's logging is bypassed. Every other failure
still rejects and still logs in full.

The sentinel check is deliberately narrow (a plain object whose only key is
the sentinel, with a string value) so no backend payload can be mistaken
for one.

Fixes #79265
2026-10-01 04:50:03 -05:00
Brooklyn Nicholson
234badf401 docs(tui_gateway): an unanswerable request would now wait forever, not 300s 2026-10-01 03:50:55 -05:00
Brooklyn Nicholson
bdf4a5d632 chore: review follow-ups — stale timeout comments, softer docs claim, group-turn cap credits only user-blocked intervals 2026-10-01 03:50:55 -05:00
Brooklyn Nicholson
553bd72f33 fix(cli): /bg turns hold their prompts open like the foreground turn
/bg registers the interactive approval panel, which no longer has a deadline;
without the binding its batch deadline and authorization-gate accounting still
assumed approvals.timeout.
2026-10-01 03:50:55 -05:00
Brooklyn Nicholson
714d859171 style(desktop): prettier wrap for the longer settings descriptions 2026-10-01 03:50:55 -05:00
Brooklyn Nicholson
214998ecc0 docs(agent): the authorization-gate bound follows the turn's approval window 2026-10-01 03:50:55 -05:00
Brooklyn Nicholson
58dffc74ef test(desktop): a group turn blocked on the user's approval outlives the hard cap 2026-10-01 03:50:55 -05:00
Brooklyn Nicholson
0ea07255e0 feat(desktop): copy and Bot Mode group turns follow no-deadline prompts
A Bot Mode group turn blocked on the user's clarify/approval answer pushes its
runaway hard cap out instead of expiring the card under the room; the offline
toast and the Settings description stop promising an approval timeout; the
approval.respond deadline comments describe what it bounds now.
2026-10-01 03:50:55 -05:00
Brooklyn Nicholson
d7fdebf45a docs: approvals.timeout and agent.clarify_timeout are the messaging-platform deadlines 2026-10-01 03:50:55 -05:00
Brooklyn Nicholson
bd501669ce feat(tui,desktop): approval and clarify cards wait until answered
tui_gateway (the TUI's and the Desktop app's backend) binds the no-deadline
policy around every prompt turn and sends clarify requests with no deadline.
2026-10-01 03:50:55 -05:00
Brooklyn Nicholson
ea5b89fa35 feat(cli): approval and clarify panels wait until answered
The classic CLI binds the no-deadline policy around each turn and drops its own
approvals.timeout / clarify_timeout deadlines; the hint row hides the countdown.
Removes the now-unreachable timeout copy from every locale.
2026-10-01 03:50:55 -05:00
Brooklyn Nicholson
e57fa350cb feat(approval): a turn can hold its approval prompts open until answered
set_prompts_wait_for_answer() binds, per turn, that built-in approval prompts
have no deadline; approval_wait_seconds() is the one resolver the gateway poll
loop, the CLI prompt and the human-wait ceiling read. approvals.timeout keeps
governing messaging platforms, ACP and approval transport plugins.

A human-wait window now fixes its ceiling when its waiters enter it, so the
batch-deadline loop reading it from an unbound context still excludes an
unbounded prompt in full.
2026-10-01 03:50:55 -05:00
Brooklyn Nicholson
572e4f4fad test(desktop): read the session-scoped tab id in the tombstone test 2026-10-01 03:46:08 -05:00
Brooklyn Nicholson
cb9cb968ee fix(desktop): keep popped Browser tabs out of docked closes after the session-scope rebase
Main's closeDockedPreviewMatching filtered popped-out tabs; carry that onto
the session-visible tab list. Re-add preview.pin/unpin in the split locale
modules and the key manifest, and let the model-picker suite derive the
focused session from its mocked tree focus.
2026-10-01 03:46:08 -05:00
Brooklyn Nicholson
310c7ae9e5 fix(desktop): keep minted Browser tab ids across restore; complete test mocks for session-focus
parseTabList rekeyed restored URL tabs onto url:<address>, breaking the
pop-out handoff (tabs are looked up by their persisted minted id) and the
never-reuse id contract. Only FILE ids need the session-scoped rekey.

The session-focus module's new imports broke tests whose vi.mock factories
omitted them: tree/store mocks now export $activeTreeGroup/$layoutTree, and
the statusbar mock keeps the real $focusedStoredSessionId derivation.
2026-10-01 03:46:08 -05:00
David Metcalfe
824e107cb7 fix(desktop): harden preview session-scoping reuse paths
Review feedback on #87288 (Enough1122 + cross-vendor validation):

- openPreview: reusing a pinned file row from another session keeps the
  owner's id (no id/sessionId split); the singleton Browser and artifacts
  reached from another session re-own to the navigating session so the new
  URL is not invisible in the session that opened it; the rail selection
  follows the row's final id instead of the pre-computed one.
- preview-tile: merge the visible-list reveal/rehome listeners into a
  single rehome-then-reveal listener so ordering is explicit.
- preview-strip-tools: the pin toggle reads the current store state at
  click time instead of the render-time closure.
- Tests: pinned-reuse owner id, focused-tile-over-selection, and Browser
  re-own coverage — each mutation-verified (fails with the fix reverted).

42 targeted tests pass; renderer typecheck and eslint clean.
2026-10-01 03:46:08 -05:00
David Metcalfe
b3e896246f fix(desktop): scope preview tabs to the session that opened them
Preview tabs were window-global: a file/URL/artifact opened in one
conversation stayed visible above the composer in every other
conversation — including duplicates of the same file (file targets were
keyed by the raw URL, so file:// and plain-path entry points stacked).

Per the design proposed on #73890, tabs now belong to the session that
opened them (sessionId stamped at open), and the drawer renders only the
active session's tabs plus an explicit pinned set:

- pinned tabs render in every session (the deliberate cross-session
  workspace); a pin toggle rides on each preview tab's strip glyphs
- file tab ids are canonicalized (file:// stripped) so the same file
  dedupes across entry points; restored legacy rows are rekeyed and
  deduped on decode
- deleting a session drops its tabs (pins survive)
- legacy unscoped tabs migrate to pinned on load; ownerless tabs opened
  on a fresh draft are adopted into the session when one appears
- the terminal pane is untouched (process state stays global)

Tests: store unit tests for stamping/visibility/pin/prune/adoption/
canonicalization/migration, plus a mirror integration test proving the
drawer swaps panes on session switch. Full UI suite 561 files / 5529
tests green; typecheck and lint clean.

Partially addresses #73890 (the preview-tab scoping gap; the Artifacts
pane and focused-session fallback paths remain separate).
2026-10-01 03:46:08 -05:00
Brooklyn Nicholson
4e7403130e fix(agent): redact the failed-turn error card before it reaches the durable store
_failed_turn_display_metadata stored result["error"] and the error_surface
raw into the boundary row's display_metadata, which SessionDB persists
without redaction — a secret echoed by a provider or tool error reached the
'store' and 'export' sinks verbatim (test_secret_redaction_gateway
[tool_reads_env_file-store/export]). Force-redact every string in the
payload (nested dicts/lists included) at the producer, matching the
compressor's persistence boundary.
2026-10-01 03:27:39 -05:00
Brooklyn Nicholson
04d9c1c804 chore(desktop): regenerate _keys.desktop.json for the new stopped-reply key 2026-10-01 03:27:39 -05:00
Adolan
86ef1afc40 fix(desktop): keep failed-turn error cards after a restart 2026-10-01 03:27:39 -05:00
Adolan
159542e251 fix(desktop): mark replies the user stopped as "Response stopped" 2026-10-01 03:27:39 -05:00
ericmaddox
8d940d2cba fix(tools): preserve agent-browser session for CDP commands to resolve snapshot refs
Ensure `--session <name>` is passed alongside `--cdp <ws_url>` in `tools/browser_tool_session.py:_run_browser_command()`.

Previously, `--session` was omitted when `session_info["cdp_url"]` was set, causing `agent-browser` CLI invocations to run statelessly without connecting to a session daemon. Consequently, the accessibility ref map built during `browser_snapshot` was not retained across CLI calls, causing subsequent ref actions like `browser_click("@e4")` or `browser_type("@e4", text)` to treat `@e4` as an unescaped CSS selector and fail with Playwright parser errors.
2026-10-01 03:18:09 -05:00
Brooklyn Nicholson
587e673e2a chore(desktop): sort main.ts imports for perfectionist/sort-imports 2026-10-01 03:13:25 -05:00
Brooklyn Nicholson
9abeb829df fix(desktop): compose the YouTube embed Referer stamp into the default session listener
Chat embeds are plain iframes in the default session, and Electron allows a
single onBeforeSendHeaders listener per webRequest hook — installRemoteHeaderRules
registered its own listener on session.defaultSession, so a separate stamp there
was silently discarded. Compose the stamp into the shared listener (belt-and-braces
alongside the loopback renderer origin from the previous commit).

Co-authored-by: Konstantin Khlopkov <konstantin.khlopkov93@gmail.com>
2026-10-01 03:13:25 -05:00
Ugo Enyioha
d428df2a65 fix(desktop): restore YouTube embeds in the packaged app
Packaged builds load the renderer over `file:`, whose origin is opaque
("null"). YouTube rejects a null-origin embed, so every YouTube embed was
dead in a packaged app while working in dev (where the renderer is served
over http from the Vite dev server).

Serve the packaged renderer from a loopback HTTP origin instead, so embeds
get a real origin in packaged builds too. `rendererBaseUrl()` becomes the
single place that resolves the renderer location -- dev server, packaged
HTTP origin, or the file:// fallback -- and the main window, session
windows and the pet overlay all route through it.

Addresses both problems raised in review of #63492:

- Navigation guard: `will-navigate` previously admitted any `file:` URL when
  packaged, and the earlier revision of this PR compared URL strings against
  the base. Both are unsound. A string compare accepts
  `http://127.0.0.1:47891@attacker.example/` -- everything before the `@` is
  userinfo, so it shares our prefix while its real origin is
  attacker.example -- which would load attacker content into a window
  carrying the `hermesDesktop` preload bridge. Navigation is now checked by
  PARSED ORIGIN in a pure, unit-tested helper (`renderer-url.ts`), with a
  regression that asserts the old prefix check would have let the hostile URL
  through. `file:` bases compare by resolved path, since their origins are
  all "null".
- Port collision: the server bound a fixed 47891 and rejected on any bind
  error, while `whenReady` awaits it before `createWindow()` -- so an
  occupied port meant startup never reached window creation. It now falls
  back to an OS-assigned ephemeral port, covered by a test that binds a
  squatter on the requested port first. The fixed port is still preferred
  because the renderer's origin keys its localStorage, so a port that
  changed per launch would orphan persisted renderer state.

Session windows keep upstream's `rendererIndexPath` path for the file://
fallback: passing a file:// URL as `devServer` would have produced
`index.html/?win=secondary`.

Scoped to the embed fix only. The OAuth/boot-recovery changes that were
mixed into this branch are split out into a separate change.
2026-10-01 03:13:25 -05:00
Brooklyn Nicholson
e496ccc7d7 fix(i18n): render line breaks in localized /reasoning and /fast picker titles
15 locales double-escaped the newlines in gateway.fast.picker_title and
gateway.reasoning.picker_title, so YAML loaded a visible backslash-n and
Telegram showed literal \n text. Every catalog value is now checked for
an escaped newline.

The RPC session.history half of #104754 landed separately as 9d1eec0ce0.

Co-authored-by: Ahmad Al-Faqih <ahmadalfakeh973@gmail.com>
2026-10-01 02:55:59 -05:00
Brooklyn Nicholson
d0fefc69c3 fix(desktop): render code blocks plain when the highlighter chunk fails to load
A rejected import('./shiki-block') re-throws at render, past Suspense, to
the markdown-render boundary — whose HugeTextFallback collapsed the whole
reply into a raw-Markdown panel because one fence couldn't load its
highlighter (#95995: 229 such catches in one desktop.log). Catch the
chunk-load failure class in a block-local boundary that degrades the fence
to the plain pre-wrapping the app already shows while the chunk loads,
retry the dynamic import once (transient fetch failures), and warn once so
desktop.log stays diagnosable. Genuine render errors still re-throw and
reach markdown-render with their component stack.
2026-10-01 02:54:59 -05:00
Brooklyn Nicholson
f79faa667d fix(a11y): grow the icon-button hit area without changing its look
Keep the 20px visible box and add a transparent 2px ::before ring, so the
pointer target reaches 24px (WCAG 2.5.8) while rest and hover render
pixel-identical to before.
2026-10-01 02:47:52 -05:00
Gonzalo Blasco
22c87cae36 fix(a11y): correct WCAG 2.5.8 citation in ICON_BUTTON comment
The comment cited WCAG 2.5.5 Target Size (Enhanced) for the 24px floor,
but 2.5.5 is the 44px enhanced target. 24px meets 2.5.8 Target Size
(Minimum) (AA, WCAG 2.2). Fix the reference so future audits measure
against the right success criterion.
2026-10-01 02:47:52 -05:00
Gonzalo Blasco
7ba2477209 fix(a11y): grow shared icon buttons to 24px hit targets
ICON_BUTTON (pane headers, list-strip menus, MCP server actions) was a
20px target, below the 24px WCAG 2.5.5 pointer-target floor. Grow it to
size-6 and adjust the MCP config row's top-margin centering (mt-3 ->
mt-2.5) so the larger control stays vertically centered in the h-11 rows
that clone McpRow geometry.

Titlebar buttons are already 24px (TITLEBAR_CONTROL_SIZE) and the
session-row controls belong to finding 3 (#38072), so they are out of
scope here. The composer coding-row git-branch glyph stays small by
deliberate density.

Refs #38072
2026-10-01 02:47:52 -05:00
Brooklyn Nicholson
88486b222a fix(desktop): inset the zoom overlay fit so wide content doesn't touch the edges
Fit-on-open used the stage's full width, so a wide diagram landed about 4px
from the overlay edges while leaving hundreds of px empty above and below.
Fit into the stage minus a 32px side and 64px top/bottom inset. The vertical
inset also keeps fitted content clear of the floating toolbar, and the
symmetric insets keep the grid centering exact.
2026-10-01 02:47:30 -05:00
Brooklyn Nicholson
72cf71b93b test(desktop): query Zoomable trigger by role after title= removal
Main 0aa13a5e4b dropped the native title= from the Zoomable trigger
button (aria-label only) and moved the sibling zoomable.test.tsx to
getByRole, but zoomable-fit.test.tsx still queried getByTitle, so all
five fit tests failed on a rebased head. Same fix: query the accessible
button name.
2026-10-01 02:47:30 -05:00
Konstantin Khlopkov
6dfaab11d5 fix(desktop): fit zoomable overlay content on open
The overlay opened at identity scale, so diagrams wider than the
viewport rendered off-canvas and looked unrendered until the user
zoomed out manually. The fit-to-stage transform was also unreachable
interactively: the reporter’s 4758px ERD needs a ~0.21 scale, below
the old MIN_SCALE floor of 0.25.

- useZoomPan: compute a fit transform (never upscale) from stage vs
  content geometry, clamp it through the scale bounds, and expose
  setter refs so portal-mounted stages subscribe correctly
- overlay: fit on open and on later geometry changes while the view
  is still the fitted one; manual zoom/pan opts out until reset
- reset and smart-zoom now return to the fitted view
- MIN_SCALE 0.25 → 0.05 so fitted giant diagrams stay reachable by
  wheel and buttons
- normalizeSvgSize: neutralise mermaid’s inline pixel max-width,
  which beat the container width caps (max-w-full / 85vw) and forced
  wide diagrams to render at intrinsic width in the inline preview

Closes #110336
2026-10-01 02:47:30 -05:00
Brooklyn Nicholson
895b506ec9 chore(desktop): register clarify select-mode i18n keys
Rebase resolution: main split the ar and zh-hant catalogs into
per-domain files, so the two hint keys move into ar_assistant.ts and
zh-hant_assistant.ts. Regenerated locales/_keys.desktop.json.
2026-10-01 02:34:22 -05:00
Brooklyn Nicholson
691f5d0b54 fix(desktop): add clarify select-mode strings to de, es and fr
main gained de/es/fr locale catalogs after #100631 was drafted; add
the two new clarify hint keys there so every bundled locale carries
the full Translations shape.
2026-10-01 02:34:22 -05:00
686f6c61
7d997e8fc0 fix(desktop): add clarify select-mode strings to all locales
Locale files type the full clarify object, so the new hint keys
failed check:lint.

Fixes #100041
2026-10-01 02:34:22 -05:00
686f6c61
348cf33360 fix(desktop): label single- vs multi-select clarify cards
Desktop rendered the same clarify affordance for both modes, so users
had to trial-click to learn whether more than one answer was allowed.

Fixes #100041
2026-10-01 02:34:22 -05:00
Brooklyn Nicholson
ffc74885c5 fix(desktop): type window-menu against Electron's MenuItemConstructorOptions
Main's menu template is MenuItemConstructorOptions[], so the local
WindowMenuItem interface (role?: string, submenu: Array<{role: string}>)
no longer satisfies it and tsc rejects the pushed item. Derive the type
from MenuItemConstructorOptions instead: role comes from Electron's own
role union (which includes 'windowMenu'), and submenu items are full
MenuItemConstructorOptions so the click-only Close item fits.

Also carry main's #65457 behaviour into the extracted template: the
non-macOS Close item is click-only with registerAccelerator: false, not
role: 'close' (which would claim CommandOrControl+W before the
before-input-event run that routes terminal-focused Ctrl+W to word
erase). Test updated to match.

Co-authored-by: victorjmilagres <294917029+victorjmilagres@users.noreply.github.com>
2026-10-01 02:29:32 -05:00
Brooklyn Nicholson
1de897036d fix(desktop): sort window-menu import on current main
Re-apply of the import-order fix-up from the superseded branch: on today's
main the window-focus-policy import sits between the hunks, so the
perfectionist/sort-imports resolution is window-focus-policy →
window-menu → window-open-policy (eslint --fix verified clean).
2026-10-01 02:29:32 -05:00
victorjmilagres
6ce0b7496c fix(desktop): restore native macOS Window menu 2026-10-01 02:29:32 -05:00
Brooklyn Nicholson
330d9d6df9 fix(desktop): wrap wide markdown and fences instead of scrolling the transcript (#70451)
Wide markdown content forced horizontal panning: fenced code in tool
bodies, user bubbles and the bot group room grew sideways scrollbars (the
group room clipped with no scroll affordance at all), and huge plain-text
fallbacks never wrapped.

- markdown-text: min-w-0 on the container so the flex chain can't grow
  from its content; HugeTextFallback chunks soft-wrap
- compact-markdown (tool bodies): fenced code soft-wraps
- user-message-text: fences soft-wrap inside the bubble
- group-chat-view: room bodies soft-wrap pre instead of overflow-x-auto
  (the clip with no scroll affordance, #91706 surface)
- tables keep content-sized auto layout, so cells wrap at word
  boundaries; an unbreakable token scrolls inside the table card, like
  code cards do, instead of squeezing every column letter by letter

Co-authored-by: yuexiong <xiongyue_hnu@163.com>
Co-authored-by: FalconOrtiz <falcon.ortiz11@gmail.com>
Co-authored-by: networthexplained <networthexplained@users.noreply.github.com>
2026-10-01 02:20:13 -05:00
Brooklyn Nicholson
7cd77b4f14 fix(desktop): gate stale-fullscreen recovery on save-time provenance
The stale-fullscreen heuristic inferred staleness from geometry alone
(near-origin + >=90% of the work area), so a valid normal window the user
sized on purpose was rewritten to a centered 80% window with its position
dropped (review on window-state.ts:161).

Recovery no longer guesses:

- persistWindowState records boundsCapturedFullScreen at save time via
  the existing save path (sanitizeWindowState schema, backward
  compatible: absent on legacy snapshots, strict boolean like
  isMaximized).
- Flagged snapshots recover on the work area they cover, as before.
- Legacy unflagged snapshots recover only on an unambiguous match:
  bounds equal to a display's work area or full bounds within 2px,
  never the >=90% shape.

The reviewer's exact case (1824x988 at (0,0) on a 1920x1040 work area) is
now preserved verbatim; the motivating legacy 1920x1040 snapshot and an
exact full-display-bounds snapshot still recover.
2026-10-01 02:18:33 -05:00
Brooklyn Nicholson
6a23a46047 style(desktop): fix named-import order in main/translucency 2026-10-01 02:18:33 -05:00
Hukla
3e5317a1ff test(desktop): cover platform-scoped window recovery 2026-10-01 02:18:33 -05:00
Hukla
ca58edbf45 test(desktop): cover stale fullscreen window recovery 2026-10-01 02:18:33 -05:00
Hukla
c5d6b96f00 fix(desktop): recover stale fullscreen normal bounds 2026-10-01 02:18:33 -05:00
UniversePeak
c9f361441d fix(desktop): avoid inactive Windows backdrop 2026-10-01 02:18:33 -05:00
Brooklyn Nicholson
aeff051a18 style(desktop): blank-line nits in default-skin theme files 2026-10-01 02:06:11 -05:00
Brooklyn Nicholson
ac6079b329 test(desktop): fix default-skin tests against current main
The dynamic imports inside 'lets the provider resolve and keep default'
deadlock under vitest's module runner (hangs at both the old PR head
and on main); import ./context and ./user-themes statically like
context.test.tsx does. 'keys default-named skin CSS' still asserted the
pre-#76579 semantics (default never registered); now the classic
default registers as its own palette carrying its customCSS and the
separate built-in CSS store stays empty for it.
2026-10-01 02:06:11 -05:00