Commit graph

3896 commits

Author SHA1 Message Date
Rod Boev
e1ec41b32c fix(session): reload latest imported session before title save (#3987) 2026-06-13 16:47:57 -04:00
Rod Boev
2278867c88 fix(#3987): generate titles for imported sessions 2026-06-11 14:09:14 -04:00
nesquena-hermes
48860418c9
Release LW stage — v0.51.359 (#3962 assistant turn anchor phase 0 scaffold) (#3977)
* feat(anchor): add stable assistant turn phase 0 scaffold

* chore: stamp v0.51.359 — Release LW (assistant turn anchor phase 0 scaffold, #3962)

---------

Co-authored-by: Frank Song <franksong2702@gmail.com>
Co-authored-by: nesquena-hermes <agent@local>
2026-06-10 23:14:18 -07:00
nesquena-hermes
1126e54132
Merge PR #3973 — v0.51.358 first-run password bootstrap hardening (#3964)
Release LV — v0.51.358 (first-run password bootstrap hardening, #3964)
2026-06-10 21:54:09 -07:00
Hermes Agent
f2ef2851d3 Release LV stage — v0.51.358 (#3964 first-run password bootstrap local gate)
#3964 [security] gate first-password bootstrap (_set_password on POST /api/settings
while auth disabled) to local clients — blocks remote unauth first-run ownership.
Uses request-start auth snapshot (auth_enabled_before), so no mid-request TOCTOU.
Self-rebased onto v0.51.357 (8-behind, 3-dot fidelity verified byte-identical).

#3970 (oauth single-flight) DROPPED from this stage: Codex+Opus both caught a
check-then-insert race — _pending_oauth_flow_for releases the lock before the
device-code request + flow insertion, so concurrent unauth starts still spawn
multiple workers (Codex empirically reproduced w/ 2 threads). Returned to author
w/ the atomic per-(provider,home) start-lock fix. Re-gating the (N-1) stage.

Co-authored-by: Hinotoi-agent <Hinotoi-agent@users.noreply.github.com>
2026-06-11 04:39:34 +00:00
nesquena-hermes
5dceb2993c
Merge PR #3963 — v0.51.357 mid-stream flicker tie fix (#3877)
fix(streaming): re-attach preserved live turn on length tie — residual mid-stream flicker (#3877 reopen)
2026-06-10 21:06:28 -07:00
Hermes Agent
4857f928bd docs(changelog): stamp #3877 flicker fix as v0.51.357 (Release LU) 2026-06-11 03:58:00 +00:00
Hermes Agent
0a561f240a chore(streaming): move dataset.sessionId stamp to the whole-turn restore paths only (Opus/Codex nit)
Both reviewers noted the _preservedLiveTurn.dataset.sessionId stamp was a dead
write in the segment-swap path (only the segment enters the DOM there; the
rebuilt turn was already stamped at build time). Move the stamp into the two
whole-turn paths where the preserved turn actually enters the DOM. No behavior
change; removes the dead write.
2026-06-11 03:20:17 +00:00
Hermes Agent
02c6696e53 chore: drop stray uv.lock accidentally swept in (not tracked on master) 2026-06-11 03:14:03 +00:00
Hermes Agent
3ec93ff6bf fix(streaming): count live-worklog shell/reason as preserved structure (Codex r2)
Codex round 2 caught that _structuralCount() only enumerated .tool-call-group/
.tool-card-row/.tool-worklog-group but NOT the live Worklog shell
(.live-worklog[data-live-worklog-shell=1]) or its .wl-reason content (built by
_flushPendingSegmentRender / ensureLiveWorklogContainer, see messages.js:2386 +
ui.js:6712/6655). So a turn whose live-ahead structure is a Worklog (not a bare
tool-call-group) undercounted preserved structure → wrongly took the segment-swap
path → detached the visible live Worklog container for a frame.

Fix: add .live-worklog[data-live-worklog-shell=1] and .wl-reason to the structural
count (mirrors the canonical 'has current worklog content' selector set at
sessions.js:1125-1131). Added a structural-test assertion + verified live: a
preserved turn with a live worklog .wl-reason the rebuild lacks now takes the
whole-turn restore path and the worklog + parser both survive.

Convergence: Codex findings narrowing each round (tie-guard → tail-segment →
worklog-selector), all real, all fixed + empirically verified.
2026-06-11 03:12:40 +00:00
Hermes Agent
c37c0d1397 fix(streaming): whole-turn restore when live DOM is structurally ahead (Opus)
Opus flagged a tool-card-frame-loss edge in the segment-only swap: the premise of
#3877 is that the live DOM can be AHEAD of S.messages, so a tool/worklog group can
land in the live turn between the last throttled persist and a mid-stream rebuild.
The rebuild (built from the lagging S.messages) would then lack that group, and a
segment-only swap would drop it for a frame (empirically confirmed: a live-only
tool card present before the rebuild was absent after).

Fix: count structural blocks ([data-live-assistant=1] + tool/worklog/thinking
groups) on both turns. Segment-swap only when the rebuild is the structural
SUPERSET (rebuilt >= preserved); otherwise restore the WHOLE preserved turn so
live-only structure the user already saw never vanishes. Verified live: tool-card-
ahead → whole-turn restore keeps the card + parser; single-segment tie → segment
swap, parser connected; multi-live-segment tail → tail parser connected, both
segments kept.

Also addresses Opus's dead-code note implicitly — the dataset.sessionId stamp now
matters on the whole-turn restore path (it's the branch that actually inserts the
preserved wrapper).
2026-06-11 03:03:55 +00:00
Hermes Agent
95e874d7be fix(streaming): target the parser-owned TAIL live segment in the #3877 re-attach (Codex CORE)
Codex caught an asymmetry in the first cut: the rebuilt side selected the tail
live segment ([...].pop()) but the preserved side used querySelector() = the
FIRST [data-live-assistant=1] segment. In a multi-live-segment turn (reconnect /
post-tool activity boundaries — see messages.js ensureAssistantRow re-attaching
to the LAST live segment), the smd parser writes into the tail segment, so
swapping the first preserved segment would move the wrong node and leave the
parser-owned tail detached — re-introducing the flicker for multi-segment turns.

Fix: select the preserved segment from querySelectorAll, defaulting to the LAST
(tail), and prefer the one whose data-live-segment-seq matches the rebuilt tail;
compute _preservedLen from that segment. Added a structural regression test
pinning the tail selection.

Verified live: a 2-live-segment turn (seq 1 + seq 2, parser on seq 2) keeps the
parser-owned tail connected across a mid-stream rebuild (was orphaned with the
first-segment selection).
2026-06-11 02:57:46 +00:00
Hermes Agent
9803a037fe fix(streaming): re-attach preserved live turn on length tie (#3877 reopen)
The #3877 fix (#3892) preserves the live assistant turn's DOM node across a
mid-stream renderMessages() rebuild and swaps it back in when the rebuilt turn
has less streamed text than the preserved (smd-parser-referenced) node. But the
guard was strict: _rebuiltLen < _preservedLen. At the throttled session
write-back boundary the rebuilt turn's live segment can carry EXACTLY the same
text length as the preserved node, so the strict guard skipped the swap and left
the streaming parser writing into the now-detached original node — the residual
'disappears, then reappears' frame the reporter still saw on v0.51.347.

Fix:
- Relax the guard to _rebuiltLen <= _preservedLen so the equal-length tie also
  restores the parser-referenced node (on a tie the preserved node is strictly
  preferable: it holds the live parser reference and nothing is lost). When the
  rebuilt turn genuinely has MORE content (reconnect where S.messages caught up
  past the parser) the guard still skips and the parser re-resolves to the fuller
  node.
- Swap at the SEGMENT level (replace only the rebuilt live segment with the
  preserved one) instead of a whole-turn replaceWith, so a multi-segment turn
  (earlier settled segments + tool/worklog groups built by the rebuild) keeps
  that rebuilt-only structure. Whole-turn replace remains the fallback when the
  rebuilt turn has no live segment to target.

Verified live (isolated server on the edited ui.js, real shipped renderMessages):
- single-segment tie: parser node stays connected (was orphaned on master);
- multi-segment tie: parser connected AND both assistant segments preserved;
- rebuilt-longer: correctly keeps the fuller rebuilt node, no content loss.

Updated the #3877 structural tests to pin the <= guard + segment-level swap.

Closes #3877.
2026-06-11 02:51:14 +00:00
nesquena-hermes
c77c8312c8
Merge PR #3960 — v0.51.356 per-profile providers & models (#3957)
fix(profiles): scope /api/providers + /api/models to the active profile (#3957)
2026-06-10 19:18:49 -07:00
Hermes Agent
808b3fa511 docs(changelog): stamp #3957 fix as v0.51.356 (Release LT) 2026-06-11 02:15:28 +00:00
Hermes Agent
85ee6ec2c8 fix(profiles): address greptile P2 flags on #3960
- _load_models_cache_from_disk: resolve _get_models_cache_path() once
  (was called twice — exists() then open()) (greptile P2).
- conftest: use a _MISSING sentinel for the _active_profile restore guard so
  it restores whenever api.profiles was importable pre-test, independent of the
  value (greptile P2; _active_profile defaults to 'default' so the prior
  is-not-None guard was functionally fine but technically value-dependent).

Co-authored-by: nesquena-hermes <nesquena-hermes@users.noreply.github.com>
2026-06-11 01:56:58 +00:00
Hermes Agent
b5d382a3bb fix(profiles): scope the detached models-rebuild worker to the active profile (#3957)
Codex + Opus both independently caught a CORE gap in the first cut: the
bounded /api/models rebuild runs on a detached 'models-catalog-rebuild' daemon
thread that inherits neither the request-profile thread-local (#798) nor
os.environ. So on a non-default profile the worker probed the DEFAULT profile's
credentials and, when the 4s budget was exceeded, published the rebuilt catalog
to the DEFAULT profile's disk cache (cross-contamination) — exactly the slow
path a non-default cold rebuild takes.

Fix:
- profile_scope_for_detached_worker(profile_name): sets the request-profile TLS
  AND applies the profile .env on the worker thread, restoring both on exit
  (no-op for default). Distinct from profile_env_for_active_request (which reads
  the current thread's TLS and must not clear it).
- get_available_models() captures the active profile on the request thread and
  wraps the rebuild worker body (probe + over-budget publish + disk save +
  fingerprint) in that scope; the legacy synchronous rebuild applies the profile
  env on the foreground. /api/models route no longer wraps (the work moved into
  get_available_models so ALL callers — chat/start, resolution — are fixed).
- 2 new regression tests incl. the worker-thread before/inside/after assertion.

Empirically verified: a fresh worker thread resolved models_cache.json/default
WITHOUT the scope (the bug) and models_cache.work.json + the work .env + the
work auth.json WITH it.

Co-authored-by: nesquena-hermes <nesquena-hermes@users.noreply.github.com>
2026-06-11 01:41:24 +00:00
Hermes Agent
70d391c44a fix(profiles): scope /api/providers + /api/models to the active profile (#3957)
On a non-default profile, Settings → Providers timed out and the model
picker showed only the default profile's models. WebUI profile switching is
per-client/cookie-scoped (#798), but two read-only paths resolved from the
process-global default profile:

- Facet A: /api/providers + /api/models did not apply the active profile's
  .env around the read, so get_auth_status() / provider_model_ids() / custom
  key lookups resolved the default profile's credentials. On a non-default
  profile the auth probes could stall past the 30s frontend abort.
- Facet B: the /api/models disk cache was a single import-time
  STATE_DIR/models_cache.json shared across every profile, while the cache
  fingerprint is profile-specific -> a non-default profile rejected the shared
  snapshot every read and cold-rebuilt (the slow serial-probe path).

Fix:
- api.profiles.profile_env_for_active_request(): applies the active
  per-request profile's .env for the duration of the read (delegates to the
  existing profile_env_for_background_worker used by streaming). No-op for the
  default/root profile, so single-profile deployments are byte-identical.
- api.config._get_models_cache_path(): profile-keys the disk cache filename
  (models_cache.<profile>.json) derived from the default path; default profile
  keeps models_cache.json unchanged (no file migration).
- routes.py: wrap both GET handlers in profile_env_for_active_request.
- conftest: restore api.profiles._active_profile + clear request-profile TLS
  after each test (a pre-existing isolation hole that profile-keyed cache
  paths newly surface under sharding).

Tested: 10 new regression tests; live before/after on isolated servers shows
a non-default profile now surfaces its configured provider (deepseek) with
its own credentials + its own cache file, where master showed only the
default profile's providers and one shared cache.

Co-authored-by: nesquena-hermes <nesquena-hermes@users.noreply.github.com>
2026-06-11 01:17:22 +00:00
nesquena-hermes
d9cf050cf3
Merge pull request #3956 from nesquena/release/stage-3573
Release LS — v0.51.355 (#3573 conversation outline panel)
2026-06-10 16:58:13 -07:00
nesquena-hermes
7dc8b35a7e fix(outline): keep outline valid on the todos sidebar panel (Opus follow-up) 2026-06-10 23:55:05 +00:00
nesquena-hermes
02e7436a80 chore: stamp v0.51.355 — Release LS (conversation outline panel) 2026-06-10 23:47:07 +00:00
nesquena-hermes
922efdf234 feat(outline): opt-in conversation outline panel (#2124)
Adds an opt-in, default-off, desktop-only floating panel that lists the user's
questions in a conversation as a numbered jump-list (click to scroll + flash the
message). Gated behind a Settings → Preferences toggle (show_conversation_outline)
wired through the existing show_* preference boot/load/autosave/save paths.

Review follow-ups applied on absorb:
- Outline is now strictly chat-only: leaving the chat view (Settings, Tasks,
  Insights, …) hides the toggle button AND closes the panel; returning to chat
  restores the toggle (panel stays closed until re-opened). Implemented by gating
  _outlineAllowed() on the active panel and re-evaluating via a MutationObserver on
  the <main> showing-<panel> class (switchPanel is a global fn declaration that
  can't be reliably wrapped from this script).
- Fixed a latent visual bug: #outlinePanelWrapper{display:flex} (id selector)
  outranked the UA [hidden]{display:none}, so wrapper.hidden=true never actually
  hid the panel — the × close button and auto-close had no visual effect. Added
  #outlinePanelWrapper[hidden]{display:none;}.
- Regression tests for both.

Closes #2124.

Co-authored-by: Rod Boev <rod.boev@gmail.com>
2026-06-10 23:46:39 +00:00
nesquena-hermes
b1a4750466
Merge pull request #3951 from nesquena/release/stage-lr2
Release LR — v0.51.354 (#3950 preserve @provider:model picks across cold catalogs)
2026-06-10 15:20:16 -07:00
nesquena-hermes
d7a97600ed chore: stamp v0.51.354 — Release LR (@provider:model cold-catalog preservation) 2026-06-10 22:17:01 +00:00
nesquena-hermes
cdd76c04b1 fix(model-resolver): preserve @provider:model picks across cold catalogs
_resolve_compatible_session_model_state() no longer reverts an explicit
@provider:model selection to the default when the provider's group is missing from
the cached catalog snapshot. explicit picks always honored; non-explicit (2nd+ turn
/ chat switch) preservation requires the provider to be KNOWN/CONFIGURED via the new
_provider_is_known_or_configured() (static registry + custom-provider config, NOT the
cold catalog) — so a cold live-discovery provider (ollama-cloud/deepseek/xai) is
preserved while a genuinely-unknown provider (@removed:...) falls through to
default-repair. A known-but-unconfigured builtin is deliberately preserved (surfaces
a clear runtime auth error rather than a silent swap; a cheap env/config credential
check would mis-classify OAuth/auth-store providers). Keeps the #3867 cached-catalog
hot path intact.

Co-authored-by: starship-s <starship-s@users.noreply.github.com>
2026-06-10 22:16:25 +00:00
nesquena-hermes
a212fcd330
Merge pull request #3949 from nesquena/release/stage-3427
Release LQ — v0.51.353 (#3427 cross-client live-turn recovery)
2026-06-10 13:27:09 -07:00
nesquena-hermes
c05b6e478e chore: stamp v0.51.353 — Release LQ (cross-client live-turn recovery) 2026-06-10 20:12:36 +00:00
nesquena-hermes
6bac70d298 fix: preserve live stream output across session switches (cross-client)
Adds a server-side run-journal live snapshot (_run_journal_live_snapshot) returned
in GET /api/session as runtime_journal_snapshot, so a FRESH client (another device,
or a tab with no in-memory snapshot) opening an in-progress session immediately sees
the already-streamed assistant text + tool cards rebuilt from the server. Composes
with the existing _replay_run_journal cursor path (seeds lastRunJournalSeq so replay
resumes from the snapshot cutoff, not duplicating it) and keys tool cards by the same
5 id aliases (tid/id/tool_call_id/tool_use_id/call_id) as #3763 so SSE replay replaces
rather than duplicates snapshot cards. Payload values truncated; redaction test added.

Co-authored-by: t3chn0pr13st <technopriest@live.ru>
2026-06-10 20:12:05 +00:00
nesquena-hermes
64c536641a
Merge pull request #3948 from nesquena/release/stage-p2
Release LP — v0.51.352 (medium round: jump-to-response, STATE_DIR warning, J/K nav)
2026-06-10 13:02:42 -07:00
nesquena-hermes
2374e12a9b chore: stamp v0.51.352 — Release LP (medium round) 2026-06-10 19:49:47 +00:00
nesquena-hermes
337e3b50ab feat(#3845): add J/K keyboard shortcuts for previous/next session navigation
Global j/k keydown bindings navigate the session list (j=next, k=prev), guarded
by _isInteractiveSwipeTarget so they never fire while typing in the composer or
any input/textarea/contenteditable. Modifier-key combos are ignored.

Closes #3845.

Co-authored-by: Rod Boev <rod.boev@gmail.com>
2026-06-10 19:49:14 +00:00
nesquena-hermes
6988995389 fix(#3915): warn on STATE_DIR divergence at startup when session store is empty
When SESSION_DIR has no session .json files and the index is empty/absent,
print_startup_config() now scans sibling state directories and prints a diagnostic
pointing the user at the populated sibling + the HERMES_WEBUI_STATE_DIR to set —
fully fail-safe (try/except, warning-only, no behavior change).

Closes #3915.

Co-authored-by: Rod Boev <rod.boev@gmail.com>
2026-06-10 19:48:41 +00:00
nesquena-hermes
97f92b3a4b fix(#3852): jump-to-question button targets response start
The per-turn jump button now scrolls to the START OF THE RESPONSE (the assistant
segment) instead of the user's question, with a fail-safe fallback to the question
row when the assistant segment isn't found. Label updated to 'to response' across
all 13 locales.

Closes #3852.

Co-authored-by: Rod Boev <rod.boev@gmail.com>
2026-06-10 19:48:41 +00:00
nesquena-hermes
0af629fbb9
Merge pull request #3946 from nesquena/release/stage-brick
Release LO — v0.51.351 (Phase 0 brick batch: data-loss + mobile stream reattach)
2026-06-10 12:14:46 -07:00
nesquena-hermes
21154870a8 chore: stamp v0.51.351 — Release LO (Phase 0 brick batch) 2026-06-10 19:11:58 +00:00
nesquena-hermes
2391630685 fix(#3929): preserve partial work on turn-level errors
Extract _build_partial_message() shared by cancel_stream() and the new
_snapshot_and_append_partial_on_error(); the two provider/exception error paths
in _run_agent_streaming now snapshot accumulated text/reasoning/tool_calls under
STREAMS_LOCK and append a _partial assistant message instead of discarding it.

Closes #3929.

Co-authored-by: b3nw <150195942+b3nw@users.noreply.github.com>
2026-06-10 19:11:58 +00:00
nesquena-hermes
f00d1bd1cd fix(#3863): reattach live stream after mobile app-switch offline recovery
_recoverFromOfflineSoftly() now probes stream status and calls attachLiveStream()
after refreshSession() so a live stream resumes after Android PWA backgrounding.

Closes #3863.

Co-authored-by: Rod Boev <rod.boev@gmail.com>
2026-06-10 19:11:58 +00:00
Rod Boev
299a7a9553
docs(#3794): clarify how to stop each launch method in Quick start (#3942)
* docs(#3794): clarify how to stop each launch method in Quick start

* docs(#3794): use lsof/ss for PID discovery instead of implying bootstrap prints it

---------

Co-authored-by: nesquena-hermes <nesquena+hermes@gmail.com>
2026-06-10 12:11:45 -07:00
Rod Boev
845aa60f66
docs(#3733): document three-service unified setup and warn about dashboard write conflicts (#3938)
Co-authored-by: nesquena-hermes <nesquena+hermes@gmail.com>
2026-06-10 12:08:53 -07:00
Frank Song
4be0dab31e
docs(rfc): add stable assistant turn anchors (#3927)
* docs(rfc): add stable assistant turn anchors

* docs(rfc): address anchor review comments

---------

Co-authored-by: Frank Song <franksong2702@gmail.com>
2026-06-10 12:03:18 -07:00
nesquena-hermes
85d0e522e0
Stamp v0.51.350 — Release LN (session-move / project-delete timeout fix) (#3923)
Co-authored-by: nesquena-hermes <nesquena-hermes@users.noreply.github.com>
2026-06-10 02:56:30 -07:00
nesquena-hermes
6be19804f5
fix(routes): bound session/move lock + safe project-delete unlink during streaming (#3746) (#3922)
Two distinct timeout causes, both surfacing as the client's 30s 'Request timed
out' toast with no server-side signal:

A) /api/session/move acquired the per-session agent lock with a bare unbounded
   'with _get_session_agent_lock(sid):'. The streaming thread holds that same
   lock during checkpoint saves; on slow file I/O (WSL/DrvFs) the move could
   block past the client abort. Now acquires with timeout=5 and returns HTTP 503
   on contention (lock kept, not dropped, since s.save() still races the writer).

B) /api/projects/delete unlinked every assigned session via get_session()+save()
   — O(N) full-messages reserialize. For an actively-streaming session we now
   clear project_id on the LIVE CACHED Session object under LOCK (the streaming
   thread persists it on its next save — the worker always does a final save at
   turn completion) instead of issuing a competing s.save(); falls back to a
   direct save when not cached. Non-streaming sessions unchanged.

Also guards the '+ New project and move' shortcut (sessions.js) against the new
503 so it shows a toast instead of an unhandled rejection, keeping the #2551
authoritative refetch in both the success and catch paths.

Adds tests/test_issue3746_session_move_delete_timeout.py (behavioral lock-timeout
test + structural guards for both handlers + the frontend 503 guard). Widened the
#2551 new-project-refetch test's fixed byte-window to a block-scoped search so the
try/catch wrap (which preserves the refetch) doesn't trip a brittle offset assertion.

Co-authored-by: nesquena-hermes <nesquena-hermes@users.noreply.github.com>
2026-06-10 02:53:01 -07:00
nesquena-hermes
6c83fbaa39
Stamp v0.51.349 — Release LM (custom-proxy model ID preservation) (#3919)
Co-authored-by: nesquena-hermes <nesquena-hermes@users.noreply.github.com>
2026-06-10 02:07:18 -07:00
nesquena-hermes
92e74c6cd3
fix(config): preserve intrinsic vendor prefix in model ID for custom proxy (#3872) (#3918)
A bare 'custom' provider with a remote base_url is a vendor-routing proxy
(LiteLLM, Bedrock gateway, etc.). A slashed model id like bedrock/opus-4-6 is
intrinsic — the proxy routes on the full string. The prior heuristic stripped
ANY known-provider prefix (bedrock is also a provider name), truncating it to
opus-4-6 and causing 403 'model not allowed'.

Fix: in the custom-base_url branch, strip the prefix only when (a) the configured
provider is a real first-party provider pointed at an OpenAI-compatible proxy
(provider=openai + proxy base_url — the #433 path), OR (b) for a bare 'custom'
provider, the bare id is genuinely a first-party model of that prefix's catalog
(openai/gpt-5.4 -> gpt-5.4, since gpt-5.4 IS an OpenAI model). An intrinsic
routing prefix whose bare id is NOT first-party of that namespace is preserved
(bedrock/opus-4-6 stays whole). Unknown prefixes (zai-org/...) preserved as before.

Both prior behaviors work in tandem with the fix:
  - #433 sprint40: bare custom + openai/gpt-5.4 -> gpt-5.4 (redundant, strip)
  - #433 model_resolver: provider=openai + google/gemma -> gemma (proxy strip)
  - #548: custom + zai-org/GLM-5.1 -> preserved (unknown prefix)
  - #1625: custom + loopback base_url -> preserved (local server)
  - #3872: custom + bedrock/opus-4-6 -> preserved (intrinsic vendor prefix)

Adds regression tests covering all of the above.

Co-authored-by: nesquena-hermes <nesquena-hermes@users.noreply.github.com>
Co-authored-by: haolf000 <haolf000@users.noreply.github.com>
2026-06-10 01:56:01 -07:00
nesquena-hermes
19080f73b1
Release v0.51.348 — Release LL (Phase 0 hotfix: timeout regression + data-loss + leaks) (#3917)
* stage v0.51.348: Phase 0 hotfix — approval/clarify timeout regression (#3913), queue/draft durability (#3906), settings auto-reopen (#3909), kanban FD leak (#3904)

* stage v0.51.348: re-anchor 4 SSE frontend tests to poll-only design (#3913); apply Opus SHOULD-FIX — immediate first poll tick so pending approval/clarify cards show instantly

---------

Co-authored-by: nesquena-hermes <nesquena-hermes@users.noreply.github.com>
2026-06-10 01:12:48 -07:00
nesquena-hermes
e8d71a2fb8
Release v0.51.347 — Release LK (streaming & render reliability cluster #3892 #3898 #3885 #3882 #3868) (#3902)
* stage v0.51.347: render/stream cluster (#3892 #3898 #3885 #3882 #3868) + 2 Opus SHOULD-FIX

* stage v0.51.347: trim #3885 error-guard comment to fit diagnostic-test window

* Stamp v0.51.347 — Release LK (streaming & render reliability cluster)

* Remove stray uv.lock accidentally staged (not part of any cluster PR)

---------

Co-authored-by: nesquena-hermes <[email protected]>
2026-06-09 17:02:35 -07:00
nesquena-hermes
aa5cd6e8ff
Release v0.51.346 — Release LJ (PWA notification controls #3229) (#3897)
* Add PWA notification controls (#3229, #3196)

Rebased onto fresh master (#3229 was 281 commits behind). Routes browser
notifications through the service worker (reg.showNotification) with a direct
Notification fallback, threads the originating session deep link/tag into the
payload, and adds Enable/Send-test/permission-status controls in Settings.
notificationclick prefers an already-open target-session tab before opening a
new window. Added the 6 new i18n keys to the Polish (pl) locale block, which
the original PR missed.

Co-authored-by: AJV20 <AJV20@users.noreply.github.com>

* Apply Opus review fixes to #3229 PWA notifications

- MUST-FIX: _showPwaNotification used navigator.serviceWorker.ready, which
  never settles when no SW registration ever activates (reverse-proxy MIME
  mishap, SW disabled) — every notification was silently dropped, a regression
  vs master. Switch to getRegistration() raced against a 2s timeout, falling
  back to new Notification().
- SHOULD-FIX: notificationclick matched client.url === targetUrl exactly, but
  _sessionUrlForSid copies the current query/hash into the deep link, so an
  already-open session tab missed the match and spawned a duplicate window.
  Compare pathnames instead.
- SHOULD-FIX: "Send test" with permission denied was a silent no-op — now
  surfaces the notifications_denied toast on the force path.
- SHOULD-FIX: permission-status span went stale after granting via the button
  — refresh it in requestNotificationPermission().then.
- Consistency: the request-permission grant branch now has the same
  new Notification fallback as the granted branch.
- Update test_pwa_notification_controls.py to assert the pathname match.

* Stamp v0.51.346 — Release LJ (PWA notification controls #3229/#3196)

---------

Co-authored-by: nesquena-hermes <[email protected]>
Co-authored-by: AJV20 <AJV20@users.noreply.github.com>
2026-06-09 15:54:21 -07:00
nesquena-hermes
cc7f8a274a
Release v0.51.345 — Release LI (model override picker for scheduled jobs #3809) (#3896)
Absorbs contributor PR #3809 (@b3nw), rebased onto fresh master (was ~20 behind,
panels.js conflict resolved by merging the new !isNoAgent skill-tags guard with
the model-select call).

Adds a Model Override dropdown to the Tasks scheduled-jobs create/edit form,
populated from /api/models grouped by provider, persisting model+provider,
clearable to default, disabled in no-agent mode. Surfaces hermes-agent's existing
per-job model override (CLI parity).

greptile P1s (override cleared on fast-save / on API failure) verified
ALREADY-FIXED in PR head; also applied an Opus UX hardening (keep the model
select disabled on a failed /api/models load so the user can't think they
cleared the override). UX approved by Nathan via screenshots.

Pre-merge fixes:
- i18n: the PR added the 3 cron_model_* keys to all locales but left 10 of them
  as 'TODO: translate' English stubs (only es was done), tripping
  test_zh_hant_locale. Provided real translations for de/zh/zh-Hant/ru/ja/fr/pl/
  it/pt/tr.
- test isolation: #3809's new test file shifts pytest-shard composition so
  test_issue2863's background-rebuild test ran after a test that leaves the
  #3884 _SESSION_INDEX_REBUILD_THREAD globals populated, suppressing the fresh
  thread it asserts on. Made that test hermetic (joins+clears the rebuild-thread
  globals up front) so it passes regardless of shard run order.

Co-authored-by: nesquena-hermes <[email protected]>
Co-authored-by: b3nw <b3nw@users.noreply.github.com>
2026-06-09 14:45:59 -07:00
nesquena-hermes
1cc8772179
Release v0.51.344 — Release LH (sidebar fork-lineage grouping #3799/#3884) (#3893)
* Release v0.51.344 — Release LH (sidebar fork-lineage grouping #3799/#3884)

Absorbs #3884 (@rodboev): manual forks are kept as sidebar lineage boundaries
so a forked session isn't collapsed under a compression-continuation root,
while enriched child-session rows stay independently visible until the later
attachment pass. Also addresses the greptile TOCTOU flag: the background
index-rebuild thread now pins + re-checks its (SESSION_DIR, SESSION_INDEX_FILE)
target under _SESSION_INDEX_REBUILD_LOCK before writing.

Rebased onto fresh master, content byte-identical to PR head, full-suite +
Codex + Opus gated.

Co-authored-by: rodboev <rodboev@users.noreply.github.com>

* fix(models): propagate target kwargs in index-rebuild fallback (Opus SHOULD-FIX)

Opus advisor stage-344: the _write_session_index fast-path fallback recursed
with _write_session_index(updates=None) and no kwargs, falling back to the
global SESSION_DIR. Safe today (the only kwargs-caller passes updates=None and
never reaches the fast path) but the invariant was implicit. Propagate the
resolved session_dir/session_index_file so a target-scoped rebuild falls back
to that same target.

---------

Co-authored-by: nesquena-hermes <[email protected]>
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-09 13:37:59 -07:00
nesquena-hermes
c89468212b
Release v0.51.343 — Release LG (Phase-1 batch: #3883 + #3878 + #3880) (#3891)
Phase-1 low-risk batch, each rebased onto fresh master + gated fresh:

- #3883 (@rodboev, #3740): sidebar refreshes a stale message_count:0 index row
  from its sidecar when user_message_count>0 + sidecar mtime newer than index,
  self-healing the interrupted-stream stale-count case beyond compression lineage.
- #3878 (@rodboev, #3833): manual workspace refresh clears the dir cache and
  re-fetches expanded descendants so background-written files become visible.
- #3880 (@koshikai): translate the 11 remaining English strings in the ja locale.

greptile flags evaluated: #3878 P1 relative-path + P2 stale-comment already fixed
in PR head; #3883 P2 missing-snapshot-test already covered by the PR's own
test_all_sessions_refreshes_stale_zero_count_snapshot_row_from_sidecar, P2
double-stat is a bounded cheap micro-opt (FOLD); #3880 'needs screenshots'
rejected (in-place translation of existing keys, no UI shape change).

Co-authored-by: nesquena-hermes <[email protected]>
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
Co-authored-by: koshikai <koshikai@users.noreply.github.com>
2026-06-09 13:03:15 -07:00