Bumps the pinned sha to the v1.1.2 release and version to 1.1.2.
v1.1.2 fixes the codex-telegram-notify attribution/link and suppresses
duplicate lifecycle notifications for Telegram-origin sessions, while
preserving existing subagent/smart-approval filtering.
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.
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
Windows has no POSIX parent-death supervisor/killpg safety net, so an
ungraceful exit of the hermes process left every stdio MCP child tree
(npx.cmd -> node.exe) running as orphans with ParentId=null, piling up
across session restarts.
- _run_stdio now attaches the process to a KILL_ON_JOB_CLOSE job object
before spawning stdio children (self-guarded no-op off Windows), so the
whole child tree dies with the parent at the kernel level.
- Windows reaps kill the process tree (direct child + descendants) in the
lifecycle orphan sweep and the spawn-ledger startup sweep, where there
is no pgid to group-kill.
Fixes#61059
Standalone MemoryProvider plugin for CortexLayer (github.com/Cortex-Layer/cortexlayer-hermes-plugin),
a linked-page long-term memory service with link-expansion retrieval. Platform mode only
(hosted api.cortexlayer.net, API-key authenticated) — implements the required lifecycle, all
5 memory tools (memory_add/retrieve/update/delete/relink), and the on_session_end /
on_pre_compress hooks (checkpoint API v2).
Owner submission (I maintain the plugin repo). Manually verified end-to-end against a real
Hermes instance before this submission (multi-turn conversation, confirmed sync_turn/prefetch
working with the profile's built-in memory disabled to remove ambiguity).
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.
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>
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.
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
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
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>
A volume that already owns /workspace skipped the configured working
directory, so tools treated that host path as unmounted. Bind it at a
second mount, or point tools at the volume that already has it, for any
drive path.
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.
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.
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.
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.
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.
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.
Desktop paste/file attachments land in Hermes-managed staging dirs on the
GATEWAY (composer-pastes/ for large text pastes, attachments/ for dropped
files), but on the Remote SSH topology the workspace root (TERMINAL_CWD) is a
path on the SSH HOST - the two filesystems are fully disjoint, as the issue
thread confirms. Two gaps combined to reject every staged attachment with
"path is outside the allowed workspace":
- _resolve_path admitted only allowed_root + composer-paste roots, so a
gateway-staged attachments/ path was refused outright. Admit the
_CACHE_DIRS staging roots (attachments/, images/, cache/*, composer-pastes/)
via a helper that asks get_cache_directory_mounts - the gateway's OWN
payload is never a workspace escape, and the path-traversal and
credential-deny guards in _ensure_reference_path_allowed still run after.
Anything else outside the workspace stays blocked.
- composer-pastes/ was missing from _CACHE_DIRS, so its bytes never reached
the remote: ssh/daytona/vercel_sandbox sync via iter_sync_files ->
iter_cache_files, and to_agent_visible_cache_path only translates mounted
dirs - a paste attached on a fresh session dangled on the remote host.
Tests cover the disjoint-filesystem SSH topology end-to-end (text inlines,
binary renders the synced ~/.hermes path), the still-refused stranger path,
local-backend unchanged, and the composer-pastes mount+sync enumeration.
Consolidates PR #110387 by Finn763 (the _agent_staged_path guard widening and
the SSH-topology tests, adapted to the current _ensure_reference_path_allowed
ordering) with PR #103412 by ericmaddox (whose mapping insight is subsumed by
the _CACHE_DIRS entry, which fixes both the sync and the translation).
Co-authored-by: ericmaddox <ericmaddox@users.noreply.github.com>
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.
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).
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.
A popped-out Browser window is a fresh renderer: no in-memory atoms cross
the window boundary, and no session ever pushes a rail scope into it (the
controller skips session/preview watching there), so its scoped previewTabs
view started empty and PreviewTilePane rendered null — the shell spawned but
never painted, matching the reported black window. Root cause is the
per-profile rail scoping from c996d1c088, not the GHSA window-open policy
the reporter suspected: the window/IPC handoff itself is fine.
Three coordinated moves in the store:
- adoptPersistedBrowserTab now reads every profile bucket (plus the
pre-scoping single-array shape; the old decode returned [] for bucketed
storage, so even the sibling URL sync was dead) and, when the tab is not
in this renderer's view, re-homes the view onto the OWNING bucket instead
of splicing the tab into 'default' — a splice would duplicate the popped
tab into the primary profile's rail. When the tab IS present (the docked
mirror on pop-out close), it adopts the newer URL/label as before.
- PreviewTilePane calls that adoption from a browser window when its tab is
missing from the view.
- The persist subscriber no longer echoes module-init emissions back over
storage: nanostores fires subscribe immediately, so every renderer used to
clobber its un-adopted record (a legacy single-array store was wiped
before pendingLegacyTabs could adopt it; a 'default'-only bucket store was
removed the same way), and the view is now seeded from the renderer's own
bucket — which also restores the primary profile's rail at boot, since
setPreviewScope('default') early-returns on the initial viewKey.
Salvaged from PR #120110 by finn763 (diagnosis + adoption mechanism +
creation-emission guard, re-homed onto the owner bucket to avoid the
duplicate-bucket write.
EOF
)
The unknown-id shortcut gave any unsaved draft a private OAuth jar, so a
pre-save cloud sign-in wrote persist:hermes-remote-oauth-conn-<id> while the
saved cloud connection reads the shared persist:hermes-remote-oauth. Send the
draft's kind/authMode with the request and only grant a private jar to remote
OAuth drafts; everything else falls back to the legacy jar.