Commit Graph

28243 Commits

Author SHA1 Message Date
ehz0ah 71a8240170 docs(openviking): clarify remember extraction semantics 2026-09-01 14:11:55 +05:30
ehz0ah 7fef5c7898 fix(openviking): clarify remember submission status 2026-09-01 14:11:55 +05:30
ehz0ah 6446e19cb5 fix(openviking): route remember through session extraction 2026-09-01 14:11:55 +05:30
Teknium a1c25d393a feat(desktop): built-in optional-skills catalog in Capabilities → Skills with one-click install
The Skills tab now lists the entire official optional-skills catalog
(optional-skills/ shipped with the repo) below the installed skills.
Each catalog row has an Install button that routes through the standard
hub action pipeline; once the install finishes the row flips into the
installed list with the normal enabled/disabled toggle.

- backend: GET /api/skills/hub/official — OptionalSkillSource.list_local()
  scan (no network) + per-profile installed flags from the hub lock
- desktop: catalog section in SkillsView with search/scope integration,
  install-state spinners off $hubActions, and an OfficialSkillDetail pane
  (hub preview: frontmatter + full SKILL.md + Install)
- CapRow gains an optional action slot (button instead of the Switch)
- electron: route the new endpoint with the skills family (primary backend)
- i18n: officialCatalog/officialPill keys across en/ja/zh/zh-hant
2026-09-01 01:39:59 -07:00
hermes-seaeye[bot] 9366ba3afb fmt(js): npm run fix on merge (#100128)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-09-01 08:34:56 +00:00
Sergey0515 f85edd3d94 fix(desktop): stop plugin-launched gateways before the Windows release gate (#70337)
releaseBackendLock tree-kills only the Desktop's own backends
(backendConnectionState + backendPool). A messaging gateway launched by
the gateway-launcher desktop plugin via /api/gateway/start lives outside
those structures; on Windows its launcher (venv\Scripts\python.exe)
keeps the venv mandatory-locked, so the 15s release gate aborts the
hand-off BEFORE the venv-blocker scan — the scanner's pausable-gateway
exemption and the CLI updater's pause machinery never get their chance.

Delegate to `hermes gateway stop --all` (launcher + worker discovery
across every profile; gateway.pid records only the uv WORKER and
taskkill /T from the worker never reaches its parent). Per review, adds
the drain-semantics counterpart: every applyUpdates abort path
(lock-held, venv-blocked, probe-failed, updater-spawn-failed) restores
gateways via `gateway start --all`, so a failed update no longer
strands every profile's gateway stopped. Pure DI'd module + tests.

Salvaged from PR #76057 (issue #70337; overlap credit: #70477 by
@JonthanaHanh targeted the same symptom earlier via ZIP-dir preservation).
2026-09-01 01:30:09 -07:00
xy952666680 6b18224df5 fix(desktop): reap detached hindsight venv daemon before update handoff (Windows)
The pre-handoff teardown tree-kills only the backends the Desktop owns
(backendConnectionState + backendPool). The memory plugin's hindsight
daemon is spawned DETACHED off venv\Scripts\pythonw.exe, so it survives
the teardown, keeps venv files mapped, and either dead-ends the
venv-blocker scan with no in-app remedy or (pre-#74805 shim-only gate)
raced the updater into a half-updated venv.

Add a narrowly-scoped reap: kill only processes whose exe lives under
venv\Scripts (ordinal case-insensitive prefix — no PowerShell -like
wildcard hazards) AND whose cmdline references hindsight_api.main.
External holders (user terminals, unrelated scripts) are never killed —
scanVenvBlockers still reports them and the hand-off aborts, per existing
design. Selection logic is a pure DI'd module with unit tests.

Salvaged from PR #75477 (scoped per review: the narrow daemon kill; the
PR's generic every-exe kill was rejected as over-broad, its updateInFlight
half was superseded by #75778/#73822, and its generic lock-probe half by
the #74805 release gate + #99724 scanner classification).
2026-09-01 01:30:09 -07:00
liuhao1024 46ac31e84c fix(desktop): sanitized deferral evidence for ledgered manual serve blockers
The Desktop venv-blocker scan (since #99724) defers ledger-verified
serve/dashboard holders to the CLI updater's stop+relaunch rungs, but the
scan output only carried an opaque deferred_backends count — nothing
explained WHICH holders the deferral consumed or why they vanished from
processes.

Add sanitized decision evidence (#98350): deferred_backend_evidence lists
structured ledger identity only (pid, purpose, recorded port) — never the
command line, which can carry tokens or private endpoints. Adds a desktop
parser contract fixture proving the consumer tolerates the diagnostics
while keeping blocked/processes authoritative.

Salvaged from PR #98350; the exemption half of that PR was independently
consolidated on main via #99724 (_is_updater_owned_backend).
2026-09-01 01:30:09 -07:00
Teknium 66e258b4b9 docs(desktop): document the in-app browser comment mode
Covers the Annotate toggle, numbered pins, save-never-sends, and
Add N comments -> composer flow shipped in #100083.
2026-09-01 01:17:33 -07:00
kshitijk4poor 801466765b fix: harden temp fallback against predictable-path attacks; neutral error text; docs
Final-diff review findings (/simplify-code on the full 3-commit stack):

- Temp-dir fallback uses a per-uid name (hermes-profile-exports-<uid>) and
  get_profile_export_path refuses a pre-existing symlink or a directory
  owned by another user — a fixed /tmp/hermes-profile-exports is a
  predictable shared path a local attacker could pre-create to receive the
  secret-bearing archive. Regression test mutation-checked.
- Fail-closed message reworded interface-neutrally (the web API surfaces it
  as HTTP 400 detail where '-o' alone made no sense).
- Docs now cover the temp fallback and the fail-closed refusal.
2026-09-01 01:00:23 -07:00
kshitijk4poor 525dd12da7 fix: fail closed when no safe export destination exists; polish salvage edges
- _profile_export_directory(): when the managed store, the home-sibling
  store, AND the temp dir all resolve inside Git checkouts, raise a clear
  ValueError instead of warning and proceeding — a stderr warning would not
  stop a scripted export from staging a secret-bearing archive in a source
  tree, which is the exact #92457 incident class. All three callers already
  surface ValueError cleanly (CLI/TUI print Error: + exit, API returns 400).
- .dockerignore: drop the /default.tar.gz line made redundant by the global
  *.tar.gz pattern this PR adds.
- hermes profile export -o help text: stop advertising the old
  <name>.tar.gz cwd default.
- Tests: cwd-in-unrelated-checkout topology (the second production shape
  from the blocking review) and the fail-closed path. Mutation-checked:
  both fail on the pre-fix helper.
2026-09-01 01:00:23 -07:00
kshitijk4poor 26fb8f60e6 fix: anchor checkout detection to the export path, not cwd
Follow-up to the salvaged #92689:

- _profile_export_directory() now proves safety on the export dir's OWN
  ancestry (_inside_git_checkout) instead of walking Path.cwd(). The old
  heuristic missed the checkout whenever HERMES_HOME sat inside one but
  the process ran from elsewhere (cron, service manager) — the export
  landed back inside the source tree, the exact incident class.
- When every candidate is inside a checkout, warn instead of silently
  violating the invariant.
- CLI/TUI export callers: move get_profile_export_path() inside the try
  and catch OSError too — a bad profile name or read-only home printed a
  raw traceback instead of the clean error main previously gave.
- Tests: bind module objects at call time (importlib) so sibling reload
  pollution in the tests/hermes_cli sweep can't divorce monkeypatches
  from the code under test; add regression tests for the cwd-independent
  topology and the clean-error path.
- Docs: mention the ~/.hermes-profile-exports fallback store.
2026-09-01 01:00:23 -07:00
joaomarcos c26f75baab fix(security): keep profile exports out of source and image contexts
Route automatic profile exports to a managed store instead of the current checkout, and enforce a CI/Docker boundary that rejects archive files before they can be published.
2026-09-01 01:00:23 -07:00
RFingAdam b6f42c667a fix(gateway): keep the drain-marker read off the event loop
_drain_control_watcher polls drain_requested() once a second, and that
does a synchronous Path.read_text() on the marker file. At a 1s cadence
that is ~86k blocking disk reads a day sitting on the event loop.

It stays invisible until the host is under I/O pressure, at which point
a single read can stall for 30s+ and take every platform heartbeat down
with it. Observed as repeated "discord.gateway: Shard ID None heartbeat
blocked for more than 30 seconds" followed by the Discord websocket
dropping, with the gateway still reporting active.

Move the call to asyncio.to_thread so the read blocks a worker thread
instead of the loop. Semantics are unchanged: drain_control never
raises, the surrounding try/except still covers it, and the sequential
await means at most one probe is in flight at a time.
2026-09-01 13:16:05 +05:30
hermes-seaeye[bot] 04224b2f82 fmt(js): npm run fix on merge (#100096)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-09-01 07:41:45 +00:00
Teknium b0d2148b43 test(desktop): isolate the consent dialog in preview-pane tests
The pane now mounts RealProfileConsentDialog, whose config hook needs a
QueryClientProvider the pane tests don't set up. Mock it out like the
pane's other collaborators — the dialog has its own test file.
2026-09-01 00:36:47 -07:00
Teknium ada7213cca feat(desktop): first-open consent prompt for real-profile browsing
Opening a Browser pane (docked tab or ?win=browser popout) while
browser.use_real_profile is off now offers to turn it on: a one-time
dialog explaining the cookie/login snapshot, with 'Use my profile'
(writes the config key through the same PUT /api/config + shared query
cache the Capabilities toggle reads, so the existing toggle flips on
the spot), 'Not now' (mutes for this app run), and 'Don't show again'
(persists the opt-out across launches).

A claim atom ensures only one mounted Browser pane renders the prompt,
so split zones never stack duplicate dialogs. Prompt copy added to all
five locale catalogs.
2026-09-01 00:36:47 -07:00
Teknium 383f827493 fix(desktop): satisfy import-sort and ref-mirror lint rules in comment mode
Auto-fix perfectionist import/export ordering across the new
preview-annotate files, and extract the conversation-switch annotate
reset into a useCallback so the effect body carries no .current writes
(no-restricted-syntax ref-mirror rule).
2026-09-01 00:30:56 -07:00
Adolanium 10f2a20966 feat(desktop): add comment mode to the in-app browser
Click or drag on the preview page to pin a numbered comment. Saving a pin only adds it to the stack. Add comments drops the crops and a short prompt into the composer and never sends the turn.

Capture takes the visible page then crops in bitmap space, because Electron's rect crop on guest webviews is empty on Windows and shifted on high-DPI.
2026-09-01 00:30:56 -07:00
Brooklyn Nicholson 21b2095d00 fix(desktop): type the config.set mock so display-toggles tests typecheck 2026-08-31 21:11:11 -05:00
Brooklyn Nicholson c42daab67b fix(desktop): mirror the Appearance switches on connect, not just on change
A gateway the app has never spoken to holds no answer at all, and for a
switch that defaults on, silence reads as consent to exactly the thing
the user turned off. Mirroring on connect as well as on change closes
that, and the connect push is narrow on purpose: only settings the user
has actually touched are re-sent, so a fresh install can't broadcast its
defaults over a config.yaml somebody hand-edited on the server.
2026-08-31 21:11:11 -05:00
Brooklyn Nicholson b293157f7d feat(tools): withdraw tip and tour when the user switches them off
Off should mean the model is never told the tool exists. A switch that
only made the call fail leaves Hermes offering walkthroughs it cannot
give and promising to point at things it cannot point at, which reads as
a broken agent rather than a respected preference.

Both gate on the switch through a shared desktop_ui.user_enabled helper,
which is the reactions check_fn generalized — same config read, same
reason it has to be config rather than an env var: the switch belongs to
the session's client, and the client may be on another machine.
2026-08-31 21:11:11 -05:00
Brooklyn Nicholson d0e41cda69 fix(gateway): let config.set write the desktop's Appearance switches
config.set matches an explicit key list and answers 4002 for anything
else, so a renderer mirroring an unlisted key wrote nothing at all. The
reactions toggle shipped that way: every write was rejected into a
swallowed .catch(), and react_to_message stayed dark no matter what the
user picked.

Adds the display booleans as a recognized group so the toggle reaches
the config of whichever gateway the app is actually talking to, which is
the only place a check_fn can read it.
2026-08-31 21:11:11 -05:00
hermes-seaeye[bot] de590cc3cb fmt(js): npm run fix on merge (#99917)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-09-01 02:09:50 +00:00
Brooklyn Nicholson 0b247df94f fix(desktop): dock the sidebar rails down to 640px
The rails left the grid for the hover-reveal overlay at 768px, which cost
the docked sidebar on any half-screen split (1512 -> 756, 1440 -> 720).

Derive the collapse point from the layout instead: a rail costs 237px
(SIDEBAR_DEFAULT_WIDTH) and the chat beside it wants roughly the 420px a
popped-out session window enforces on itself. Express it as a dock floor
rather than a collapse ceiling so an exactly-640px window still docks.
2026-08-31 21:08:57 -05:00
Brooklyn Nicholson b0bcb3640c fix(desktop): drop redundant onReorderSessions check after sessionsDraggable 2026-08-31 21:03:25 -05:00
Brooklyn Nicholson 9052ce76c4 feat(desktop): collapse Yesterday / Last week groups in the sessions sidebar
Date and status labels were separators only. Clicking one now hides the
sessions under it, same as project and profile rows, and Collapse all
covers those buckets too.
2026-08-31 21:03:25 -05:00
Brooklyn Nicholson 1b47277067 test(tui_gateway): cover stop-vs-marker recovery races
Pin that session.interrupt retires the original and rotated marker keys
on ACK, and that a Stop arriving before the disk write cannot leave a
resume-able marker behind.

Co-authored-by: Jaime Chieng <164842890+buddhaholic420@users.noreply.github.com>
2026-08-31 20:50:39 -05:00
Brooklyn Nicholson 5c3bfb6da8 fix(tui_gateway): retire recovery marker on local stop
A confirmed Desktop Stop left the crash-recovery marker on disk until the
run thread finished. If the backend exited in that window, resume treated
the leftover as a crash and auto-continued the turn the user had stopped.

Co-authored-by: Jaime Chieng <164842890+buddhaholic420@users.noreply.github.com>
2026-08-31 20:50:39 -05:00
Gille e07172319d fix(desktop): make /stop interrupt active turns
Preserve the existing background-process cleanup after interrupting the targeted Desktop session. This salvages the narrow /stop behavior from the broader, conflicting PR #45030 on the current slash-command architecture.

Co-authored-by: AlvaroBiano <alvarobiano@users.noreply.github.com>
2026-08-31 20:50:10 -05:00
Brooklyn Nicholson adb23c13cb fix(bot-mode): keep Bot Chat resume on a proven compression tip
Desktop opens the registry id, then session.resume walked the legacy
unmarked-child fallback, so Open Chat still landed in a side chat after
the title lookup was already strict. Recoverable-archive resurrection
uses the same helper.

Co-authored-by: Gille <4317663+helix4u@users.noreply.github.com>
2026-08-31 20:47:53 -05:00
Gille 4abf5c0790 test(bot-mode): scope title lookup regressions 2026-08-31 20:47:53 -05:00
Gille bb28056efd fix(bot-mode): keep canonical lookup on compression lineage 2026-08-31 20:47:53 -05:00
Ben Barclay 56916841b5 refactor(dashboard-auth): replace PKCE cookie payload with base64url(JSON) codec (#99210)
The PKCE cookie's payload has now needed three serialization fixes at
the same spot: the original flat 'k=v;k=v' string tripped http.cookies'
\073 quoted form (dropped whole by strict cookie parsers like Go's
net/http — #83832 field case), and #99176 URL-encoded the whole flat
payload to stay inside the RFC 6265 cookie-octet set. The stacked
layers (single-encoded next=, ';' joins, whole-payload encoding, legacy
discriminator) were the recurring defect source.

Kill the bug class instead of patching it again: the payload is a dict
end-to-end and goes on the wire as base64url(JSON) — the urlsafe
alphabet is a strict subset of cookie-octets, and JSON framing means no
segment value can ever collide with a delimiter. parse_pkce_payload
keeps a three-rung compatibility ladder (base64url(JSON) -> oldest flat
form split-as-is -> #99176 unquote-then-split) for in-flight cookies
during a rolling upgrade (10-minute TTL); a new cookie hitting an old
server fails the OAuth state check and the user just retries.

The 'next' segment is stored as its plain validated path — no extra
encoding layer, so the post-login redirect Location is byte-for-byte
the original target.

Refs #99176, #84065.
2026-09-01 11:47:38 +10:00
xxxigm bcecd675f7 fix(desktop): unwrap Models-page code-skew 503 and recycle the owned backend (#97046)
Show a Restart backend action that kills the SSH serve before the local child
so reconnect cannot reuse a stale lockfile, instead of dumping raw IPC JSON.
2026-08-31 20:43:36 -05:00
xxxigm c99a3919a8 fix(dashboard): drop systemd-only advice from the model-picker code-skew 503 (#97046)
Desktop-owned serve and macOS hosts have no hermes-dashboard unit, so the
restart hint now follows HERMES_SERVE_HEADLESS instead of hardcoding systemctl.
2026-08-31 20:43:36 -05:00
Brooklyn Nicholson 42c415820c test(desktop): assert regenerate rejection restores the full history
Drive the real reloadFromMessage catch, not a copied merge object.
Same coverage on the session-tile sibling.

Co-authored-by: ygd58 <buraysandro9@gmail.com>
2026-08-31 20:43:06 -05:00
Brooklyn Nicholson f615f3796d fix(desktop): restore the transcript when regenerate is rejected
reloadFromMessage hid/truncated messages for the optimistic UI, then on
a failed submit (provider error, compressed-away turn) reset only the
busy flags. restoreToMessage already rolled messages back; do the same
on the primary chat and the session-tile path.

Co-authored-by: ygd58 <buraysandro9@gmail.com>
2026-08-31 20:43:06 -05:00
Brooklyn Nicholson 5c6dbe22c3 fix(compression): take reasoning_content when the summarizer leaves content empty
Local and thinking backends (DeepSeek, Qwen, Kimi) often return a usable
summary in reasoning fields. Treat that as the summary instead of burning
another 100s+ empty-content retry. Leave the wire max_tokens omit intact.

Co-authored-by: Chris DePuy <chris@650group.com>
Co-authored-by: chenhm <chenhm@yuancheng.local>
2026-08-31 20:43:00 -05:00
Brooklyn Nicholson 02458e67ad fix(auxiliary): accept dict and object messages in extract_content_or_reasoning
Compression and some OpenAI-compatible proxies hand us a dict-shaped
response or a bare message, not a ChatCompletion. Reuse the existing
helper instead of a second extractor, and bound an optional reasoning
fallback so a chain-of-thought dump cannot become the summary.

Co-authored-by: Chris DePuy <chris@650group.com>
Co-authored-by: chenhm <chenhm@yuancheng.local>
2026-08-31 20:43:00 -05:00
Brooklyn Nicholson 6eddeca6ef fix(desktop): stop hidden composers from stealing the caret
Keep-alive tabs remount their composer on transcript/status backstops
and were calling focus() while the user typed in the front tab. Gate
autofocus on pane visibility, and refuse to steal the caret from
another visible composer. A hidden tab that still holds DOM focus
does not block the pane the user just switched to.

Co-authored-by: Dan Bennett <dan@danbennett.me>
Co-authored-by: mor44-AI <andrefmontemor@gmail.com>
Co-authored-by: d4rk pr10r <darkpriorlabs@gmail.com>
2026-08-31 20:41:08 -05:00
Brooklyn Nicholson 60ba6024b5 fix(desktop): isolate keep-alive panes from the visible transcript
Hidden session panes published stick-to-bottom into window-global atoms
and every mounted list subscribed to the same jump broadcast, so a buried
tab yanked the reader and flashed the composer. Only the visible pane
may publish, scroll requests are keyed by session, and a run start or
same-session refresh leaves a scrolled-up reader where they were.

Co-authored-by: gamewocao <gamewocao@users.noreply.github.com>
Co-authored-by: mor44-AI <andrefmontemor@gmail.com>
Co-authored-by: d4rk pr10r <darkpriorlabs@gmail.com>
Co-authored-by: Jackal991 <lawrence@hydra-flow.co.uk>
2026-08-31 20:41:08 -05:00
Brooklyn Nicholson d72ce5e434 fix(desktop): rebind the visible chat after a reaped runtime
session.reclaimed now heals through markRuntimeGone before dropping
cache. A 4001 on the visible session's dispatcher request asks for a
durable resume. prompt.submit uses the window dispatcher so the turn
lease survives the ACK, and a primary route no longer registers a
phantom turn lease.

Co-authored-by: Lester Liang <153183032+lesterlxt@users.noreply.github.com>
Co-authored-by: wz-heng <68931789+wz-heng@users.noreply.github.com>
2026-08-31 20:40:31 -05:00
Brooklyn Nicholson 36bea50139 fix(desktop): latch approval and goal polls off a dead runtime
process.list already stopped hammering a reaped id. approval.pending and
goal status did not, and session.info heartbeats republished an equivalent
state object so every tab rerendered. Share the gone-latch from
runtime-gone and keep heartbeat identity when nothing changed.

Co-authored-by: liuhao1024 <sunsky.lau@gmail.com>
Co-authored-by: Dolverin <5910064+Dolverin@users.noreply.github.com>
2026-08-31 20:40:31 -05:00
chelsealong cbe007a559 fix(desktop): stop status-stack remounts from re-arming a dead-runtime poll storm (#98434)
A boot-restored chat can stay bound to a dead runtime id and remount its
composer status stack repeatedly with no genuine rebind ever occurring.
The stack's mount effect cleared the gone-polling latch on every mount, so
each remount re-armed process.list + slash.exec('goal status') against
the same phantom id forever, churning the composer every ~5s.

Real rebinds already reset the latch at the runtime-mint seams
(use-gateway-boot.ts, store/gateway.ts). Drop the redundant per-mount
reset so the latch actually holds across a remount.
2026-08-31 20:40:31 -05:00
Brooklyn Nicholson 308454b372 fix(desktop): route model catalogs and picker reseeds through the focused owner
requestModelOptions now sends the owner profile on the RPC, forced reseeds call getGlobalModelInfo(profile), and picker/cache keys include the registry connection so a tile cannot fall back through the ambient socket.

Co-authored-by: fangliquanflq <fangliquan@qq.com>
2026-08-31 20:40:09 -05:00
Brooklyn Nicholson f08666d42a fix(desktop): persist composer model/provider per remote connection and profile
Sticky composer keys were global, so a provider picked on one remote profile could ride into session.create on another. Persistence now follows an explicit (connectionId, profile) owner published before the active-profile reseed; unresolved legacy owners fail closed.

Co-authored-by: fangliquanflq <fangliquan@qq.com>
2026-08-31 20:40:09 -05:00
Brooklyn Nicholson 0e7eebc266 fix(desktop): scope remote model catalog and primary-label REST to the focused profile
A shared dashboard's launch HERMES_HOME is not the selected profile. model.options now runs under @_profile_scoped, and global-remote REST keeps ?profile= even for the primary label.

Co-authored-by: fangliquanflq <fangliquan@qq.com>
2026-08-31 20:40:09 -05:00
Brooklyn Nicholson 8b28bdceb5 fix(desktop): stop a stale composer model pinning every new chat
Settings → Model while a chat is open flips the composer source to
'default' but leaves the live session's model painted. Sending that
value on session.create pinned every new chat and skipped model.default.

Only a manual composer pick is a per-session override.

Co-authored-by: Tharanee <tharanee@tharanee.net>
2026-08-31 20:39:06 -05:00
Gille f98f5e74e0 fix(desktop): preserve terminal startup output 2026-08-31 19:43:31 -05:00