Commit Graph

6711 Commits

Author SHA1 Message Date
Hermes Agent
0f4a98f87c fix(desktop): pair repeated-caption attachment folds by paste ordinal (#122079)
Two pastes of the same captioned screenshot strip to identical tolerant
captions, so findIndex folded the second paste's error onto the FIRST paste's
settled reply, and the tail prompt match dropped the second paste's prompt as
already-represented by the first's committed row. Pair the n-th local
captioned paste with the n-th stored one; an unpaired ordinal keeps the
conservative preserve path.
2026-09-26 21:05:51 -05:00
Hermes Agent
820bb0b1af fix(desktop): keep the attachment-tolerance dedupe inside the identity gate (#122079)
The tolerant sameAttachmentTurn arm was an unparenthesised || operand, so a
rowId-bearing optimistic row could be swallowed by a committed row it provably
is not — pasting the same captioned screenshot twice dropped the second,
genuine turn. Gate both arms on !conflictingTranscriptIdentity, matching the
comment's invariant; the rowId-less paste keeps matching tolerantly.
2026-09-26 21:05:51 -05:00
Hermes Agent
056a960870 fix(desktop): never tolerance-match a captionless attachment turn (#122079 review)
Two captionless pasted-attachment turns strip to the same empty tolerant
caption, so sameAttachmentTurn matched ANY markers-only stored row against
ANY captionless local row and reconciliation folded one turn's error onto
another paste's reply. Empty now matches nothing: such rows take the
conservative preserve path. Regression test covers the two-paste shape.
2026-09-26 21:05:51 -05:00
Hermes Agent
da4a1ffd08 fix(desktop+gateway): surface continuation kind on compression-chain tips (#121148)
The branch-render guard landed earlier (78b133127d); what remained was
provenance: a projected continuation tip rendered as a brand-new
session. list_sessions_rich now stamps continuation_kind='compression'
on projected rows, StoredSessionRow carries it (contracts regenerated),
and the sidebar session row labels continuations with their lineage
root instead of a fresh-conversation affordance. The live tip stays
listable while naming its parent; sealed chain segments stay hidden.

Co-authored-by: wave-2 worker D <wave2d@hermes-triage>
2026-09-26 21:05:51 -05:00
Hermes Agent
4c1f111de3 fix(desktop): attachment-tolerant transcript reconciliation (#120978)
Pasted-attachment user rows carry no rowId until hydration, so
hydratedIdFor returned undefined and matchesTailUserInNext required
exact text+refs — the conservative append duplicated the turn. Match
on attachment identity (normalized refs + text) when the rowId is
absent, reusing the new attachment-turn helpers.
2026-09-26 21:05:51 -05:00
Hermes Agent
fc91608d85 fix(desktop): dedupe acknowledged prompt against all newly committed user rows (#121088)
preserveLocalPendingTurnMessages compared the optimistic prompt only
against the single newest authoritative user row. After an in-place
compaction handoff or preserved-task notice the newest user-role row is
the synthetic one, ordinal pairing shifts, and the committed copy of the
prompt is missed — so the optimistic user- row is re-appended below the
whole refreshed turn.

Compare against every newly committed durable user row (rowId-bearing,
non-[System: marker), identity-gated via conflictingTranscriptIdentity,
keeping the newest-row candidate as before for rowId-less positional
hydration windows. A genuinely unacknowledged repeat of an older
question survives: its committed twin predates the acknowledged
boundary and never enters the window.

Regression covers the handoff-row-before-copy trigger plus the
repeat-must-survive invariant.
2026-09-26 21:05:51 -05:00
Hermes Agent
e3841b1a0e fix(desktop): refuse backtick in URL artifact capture (#118415)
normalizeValue's trailing trim cannot save a URL captured THROUGH its
closing inline-code backtick when punctuation follows the delimiter
(`https://voice.qwickapps.com`,): the punctuation strip runs first and
leaves the backtick on the stored value/href, so the preview fails.
Exclude the backtick from URL_RE's char class — the capture now stops at
the delimiter itself, matching PATH_RE which already refuses it.

Builds on #118416 (cherry-picked in the previous commit).
2026-09-26 21:05:51 -05:00
Raaj Kumar
fdde5ee1ab fix(desktop): strip markdown delimiters from artifact paths 2026-09-26 21:05:51 -05:00
Hermes Agent
188261f4bc fix(desktop): regex-literal state in runtime-loader codeRanges (#120208)
codeRanges() had only comment/string/template states: a `/` in code was
checked for `//` and `/*` and otherwise treated as plain code, so the body
of a regex literal was lexed as code. A backtick inside a pattern (the
entities htmlReplacer character class, or a simple /`/) then pushed
'template' onto the stack and flipped template parity — every import
after it was classified as string text and left un-rewritten, so the
plugin failed with "Failed to resolve module specifier 'react'".
Regression from #117610.

Adopts open PR #120302 (credit: that author): a `/` that cannot follow a
value opens a regex-literal region — isRegexStart() is the standard
division-vs-regex heuristic (keyword/charclass/paren awareness, property
division stays code), regexEnd() honors escapes and [...] classes and
refuses to cross a newline, so a division that never closes on its line
stays plain code and its imports still rewrite. The pattern itself is
excluded from code ranges: import-looking text inside it is neither
matched nor rewritten in place.

Regression coverage: backtick-in-regex + import after it; the entities
htmlReplacer line; divisions stay code (imports after real division
still rewrite); `from 'react'` inside a pattern is never rewritten.
2026-09-26 21:00:46 -05:00
Hermes Agent
3d9adc0846 fix(desktop): list base branches once and keep the caller-chosen worktree base (#119745)
The new-worktree base-branch picker re-listed branches in an endless loop
on a non-git folder — the mount effect re-fired whenever
(branches.length === 0 && !loading), which is exactly the state an
empty/failed listing leaves behind — and load() unconditionally called
onValueChange with the default branch, so the base the caller picked
("Branch off from <current>" in the coding row's kebab) was overwritten
the moment the list landed.

- One listing per repo: the load effect is keyed on repoPath only, and an
  empty list or a failed listing is a final answer, never a re-trigger.
  The effect cleanup ignores a list that settles after the picker moved
  to another repo (the dialog remounts per repo) or unmounted, so a stale
  response can neither paint the previous repo branches nor set the new
  repo base.
- The default branch (origin/HEAD, falling back to the first branch)
  fills only an EMPTY value, from the settled list — never from inside
  load() — so the caller base stands and a project switch cannot leak a
  stale default.
- The popover-open re-load guard is gone with load(): the mount listing
  already answered, and re-opening must not re-run the bridge.

Consolidates the two open candidate fixes (credit: jonpol01 #119746 —
once-per-repo effect, value-empty-only fallback, and the regression
tests; JoaoMarcos44 #119882 — the active-cleanup stale-response guard),
superseding both.
2026-09-26 21:00:46 -05:00
Hermes Agent
9fb2dd4c39 fix(acp): stamp ended_at on ACP sessions at adapter shutdown (#118216)
ACP v0.9 has no per-session destroy, so the adapter's stdio shutdown is
the session end — but nothing ever wrote it: session rows created with
source="acp" kept ended_at NULL forever, the ended-session guard in
hermes_state_maintenance (prune/archive share it) could never select
them, and the desktop recents list accumulated one auto-titled row per
editor wake.

- SessionManager.end_all_sessions(): best-effort end_session("acp_disconnect")
  for every live session; called from entry.py's finally so EOF, SIGINT and
  a crash all stamp the rows.
- _restore() reopens a row ended by a previous adapter process before
  resuming it — the same contract the TUI gateway's cold resume uses, so
  load/resume across editor restarts keeps working.
- Desktop sidebar: 'acp' joins SIDEBAR_EXCLUDED_SOURCES (recents) and
  LOCAL_SESSION_SOURCE_IDS (keeps it out of the messaging slice); the
  conversations live in the editor, not the app's recents.

The prune/archive "open session(s) also match these filters" warning the
issue asks for already exists on main (count_open_prune_matches in
_cmd_prune_or_archive).
2026-09-26 21:00:46 -05:00
Hermes Agent
c488b42a82 fix(desktop): show recently failed delegations in the Agents panel and status bar
subagent.list now carries durable failed async delegations. Reconcile them
into terminal failed rows so a failure stays visible after its child ends or
a renderer reload empties the live roster. A live row for the same task wins,
and a row a turn already retired stays gone. The Agents summary tints the
"N failed" count and drops the duplicate error glyph on stream lines.
2026-09-26 20:40:40 -05:00
Hermes Agent
a30bd337e5 fix(tui-gateway): report recently failed async delegations in subagent.list 2026-09-26 20:40:40 -05:00
Hermes Agent
66447b4e9f fix(desktop): share the failed-call predicate for answer-only rows
The answer-only gate re-derived the failure check the run summary and
skill activity already use. Move it to fallback-model/format.ts as
toolCallFailed and use it in all three places; the gate adds only the
non-zero exit_code rule that mirrors the gateway's _tool_result_needs_user.

The answer-only tests now drive tool.complete through the real message
stream instead of upsertToolPart, and the success: false case uses a
non-card tool so it can actually fail.
2026-09-26 20:39:52 -05:00
Hermes Agent
e0a5c07109 fix(tui): preserve failures in answer-only mode 2026-09-26 20:39:52 -05:00
brooklyn!
5f32e201c9 fix(tui): hide reasoning and tool chrome when show_reasoning is off
display.show_reasoning false is answer-only. The gateway stops emitting
reasoning deltas, completed reasoning blocks, and non-essential tool
chrome without requiring reasoning_effort none. Clarify, approvals,
failed calls, and provider wait notices still leave the gateway.
Desktop drops the run scaffold and process rows under the same flag.

Refs #85110
2026-09-26 20:39:52 -05:00
Hermes Agent
70c588404b test(desktop): seed the readiness parser fixture in the Linux after-pack toolchain suite
afterPack now asserts the packaged backend-readiness matcher before any
platform work (#60772). The Linux toolchain fixture never packed an
app.asar, so the guard aborted with 'Missing packaged app.asar' before
the payload-link repair it exists to exercise. Seed the same
unpacked-mirror fixture after-pack.test.mjs uses.
2026-09-26 20:35:05 -05:00
Hermes Agent
496367f011 test(desktop): fail the pack when the bundled readiness parser goes stale
afterPack now verifies the packaged dist/electron-main.mjs still carries
the dual-token readiness matcher before any platform work, turning
source/packaged-artifact skew (#60772) into a build failure instead of a
user-side boot loop.

Co-authored-by: embwl0x <embwl0x@users.noreply.github.com>
2026-09-26 20:35:05 -05:00
Hermes Agent
5ef8d96554 fix(bootstrap-installer): resolve linux-arm64-unpacked desktop builds (#94703)
resolve_hermes_desktop_exe and the update lock probe only knew the x64
linux-unpacked dir, so on ARM64 Linux the bootstrap installer could not
find the rebuilt app to relaunch and did not wait for the running app's
app.asar before an update. Add linux-arm64-unpacked to both lists.
2026-09-26 20:29:47 -05:00
Brooklyn Nicholson
723c50233b fix(desktop): link-title pipeline refuses non-http(s) input before loadURL (#93893)
The hermes:fetchLinkTitle IPC handed whatever string the renderer sent
straight to the title tiers: canonicalTitleCacheKey's catch block
returned the RAW value (so leaked @url: markup became a loadable-looking
cache key), and the value then reached curl and the hidden title
window's loadURL(), surfacing as repeating
`Failed to load URL: @url:… ERR_NAME_NOT_RESOLVED` console noise on
printf-style code snippets.

Two layers, both in a new pure electron/link-title-url.ts module so the
admission rule is testable without Electron: fetchLinkTitle bails on any
input that does not parse as an absolute http(s) URL, and the cache-key
builder collapses unparseable input to '' instead of the raw string.

Co-authored-by: beplee <beplee@example.com>
2026-09-26 20:29:47 -05:00
Brooklyn Nicholson
e0eb93a168 fix(desktop): profile Cmd+1-9 always switch profiles; tab slots get their own action (#92569)
The profile.switch.N handler dispatched tab-first (activateTreeTabSlot
before switchProfileToSlot), so open session tabs silently hijacked the
chords — and because the tab dispatch lived INSIDE the profile action's
handler, rebinding the chord could not change the semantics.

Make profile.switch.N unconditional. Positional tab switching moves to
new view.tabSlot.N actions (shipped unbound — assignable in the shortcuts
panel), registered before the profile switchers in KEYBIND_ACTIONS so a
user rebind wins the combo-index race (first action to claim a combo
wins). Labels added for en + the complete locales (fr/de/es); overlay
locales fall back to English.

Co-authored-by: DavidMetcalfe <davidmetcalfe@example.com>
2026-09-26 20:29:47 -05:00
Brooklyn Nicholson
08453d7800 fix(desktop): inline math closed after an escaped backslash stays shielded (#92371)
MATH_SPAN_SPLIT_RE guarded the inline branch's closing dollar with a
one-character lookbehind. A backslash RUN defeats it: in a span whose body
ends with an escaped backslash (a literal backslash, valid TeX), the final
dollar really closes the span, but the lookbehind saw a backslash and
refused — the span went unshielded and the prose citation-marker rewrite
ate the [2]-style index inside it. Drop the closing lookbehind: the inline
body alternation consumes escape pairs atomically, so any dollar the
engine reaches after the body is by construction unescaped. The display
branch keeps its guard (its lazy [\s\S]* body does not step over escape
pairs).

Co-authored-by: DavidMetcalfe <davidmetcalfe@example.com>
2026-09-26 20:29:47 -05:00
Brooklyn Nicholson
64d38a1790 fix(desktop): skill pill stops re-firing on slash commands mid-message (#91626)
The suggestion provider only stood down when the draft STARTED with '/',
but the whole-word scan matched skill names inside mid/end-of-message
slash tokens ("please run /github-auth on this" re-offered github-auth).
Strip whitespace-bounded /<token> slash commands from the haystack before
scanning — whitespace-bounded on purpose so URL fragments (/api/v1 in
https://example.com/api/v1) are preserved. Real prose mentions elsewhere
in the draft still hit.

Co-authored-by: iacker <iacker@example.com>
2026-09-26 20:29:47 -05:00
Brooklyn Nicholson
e634321527 fix(desktop): keybind normalization survives a missing KeyboardEvent.code (#91611)
baseKeyFromCode assumed event.code is always a string; synthetic and IME
keydowns can arrive without one, and code.startsWith threw a TypeError
inside the packaged renderer's keybind listener (crash logs in the issue).
Guard the input: a non-string or empty code yields no physical-key
fallback, so comboFromEvent still resolves via event.key when that is
valid and returns null otherwise.

Co-authored-by: wodesiku <wodesiku@example.com>
2026-09-26 20:29:47 -05:00
Brooklyn Nicholson
15c775aa22 fix(desktop): sidebar search shows the real session title, not the matched snippet (#91068)
The backend's session-search payload already carries the real session title
on every hit (web_routers/sessions.py add_lineage_result enriches each
result via get_session_rich_row), but the sidebar's search mapper dropped
it (title: null) and sessionTitle() fell back to the preview — the FTS
snippet of the matched message — painting rows with raw message content
(tool JSON, shell commands) as the session name.

Map result.title onto the synthesized row (trimmed; untitled sessions keep
the snippet fallback) and declare the field on SessionSearchResult.

Co-authored-by: DavidMetcalfe <davidmetcalfe@example.com>
2026-09-26 20:29:47 -05:00
Hermes Agent
41cd3117ae fix(desktop): keep the sidebar alive for messaging-only and cron-only profiles
showSessionSections gated the whole session area — search, sessions,
messaging platforms, cron jobs — on normal-session visibility. A profile
whose only sessions are messaging threads, or that only has scheduled
jobs, collapsed the sidebar to the 'No sessions yet' blank state, hiding
its Telegram/Slack conversations and jobs until a normal session was
created (the messaging half of #63593).

Messaging sessions and cron jobs are already profile-scoped upstream
(sidebarProfileForScope / filterSessionsByProfileScope), so the sections
rendered inside the area are the active profile's own — no state is shared
between profiles.

Fixes #63593
2026-09-26 20:10:17 -05:00
finn763
ca16be564d fix(desktop): guard print crash Closes #101880 2026-09-26 20:00:59 -05:00
Austin Pickett
39aaa5c326 fix(desktop): expose APPROVAL_RESPOND_TIMEOUT_MS via the plugin SDK
Plugins may only import @hermes/plugin-sdk (no-restricted-imports); the
hermes-bots group-approval path pulled the constant from @hermes/shared and
failed check:lint. Re-export it from the SDK and import it there; the
plugin-sdk test mock gains the export.
2026-09-26 20:58:31 -04:00
Austin Pickett
aff4034f91 fix(desktop): ride the backend approval window for approval.respond
The client rejected its own approval.respond RPC after the generic request
timeout (120s shared default, 30s desktop) while the backend still waits the
full approvals.timeout (300s) for the user: answering later surfaced a false
"request timed out" over an approval the backend then applied anyway.

approval.respond now carries APPROVAL_RESPOND_TIMEOUT_MS (300s, the backend
default); ambientRequestFor forwards the optional deadline so session-routed
approval RPCs can raise their timeout; the hermes-bots group-approval path
rides the same deadline.

Fixes #60654
2026-09-26 20:58:31 -04:00
Austin Pickett
3e68e6ba1a test(desktop): advance fake timers by BACKEND_BOOT_WAIT_TIMEOUT_MS, not a hardcoded 45s
The boot-budget tests hardcoded the old 45s (and a 60s cushion) budget, so
raising BACKEND_BOOT_WAIT_TIMEOUT_MS to 180s made them time out or assert
before the deadline elapsed. Drive them from the constant so the next
budget change moves both together.
2026-09-26 20:54:47 -04:00
Austin Pickett
895ef9b839 fix(desktop): raise backend readiness deadline 45s to 180s
A cold backend boot (plugin discovery + route mounting at web_server
import time) takes 45-60s on slower hardware, so the 45s readiness
deadline made first-boot readiness a coin flip. Every lost race tore
down a healthy-but-slow backend and re-drove boot, cascading into
minutes of 'not connected' and orphaned python processes.

Raise both ends of the paired budget in lockstep: the main-process
readiness poll (DEFAULT_BACKEND_READY_TIMEOUT_MS) and the renderer
cold-boot wait that mirrors it (BACKEND_BOOT_WAIT_TIMEOUT_MS). The
poll returns the moment the backend responds, so fast machines see
no change.

Fixes #63454

Based on #63456 by @frohsinnllc

Co-authored-by: frohsinnllc <frohsinnllc@users.noreply.github.com>
2026-09-26 20:54:47 -04:00
Austin Pickett
84f87a18c0 fix(desktop): scope the session-cookie mirror by partition and gate the 401 replay
Review follow-ups on #123008 (andrexibiza):

- P1: key the in-memory mirror by the resolved OAuth partition (the same
  owner resolveOauthPartition picks for the jar) + origin, so two
  same-origin sub-path gateways on separate jars can never see each
  other's credentials through the explicit Cookie header. Cookies keep
  their effective Path (RFC 6265 path-match on attach) and an expiring
  Set-Cookie (Max-Age<=0 / past Expires / empty value) deletes the entry.
  Tests: A→B→A stays separate, a B-only 401 clears only B, path scope,
  deletion.
- P2: the silent re-login + single retry now runs only when
  shouldReplayAfterCookie401 holds — the 401 body is the dashboard auth
  gate's structured pre-handler refusal ({error: unauthenticated |
  session_expired, reason}) AND the operation is idempotent (GET/HEAD) or
  vouched replay-safe by the caller (replayOn401, set by the WS-ticket
  mint). Application-level 401s and arbitrary mutations keep the
  no-replay rule of requestWithOauthFallback.
- lint: perfectionist/sort-imports for the new module import.
2026-09-26 20:47:10 -04:00
austinpickett
adc3678150 fix(desktop): mirror remote session cookies in memory and retry once on 401
The persist:hermes-remote-oauth partition family drops its hermes_session*
cookies in the field (Windows %3A profile folders, lazy hydration, flush
races), and electronNet's useSessionCookies intermittently omits the cookie
entirely → every authed REST call and WS-ticket mint 401s as no_cookie right
after a successful sign-in (#61457).

- New remote-session-cookies.ts: process-lifetime per-origin Set-Cookie
  mirror (memory only, never persisted).
- The login window snapshots the fresh jar into the mirror on success; every
  authed REST response records its Set-Cookie headers.
- fetchJsonViaOauthSession attaches the mirror as an explicit Cookie header
  (an explicit header bypasses the network stack's jar lookup) unless the
  caller supplied its own Cookie.
- On a 401 the mirror is dropped for the origin, one silent re-login is
  forced, and the request is retried once (safe: a 401 means the server
  rejected the request before executing it).

Fixes #61457
2026-09-26 20:47:10 -04:00
Austin Pickett
fc9349082f fix(desktop): type scanWindowsProcesses rows as strings so tsc accepts both call sites 2026-09-26 20:42:11 -04:00
Austin Pickett
3aa0ac5e0d fix(desktop): kill external autostart venv holders before update hand-off
The Windows update/uninstall hand-off aborted with 'venv shim still
locked' whenever an autostart Hermes process held the venv: the gateway
Startup item and the dashboard Scheduled Task are launched outside the
desktop app, so releaseBackendLock() never saw them and every hand-off
failed after the 15s gate.

- new isExternalVenvHolder() selector in venv-holder-select.ts: exe must
  live under <venv>\Scripts\ AND be unambiguously a Hermes program
  (hermes.exe shim, python -m hermes_cli, python -m hermes) — far
  narrower than the install-root substring matching that sank #62445,
  so unrelated processes mentioning the install root or borrowing the
  venv interpreter are never tree-killed
- releaseBackendLock() kills those holders before the release gate and
  re-scans inside every gate pass, so a respawning autostart holder
  loses the race; the shim-lock probe remains the backstop abort for
  non-Hermes holders
- the Win32_Process scan is shared with the hindsight-daemon sweep

Based on #62445 by @LeonSGP43 (approach credited; matching narrowed).

Fixes #62311
2026-09-26 20:42:11 -04:00
Brooklyn Nicholson
9bb2ea1b5c fix(desktop): make the Floating Composer setting findable as a dock lock
Settings search matches lock, peel and pop out, and the description says what off means now that off is the default.

Co-authored-by: networthexplained <300128320+networthexplained@users.noreply.github.com>
2026-09-26 19:00:41 -05:00
Brooklyn Nicholson
4b1da97299 fix(desktop): require a deliberate drag to peel the composer out
Raise the docked peel threshold from 16px to 48px so a brush of the grab ring no longer undocks it.

Co-authored-by: networthexplained <300128320+networthexplained@users.noreply.github.com>
2026-09-26 19:00:41 -05:00
Brooklyn Nicholson
e928574ab3 fix(desktop): lock the docked composer to the dock by default
Drag-to-float gestures now default off on a fresh install. A composer that was already floating before the flip keeps its gestures, and a stored choice wins either way.

Co-authored-by: networthexplained <300128320+networthexplained@users.noreply.github.com>
2026-09-26 19:00:41 -05:00
Brooklyn Nicholson
50873d2154 fix: picker/input cluster — vendor casing, variant tags, submenu keyboard path, stale moa pick, CJK search star
Six fixes for the wave-7 picker/input cluster:

DeepSeek -> "Deepseek") and left the gemini- branch's words lowercase
("Gemini 2.5 pro"). Vendor casing + parameter counts now applied after
title-case (GLM, DeepSeek, MiniMax, OpenAI, ERNIE, MiMo, BGE, VL, IT,
FP8, AI; 8b -> 8B, a3b -> A3B), and the gemini branch title-cases like
every other branch.

and was unreachable by keyboard (rows are highlighted, never DOM-focused,
so Radix's own ArrowRight never fires). The chevron is now visible on
every model row and ArrowRight (caret parked at query end) hands focus
to the highlighted trigger and opens its sub; ArrowLeft returns focus
to the search field. Consolidates #86968 + #104532.

-fast/-thinking/-preview ids to the base label. The tag now rides the
display name on every surface, and formatModelPillLabel no longer
doubles Fast for a -fast variant id.

all MoA presets were disabled: manual picks are sticky by design
(d595e636c8), but the virtual moa provider's catalog row disappears
entirely once no preset is enabled, so that one absence is
authoritative (moaPickRemoved) and the pick reseeds from the profile
default. Narrow moa-only exception — no general catalog diff.

token (nimb -> nimb*); none of the CJK routes can honour it (bigram and
trigram routes quote tokens so the star matches literally; LIKE has no
star wildcard at all), so CJK searches returned zero results. The star
is now stripped per token on the CJK path only.

measure() effect deps (stale measurements after toggling Inbox style)
and the card estimate undershot the four-line/wrapped-title worst case
(74px), painting rows over their neighbours on cold start. Card
estimate raised to the worst-case-covering 96px and the deps fixed.
2026-09-26 18:16:44 -05:00
hermes-seaeye[bot]
13f6b46daa fmt(js): npm run fix on merge (#124529)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-09-26 23:12:07 +00:00
Hermes Agent
b24b8149dd fix(icons): give the dev Dock its own mac-grid png
Linux uses apple-touch-icon.png as the window icon and Nix requires it to
match the full-bleed launcher icon, so keep it full-bleed and point the
dev-only app.dock.setIcon at assets/icon-mac.png instead.
2026-09-26 18:10:54 -05:00
Hermes Agent
c8094dc399 fix(icons): put the dev Dock icon on the mac grid and enlarge the mac art
Dev runs replace the Dock icon with public/apple-touch-icon.png, which the
generator rendered full-bleed, so it drew ~24% larger than its Dock
neighbors. Render it from the mac-grid master like the icns targets.

On the 824 grid the plate matches peers, but the girl inside a white tile
with a ring read small; scale her 1.12x about the plate center for every
mac target.
2026-09-26 18:10:54 -05:00
Hermes Agent
f4795c922c fix(desktop): tell the user when a clarify request never reached the app
A pending clarify card waits on its gateway clarify.request. When that
frame is lost the card sat as a disabled preview until the 300s timeout
with no hint of what went wrong.

After a 4s grace the card now asks the owner socket for
session.events.since, which re-delivers the session's open requests, so a
request the backend still holds parks and the card goes live. If nothing
turns up, single and batch cards show an inline notice (all locales)
pointing at Stop, and drop the dead Skip/Continue actions.
2026-09-26 18:00:36 -05:00
liuhao1024
00c8e5ca48 test(desktop): cover the one-entry batch clarify shape end to end
Since the questions[]-only schema (#95907) every single question is a
one-entry batch on both the tool args and the gateway wire, yet no test
exercised that shape (called out in #98645). Lock the behavior down at
both layers:

- unit: a one-entry batch renders the batch card (not a blank/spinner
  single card) both with the wire already parked and when the request
  lands after the tool row, and answers with the qid-keyed lock
- e2e: a SINGLE_BATCH trigger drives the real chain (composer -> gateway
  -> agent -> clarify tool -> clarify.request -> renderer) through mount,
  pick, confirm, and settle for questions.length === 1

The e2e mock's trigger routing also learns to scope its has-tool-result
guard to the answering turn's own question text: the existing any-tool-
result check would false-positive once a second scripted clarify shares
the conversation history.

Adapted to main: the mock server now lives in tests-js/scripts, and a batch
confirm answers with clarify.lock.
2026-09-26 18:00:36 -05:00
Brooklyn Nicholson
fd9350a941 fix(desktop): sort the launch-profile import 2026-09-26 18:00:30 -05:00
brooklyn!
8684bf3ddc fix(desktop): forward --profile into the packaged desktop launch
Electron booted from active-profile.json and ignored argv. hermes
desktop and hermes -p <name> desktop never appended --profile, so
the packaged app kept the stored profile.

Parse both --profile spellings before startHermes, persist that
name, and append the same flag on the packaged launch. A missing
flag does not change the stored profile.
2026-09-26 18:00:30 -05:00
Hermes Agent
6d94896c78 chore: map contributor emails 2026-09-26 18:00:17 -05:00
Muhammed Emin Boydak
a723194c1e fix(desktop): reserve the window-controls band on the narrow sidebar overlay (#110033)
The narrow-viewport overlay for a collapsed zone is `absolute inset-y-0` —
it starts at the viewport's top edge with its tab strip (SESSIONS | BOTS) as
the first child, so on macOS the strip slides under the traffic lights.
Docked zones already reserve that band (TreeGroup: useWindowControlsOverlap
-> paddingTop plus an absolute [-webkit-app-region:drag] spacer; top-edge
zones use usePanelTitlebar), but the overlay used neither.

Reserve it the same way: measure the native controls rect against the
overlay element and pad the strip below it, keeping the band a window-drag
target via the drag-region spacer. The overlay's data attribute now carries
the revealed pane id (it was a constant empty string), which the regression
test uses as its selector.

Salvaged from PR #110034 by Muhammed Emin Boydak (the overlap hookup, the
paddingTop + drag spacer, and the 34px-reservation test), adapted to the
current file (NO_PANE_GROUP import, per-pane data attribute).

Fixes #110033.
2026-09-26 18:00:17 -05:00
Hermes Agent
a6dadb83ba fix(desktop): strip citeturn web-citation transport markers from prose (#120587)
Model output can arrive carrying Gemini-style grounding citation markers:
private-use delimiters U+E200/U+E201 wrap a `citeturn<n>search<m>` id list
(U+E202 separates ids) - e.g. `\uE200citeturn0search11\uE202turn2search0\uE201`.
Desktop had no rule for that shape (CITATION_MARKER_RE only strips bare
numeric `[n]` markers), so the private-use code points painted as replacement
glyphs - the reported "triple bars" - and the `turn…search…` ids rendered as
literal prose, wrapping across table cells and obscuring the answer.

Add CITATION_TRANSPORT_MARKER_RE and strip it in rewriteProseSegment, the
same shielded path the numeric marker rule rides: inline code and math spans
split out first, so `$\sqrt[3]{8}$` and quoted marker text are untouched, and
the bare no-delimiter alternative only fires on the `cite` head so plain prose
and stray private-use characters (icon fonts) are left alone. A marker that
cannot be resolved to a source is dropped, never invented into a link -
matching the reporter's own expectation. Mid-stream flushes (closing U+E201
not yet arrived) are covered by the optional-tail shape.

Backend-side emission (which search provider leaks the markers into model
text) remains unisolated, as the report itself notes; the display-layer strip
is justified regardless.

Fixes #120587. No external PR existed (the catalog's linked PR #120592 is a
dead reference).
2026-09-26 18:00:17 -05:00
Phantomthedog
d38d2a8f0a fix(desktop): keep a bare URL's full text visible in chat markdown (#121007)
MarkdownLink nulled fallbackLabel whenever the link text matched the target
URL — exactly the bare-autolink case — so PrettyLink fell through to
urlSlugTitleLabel and rendered a host-only label (`ncpssd.org` for
https://www.ncpssd.org/), with the address readable only via hover/inspect.
The user could not read an address sent in chat.

The link's own text is always a legitimate fallback label; pass it through.
Labeled links are unchanged: their authored label already wins display, and
that shape (a label hiding the address) is PrettyLink's documented contract,
not a bug.

Salvaged from PR #38213 by Phantomthedog (the fallbackLabel change and the
localhost/example.com render tests), reworked to keep the change scoped to
the bare-autolink shape and reshaped into an end-to-end
MarkdownTextContent test alongside the existing session/filelinks suites.
2026-09-26 18:00:17 -05:00