Merge 27df3b8847 brought back the hermes-agent[hindsight] extra
(hindsight-client==0.6.1) that 73c598e319 removed. The catalog
Hindsight plugin now requires hindsight-client>=0.10.1,<1, so any
workspace containing it fails `uv lock`. Remove the extra, its
exclude-newer entry and its legacy-takeover mapping, and regenerate
uv.lock.
The workspace-member rename from the original PR is dropped here;
#122098 landed that half.
Salvaged from #122092.
Electron denies every target=_blank window, so a web link in a previewed
.md file looked clickable and did nothing; a #fragment link rode the
HashRouter and changed the app route instead of scrolling the note; a
relative link to a sibling note never opened it.
Links now take the same doors chat links take: web links render through
ExternalLink (in-app browser, Cmd/Ctrl for native), file links are wrapped
in the existing #preview/ hash before Streamdown's hardener sees them and
resolved against the note's directory by normalizeOrLocalPreviewTarget,
and headings get GitHub-style ids from a rehype plugin so a TOC click
scrolls within the pane. Slugs and lookups are NFC-normalized so a TOC
written on another editor still finds its heading.
Fixes#81055
Co-authored-by: xxxigm <tuancanhnguyen706@gmail.com>
Review suggestion from #122098: spell out on the condition line that
uv package mode produces real build metadata, so such members keep
their declared name.
record() looked up stamp['identity']['windowsExecutableName'], a key no
stamp writer emits, so every Windows record on a runner resolved no
executable and failed. The built package's Application/@Executable
names the exe; match it in the unpacked dir only, which also skips
before-pack's .bak rollback copy.
uv identifies a workspace member by its declared project name, so the
same plugin enabled in two profiles declares one name twice and the
dependency sync fails with 'Two workspace members are both named ...'.
Metadata-only members (no build backend) now carry the unique member
key in their name, exactly like manifest-only members already do. A
buildable member keeps the name it declares, since uv verifies it
against the package metadata its backend produces.
The 30s drain opened a throwaway gateway socket even when Bot Mode was off or the route had no outbox work. With the push door, only a route that signaled bot_relay.outbox.pending is drained. Older shells without that door still poll. The background-scope reset from #119836 is unchanged.
Importing a script from `node -e` leaves process.argv[1] undefined, and
pathToFileURL(undefined) throws on import. The bootstrap installer's
signing step imports batch-sign-binaries.mjs this way, and it crashed
when that pulled in sanitize-pe-signatures.mjs.
An authoritative still-pending clarify was answerable only after
getLatestSessionMessages returned. Publish the snapshot tool-call row
in the same needsInput view update, and keep provenance checks from
hiding that row.
The post-update relaunch refused a backend that had published a session token
and spawned another. Adopt the host rendezvous token when GET / withholds it.
The stale app.asar half is dropped: main now judges desktop freshness from the
compiler receipt written inside the packaged output (26c4e8b160), so a skipped
or failed rebuild no longer looks current.
A Telegram row that omits profile is owned by the backend that served
the messaging list. Keep a non-primary connection id, send a primary-pool
list through that profile door, and do not guess primary or ambient when
the list server was never recorded.
A dead or interrupted turn left the thinking spinner up, including after
a partial assistant payload. When that session stops producing events,
force-settle it and stamp the existing retryable error card instead of
leaving the spinner spinning.
Clicking a single-select choice leaves focus on the option button, and the
window keydown handler bailed for every button, so Enter never submitted the
staged answer. Exempt button[data-choice] and let activateActive handle Enter:
a staged single-select answer submits, a multi-select row toggles, and Continue
still confirms the set. Choice buttons do not call submitAnswer.
Fixes#92816
Co-authored-by: echohn <echohn@gmail.com>
The shared ['hermes-config-record'] slot survived a gateway switch, so a
settings save painted the previous machine's record and PUT it onto the
other config.yaml. Key that existing slot by $activeConnectionId.
Fixes#101640
Hiding inside the minimize handler wedges the Windows minimized flag, and
showInactive() restores a painted window that never takes input. Defer hide
until after that dispatch, skip a stale hide if the user already restored,
and activate with show()+focus() on Windows. Log main-process event-loop
stalls in desktop.log; renderer unresponsive handlers cannot see AppHangB1.
Reported in #119252.
Co-authored-by: finn763 <165816600+finn763@users.noreply.github.com>
Co-authored-by: KoNit-K <konit.block@protonmail.com>
Auto-speak keyed already-spoken on the assistant row id and ordinal.
Hydration rewrites the live id and tool-row folds move that ordinal, so
the same turn looked unspoken and was played again. A second
playSpeechText stopped the first clip. markSpoken also ran before a play
that never started, so the turn stayed silent.
Key the anchor on the user turn, which survives both. A later turn that
says the same thing is still spoken. Release the anchor when playback
never starts, and do not let a second start of the same turn stop the first.
An empty connection id is no longer rewritten to registry.primary, which dialed
another SSH host. A concrete remote-only profile, including default, is refused
on the forced-local branch instead of spawned. A profile open without an owner
route no longer stamps mode local when the live connection is remote.
A second Desktop window on the same stored session never saw the first
window's completed turn, and submitting from that stale transcript could
fork the session. Notify other windows to re-pull on turn completion, and
refuse composer and session-tile submit when the local transcript is behind
the authoritative latest page.
Co-authored-by: stantheman0128 <stanshih888@gmail.com>
Desktop speak-stream sent type=fallback whenever the provider had no
chunked PCM API. Edge is that case, so the client waited for the full
reply and POSTed it. Cut sentences with the existing sync TTS tool and
stream that PCM. Fallback stays the last resort when synthesis produces
no audio.
Refs #91997
A profile that finished work (or blocked on input, or is still running)
while another profile was selected showed no indicator on its rail square
or dropdown row — the session-level unread/attention layers
($sessionDotStateById, the persisted per-profile unread markers, the
backend row.unread watermark) all existed, but the profile rail
subscribed to none of them.
Add $profileDotStateByScope: a per-(connection, profile) rollup of the
shared session dot state, derived in three passes:
1. loaded chat/messaging rows claim their row-tagged scope
(stalled/background fold into working, like the sidebar's buckets;
cron rows stay excluded so a profile square is not a cron counter),
2. unlisted live runtimes claim the scope their socket proved
(runtimeSessionOwner; no proven owner claims nothing),
3. persisted unread markers with no loaded row claim their profile's
scope only when the owning gateway is unambiguous — two gateways
sharing a profile name leave the bucket unpainted rather than
guessing.
Priority: needs-input > working > unread, matching the session rank.
The busy->idle finish edge now passes the socket-proven owner profile
into markSessionUnreadFinished, so a background profile's finish can no
longer land in the ACTIVE profile's marker bucket (which would light
the wrong square). The reconnect parked-set becomes a Map so a later
confirm keeps the runtime id for that lookup.
The rail paints the rollup as a small dot on ProfileSquare, RestSquare,
ProfilePill and both dropdown rows, with every non-zero count in the
accessible name and tooltip ("writer, 1 unread session"). The active
profile's square stays clean — the workspace is homed there and its
rows are on screen in the sidebar below.
Attached images rendered at a 512px thumbnail in the live sent bubble and
click-to-zoom lightbox, only becoming sharp after a session reload rehydrated
the turn from disk. Route the in-flight bubble through the same DirectiveImage
path as a reloaded turn: a bounded thumbnail is painted inline (preserving the
deliberate anti-freeze cap) while the full-resolution file is handed to the
on-demand lightbox and download.
Fixes#93204
Review points:
1. Noisy shell stdout (curl -v, build logs) is kept from flooding the panel:
every prose candidate passes through looksLikeArtifact, which requires a
file/image extension or http(s)/data: scheme, and local file existence is
resolved via the media ladder (artifactImageSrc -> resolveMediaDisplaySrc
-> readFileDataUrl). Documented at the shell-output scan site.
2. Bare numeric array indices are dropped from the key path intentionally so
array-of-results payloads (e.g. outputs.0.output) match via their real
segments; noted that switching to exact-key matching would silently break
those shapes.
Terminal results were never scanned for artifact references: 'terminal'
is not matched by ARTIFACT_PRODUCER_TOOL_RE, and its stdout lives under
the bare 'output' key which STRONG_TOOL_ARTIFACT_KEY_RE does not list.
Script-generated figures (matplotlib, ffmpeg, pandoc, ...) never showed
up in the Artifacts panel unless the assistant remembered to re-emit a
MEDIA: tag in prose.
Treat terminal as an artifact producer: scan its free-text result
(MEDIA tags, markdown refs, URLs, absolute paths) and accept
'output'/'stdout' as scannable keys for that tool only. Non-terminal
tools keep their existing strict key gate.
Fixes#92220
Hermes-Setup has never had a CI build; every published copy was built by
hand. This workflow_dispatch lane builds the Windows x64 exe (signed via
the desktop MSIX's Azure Trusted Signing path, batch-sign-binaries.mjs)
and the macOS arm64 dmg (Developer ID signed, app + dmg notarized and
stapled) from any ref, and uploads them as run artifacts. Nothing is
published. No caches: no actions/cache or setup-node cache, and fresh
npm/cargo/electron-builder cache dirs.
A source checkout without hermes_cli/source_check.py predates release
channels, so the only line it can be on is git. The desktop treated the
missing probe as unsupported and parked the user on a manual
`hermes update --help` card ("This checkout predates desktop
source-channel checks"), so an older non-bundled install could never
update itself from the app.
Report such a checkout as tracking main with an update available and let
apply take the normal git handoff. That update brings in the probe, so
later checks resolve normally. A probe that exists but fails still throws.
User installs failed with 'resvg-py is missing' because the web/desktop
source builds rendered icons on whatever python was on PATH. The default
brand outputs are now committed; source_build, apps/desktop build.mjs and
the npm/docusaurus pre-hooks consume them directly. Flavored release
bundles (canary/commit) still render into their own product dir.
icons-freshness-check now regenerates and fails on any byte diff.
A non-elevated Windows ARM64 install threw "run setup-hermes.ps1 in an
Administrator PowerShell" whenever Visual Studio ARM64 C++/Clang were
missing. Interactive runs now launch the signed VS installer through a
UAC prompt; CI, ssh and scheduled runs keep the explicit instruction.
A bare `pm install` fetched agent-browser (~200 MB with Chromium) before
the venv sync, so a Windows ARM64 machine without build tools downloaded
it and then failed preparing the native build. Install defaults only once
the venv (and its build tools) succeeded.
The e2e lane at 4 workers still starved the PTY turn and serve-SIGTERM
deadlines intermittently; drop it to 2. The upgrade lane had no cap and
ran cpu_count real-updater trees at once; cap it at 4.
activate() now tells check() whether to include the venv verdict; the
no-argument stand-in raised TypeError, which startup swallows, so the
check was never counted.
activate(allow_incomplete=True) discarded the venv verdict but still
computed it, and venv_is_current reads plugin selection through the
application config reader (ruamel). The update child runs the bare
bootstrap interpreter, so `hermes update` failed with "No module named
'ruamel'" once it published tools before the sync.
prepare_launch finishes an interrupted update, or a hand-run git pull, by
syncing the venv at startup. It skipped the required-tool step that
`hermes update` now runs first, so a bumped ripgrep/ffmpeg/python pin stayed
uninstalled and activation warned on every start.
A normal `hermes update` only re-synced the venv. The sync pulls uv/python
in through its own dependency, but a ripgrep/ffmpeg/node/npm pin bump in
pm/lock.json was never installed, so PATH activation warned and skipped the
managed tool dirs on every CLI start and gateway boot. Only the takeover
route for historical releases ensured the tool roots.
Move the takeover's loop into pm.client.ensure_tools_for_sync() and call it
from both routes before the sync: update_completion._prepare (the CLI and
Desktop route, running from the new tree so the new lockfile applies) and
_update_takeover.prepare. It uses explicit=True like the takeover (an update
is an explicit user action) and a failed download fails the update.
post_update.step_provision_runtimes / MACHINE_STEPS stay: `python -m
hermes_cli.post_update --scope machine` and tests still reference them.
The ffmpeg docstring no longer claims that step re-ensures it.
The driver passed --skip-browser whenever the installer's help listed it.
That was for historical installers' own Playwright step; on a PM installer
the flag is now a real opt-out and would hide the default users get.