Commit graph

4055 commits

Author SHA1 Message Date
Frank Song
a89a6be075 fix(chat): avoid duplicate reasoning fallback owner 2026-07-15 06:58:02 +08:00
Frank Song
e79f3cf53e fix(chat): recover reasoning after stale live turn 2026-07-15 06:58:02 +08:00
Frank Song
54f43b37fa fix(chat): isolate reasoning fallback by session 2026-07-15 06:58:02 +08:00
Frank Song
4f2190c500 fix(chat): preserve visible reasoning ownership 2026-07-15 06:58:02 +08:00
pxxD1998
73b6c5d604 fix(sessions): cascade delegated subagents on parent deletion 2026-07-14 18:10:03 +08:00
Frank Song
59e92461fc fix: fail closed on wrapped gateway pid helpers 2026-07-14 16:36:23 +08:00
Frank Song
d08116b4d2 fix: harden gateway restart recovery targeting 2026-07-14 16:01:56 +08:00
Frank Song
ba1a058f87 fix: fail closed on implicit gateway pid recovery 2026-07-14 15:25:36 +08:00
Frank Song
14adbb61a4 fix: pin agent update gateway restart profile 2026-07-14 14:46:00 +08:00
Frank Song
885e267213 fix: retry transient agent gateway restart failures 2026-07-14 14:13:19 +08:00
Isla-Liu
2e8915baf8 perf(webui): skip impossible state-db prefix normalization 2026-07-14 05:38:34 +00:00
Rui Xue
b2c7578653 fix(sessions): guard all auto-mint paths + strict config/body parsing (gate review)
Gate review follow-ups for the #6022 no-leak rule: every non-deliberate
newSession/session-new call site passes worktree:false; only deliberate
New Chat inherits the config default.

- Strict boolean config read: only YAML true enables the default;
  quoted strings/ints/lists/dicts/null fall to the safe no-worktree
  default instead of truthiness-coercing.
- Presence-based explicit detection: 'worktree' in body — an explicit
  worktree:null is an opt-out, never a fall-through to config.
- Guarded remaining auto-mint call sites with explicit worktree:false:
  profile switch (panels.js), promptWorkspacePath + switchToWorkspace
  blank-page binds (panels.js), promptNewFile + promptNewFolder
  (ui.js), terminal auto-session (commands.js).
- Tests: worktree:null vs config-true (factory must not be called),
  strict-boolean malformed-shape matrix, static regression gates for
  every auto-mint site + a bare-body sweep so future auto-mint paths
  can't silently omit the key; updated the cmdTerminal preflight
  assertion for the new call shape.
2026-07-14 04:35:06 +00:00
Rui Xue
5f638a1694 feat(sessions): honor the agent's worktree config default in /api/session/new
/api/session/new treated an absent 'worktree' key as false, so the
agent's config-level 'worktree: true' default was never consulted for
WebUI sessions even though the WebUI reuses the agent's own worktree
helper. Both callers should agree on same-agent/same-repo isolation.

Three-value model:
- body 'worktree' absent -> agent config default applies (profile-aware
  via get_config_for_profile_home, so a user with worktree:true in one
  profile but not another gets per-profile behavior)
- explicit true/false -> honored verbatim; explicit always wins

Non-git workspaces: a config-default request degrades to a plain
session and surfaces 'worktree_skipped' in the payload; an explicit
worktree:true keeps the hard 400.

Frontend (same change, load-bearing): newSession() forwards explicit
true/false and omits the key otherwise; the boot-time auto-bind
(_maybeBindFreshDefaultWorkspaceSession) and the onboarding session
send worktree:false explicitly so a config default can never mint a
worktree + branch from merely opening the UI. Deliberate 'New Chat'
sends nothing and inherits the default.

Tests: route matrix (config on/off x body absent/true/false), non-git
fallback vs explicit 400, explicit-false-beats-config-true, config
helper (ambient/named-profile/fail-soft), static UI assertions that
auto-bind and onboarding opt out and New Chat paths don't pin a value.

Fixes #6022
2026-07-14 04:35:06 +00:00
nesquena-hermes
503a369d0c refine(#2147): drop confusing Binding row, keep localization + Example
Maintainer revision of #5806: the localization refactor (all profile-concept
help text now goes through t() so it's translatable via the documented
English fallback) is the core value, and the concrete Example row genuinely
clarifies how profiles + workspaces work together. The Binding row restated
the Together row + added scheduled-task edge-case trivia that muddied a concept
explainer, so it's dropped (row + its 2 i18n keys + its test).

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-07-14 04:04:39 +00:00
Rod Boev
acc63090dd test(#2147): honor english fallback-owned profile concept copy 2026-07-14 03:33:00 +00:00
Rod Boev
d2843d94e7 fix(#2147): restore i18n fallback for profile concept copy 2026-07-14 03:33:00 +00:00
Rod Boev
453b9696e3 fix(#2147): add profile_concept keys to all locale blocks and update existing test 2026-07-14 03:33:00 +00:00
Rod Boev
e9b17b1a52 feat(#2147): localize profile concept help and add usage examples 2026-07-14 03:33:00 +00:00
nesquena-hermes
7c3eae400a Merge branch 'rebase-5431' into stage/nix-slash 2026-07-14 01:24:31 +00:00
nesquena-hermes
7b210baca4 fix(#5759): match composer-focus chord on '/' char not physical Slash code
The Cmd/Ctrl+/ predicate keyed off e.code==='Slash' + !e.shiftKey, which on
QWERTZ layouts (a) stole Ctrl/Cmd+- browser zoom-out (physical Slash there is
the minus/zoom key) and (b) missed the real shifted-'/' chord. Match e.key==='/'
instead — layout-correct on every keyboard. Tests updated to assert the physical
code isn't used and a layout-shifted '/' still matches.

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-07-14 01:20:12 +00:00
Rod Boev
9bd96fb75d fix(#2422): support packaged Hermes Agent discovery 2026-07-14 01:06:49 +00:00
Rod Boev
53348df68d fix(#2422): allow Nix package version generation 2026-07-14 01:06:49 +00:00
Rod Boev
b7f523c3a1 fix(#2422): tighten Nix module polish 2026-07-14 01:06:49 +00:00
Rod Boev
243f27e7ed fix(#2422): keep NixOS state private 2026-07-14 01:06:49 +00:00
Rod Boev
72b51a6b78 fix(#2422): avoid hardcoded agent site packages 2026-07-14 01:06:49 +00:00
Rod Boev
70514549c4 fix(#2422): harden NixOS packaged runtime 2026-07-14 01:06:49 +00:00
Rod Boev
37b51803e6 Keep composer focus shortcut easy to reach 2026-07-14 01:06:46 +00:00
Rod Boev
ca5f02bbff feat(#5759): add composer focus shortcut 2026-07-14 01:06:46 +00:00
Rod Boev
001d7985bc feat(#6006): instrument send-to-first-token latency 2026-07-13 17:56:44 -04:00
rudidev08
34cf2de57a Fix: "Approve once" no longer persists as a session approval (#6017)
In the local (non-gateway) approval path, _resolve_approval_legacy grouped
"once" together with "session" and called approve_session() for both.
approve_session() records the pattern in the session-wide allowlist that the
guard later consults, so a single "Approve once" click silently approved every
later matching tool call for the whole session -- a destructive tool could then
run with no approval card.

Only "session"/"always" persist now. "once" falls through: the
resolve_gateway_approval() call below still unblocks the parked agent thread for
every choice, so the current tool call proceeds, but nothing is written to
_session_approved and the next matching guarded call re-prompts.

Adds tests/test_issue6017_approve_once_no_persist.py, asserting is_approved is
False after "once" and True after "session".
2026-07-13 13:05:52 -07:00
sheldon-im
d7c2cc2d5d Restore focus from non-button session action menus 2026-07-13 19:24:09 +00:00
sheldon-im
bc85711af2 Improve conversation actions menu keyboard access 2026-07-13 19:24:09 +00:00
Rod Boev
3ada146d48 fix(#5989): keep colon-suffixed proxy models distinct 2026-07-13 18:57:15 +00:00
Rod Boev
ff89f2ebd6 fix(#5989): keep cross-provider live rows selectable 2026-07-13 18:57:15 +00:00
Rod Boev
58725d5de9 fix(#5989): separate picker dedup from restore matching 2026-07-13 18:57:15 +00:00
Rod Boev
052effcfc4 fix(#5989): deduplicate proxy and catalog model rows in the picker 2026-07-13 18:57:15 +00:00
Rui Xue
c833f290a9 fix(worktrees): unlock before remove so WebUI-created worktrees can be reaped
WebUI-created worktrees are locked at creation by the agent
(cli._setup_worktree locks with --reason 'hermes pid=...'), but
remove_worktree_for_session never unlocked, so git refused every removal
with "use 'remove -f -f' to override or unlock first" — force=true
included, since a single --force cannot override a lock.

Mirror the agent's own _cleanup_worktree ordering: fail-soft
'git worktree unlock' right before the remove, after all
dirty/untracked/unpushed/stream/terminal guards have passed. Those
guards remain the real safety layer; force keeps meaning 'override
dirty working state', never 'override a foreign lock'.

Tests: locked worktree now removes cleanly without force; unlock is
fail-soft when the worktree was never locked; existing mocked
call-order assertions updated for the leading unlock call.

Fixes #6023
2026-07-13 17:47:58 +00:00
silent-reader-cn
946f7dbc2b fix(media): preserve scanner prose ownership 2026-07-13 07:41:13 +00:00
silent-reader-cn
e13a75b7b1 test(media): cover fade MEDIA streaming splits 2026-07-13 07:41:13 +00:00
silent-reader-cn
3b19660235 fix(media): handle fade MEDIA prefix splits 2026-07-13 07:41:13 +00:00
silent-reader-cn
a17370ad4a fix(media): harden streamed MEDIA rendering 2026-07-13 07:41:13 +00:00
silent-reader-cn
188a03bb28 fix: remove unused smd media test variable 2026-07-13 07:41:13 +00:00
silent-reader-cn
76940e821d fix: flush smd media tail on stream end 2026-07-13 07:41:13 +00:00
silent-reader-cn
547c218ddf fix: clear fallback smd media tail 2026-07-13 07:41:13 +00:00
silent-reader-cn
ca3a1ac04d fix: accept extensionless streaming media urls 2026-07-13 07:41:13 +00:00
silent-reader-cn
13b7080bf0 fix: align smd media boundary extensions 2026-07-13 07:41:13 +00:00
silent-reader-cn
5359b70c6a fix: harden smd media split-tail streaming 2026-07-13 07:41:12 +00:00
silent-reader-cn
7cf8823921 fix: remove unused smd media test variable 2026-07-13 07:41:12 +00:00
silent-reader-cn
0b14ab54e3 fix(webui): split prose/MEDIA paths in streaming interceptor + per-parser tail buffer
Code-level fixes for the two issues Greptile + owner review surfaced:

1. SAFETY (Greptile #1 / owner security regression): the original
   _smdMediaAwareAddText concatenated prose slices + MEDIA HTML into
   one string and handed it to DOMParser. Prose containing HTML
   metacharacters (e.g. <img onerror=...>) would be parsed as live
   elements. Fixed by routing each prose run through baseAddText
   (which calls createTextNode) and only feeding each single MEDIA
   token's trusted _inlineMediaHtmlForRef output through DOMParser.

2. CROSS-CHUNK SPLITS (Greptile #2): smd may deliver a MEDIA: token
   across two flushes (e.g. "MEDIA:C:\...\" then "foo.png"). The
   original implementation would treat each half as separate text
   because neither half terminated cleanly. Fixed with a per-parser
   tail buffer (WeakMap keyed by parser instance) that holds trailing
   bytes ending in "MEDIA:" or MEDIA:<partial-ref> until the next call.

   The tail-buffer boundary detection was also improved: the previous
   `/[\s\)\]]/.test(rest)` incorrectly rejected "before MEDIA:" (the
   whitespace is before MEDIA, not after a completed ref). Replaced
   with `rest.endsWith('MEDIA:')` and `/MEDIA:...$/.exec(rest)`.

Architecture: _smdAppendMediaNode is a new single-token DOM insertion
helper that creates a DocumentFragment from one _inlineMediaHtmlForRef
output. _smdMediaTailSet/_smdMediaTailClear manage the per-parser
buffer. Both _safeSmdRenderer and _streamFadeRenderer now route through
the updated interceptor with tail map and parser identity.

Coverage: test_smd_media_in_stream.py tests the architectural invariants
(helper exists, both renderers delegate, recursion guard, entity-safety,
cross-chunk tail). 222 tests pass across all renderer/behaviour suites.
2026-07-13 07:41:12 +00:00
silent-reader-cn
6950540ed5 fix(webui): render MEDIA: tokens inline during smd streaming
The full renderMd() pipeline maps MEDIA:<path-or-url> tokens to inline
images / video / audio / SVG / PDF / etc. via stash + restore passes.
The streaming smd renderer used by the live assistant body never went
through that pass, so any MEDIA token emitted mid-turn showed up as the
raw path string in the chat window and only became a real <img> once
the turn settled and the full render fired.

Fix:
- Extract the URL/media → HTML mapping in renderMd() into a shared
  helper _inlineMediaHtmlForRef(ref, sessionId) so the streaming path
  can reuse the same DOM the settled message will eventually render.
- Add _smdMediaAwareAddText in messages.js that wraps smd's add_text
  to detect MEDIA tokens in any incoming text chunk and splice the
  resulting <img>/<video>/<a>/<div data-path=...> node directly into
  the smd parent via a DOMParser-created fragment. Plain prose still
  delegates to the renderer it intercepted (so the fade renderer keeps
  its word-by-word animation, the safe renderer keeps its O(n) plain
  text-node insertion).
- Route _safeSmdRenderer.add_text and _streamFadeRenderer.add_text
  through the interceptor. Each fast-paths the no-MEDIA case to the
  original baseAddText so we can't recurse into our own wrapper.

Coverage:
- New test_smd_media_in_stream.py locks in the architectural invariants
  (helper exists, both renderers delegate to it, recursion guard).
- test_renderer_js_behaviour.py driver now provides a minimal
  _inlineMediaHtmlForRef stand-in so renderMd can be invoked standalone
  under node without the rest of ui.js in scope (matches the patterns
  renderMd already relied on for _IMAGE_EXTS / _SVG_EXTS / _AUDIO_EXTS).
- test_svg_audio_video_rendering.py static-count assertions relax from
  ">=2" to ">=1" since audio/video/SVG markup now lives in a single
  shared function rather than being copy-pasted inside renderMd's
  stash-restore closure.
2026-07-13 07:41:12 +00:00