Commit graph

5299 commits

Author SHA1 Message Date
Frank Song
7e8834d8ca Fix tool-call tail ordering in session merge 2026-06-26 02:18:18 +00:00
nesquena-hermes
76eb548007
Release XY (v0.51.669): persist OpenAI priority service_tier (#4543, fixes #4536)
Release XY (v0.51.669): persist OpenAI priority service_tier in main-model settings (#4543, fixes #4536)
2026-06-25 19:12:18 -07:00
nesquena-hermes
b22bb85a6a Release XY (v0.51.669): persist OpenAI priority service_tier in main-model settings (#4543, fixes #4536) 2026-06-26 02:09:20 +00:00
nesquena-hermes
b2510efcb9 i18n(#4543): add service_tier keys to the vi locale (added to master after this PR; strict locale parity) 2026-06-26 01:51:59 +00:00
Rod Boev
61ea7360a0 fix(#4536): reject openai-codex provider before empty-model early return 2026-06-26 01:46:57 +00:00
Rod Boev
2f2d36d4eb fix(#4536): gate service tier on effective send model, not saved default 2026-06-26 01:46:57 +00:00
Rod Boev
4d64c18832 fix(#4536): block Codex models from priority service tier 2026-06-26 01:46:57 +00:00
Rod Boev
cae551f4a6 fix(#4536): match service-tier UI gate to backend 2026-06-26 01:46:57 +00:00
Rod Boev
6f9d23eb48 fix(#4536): gate service tier to real OpenAI model ids 2026-06-26 01:46:57 +00:00
Rod Boev
a340c69b08 fix(#4536): clear stale service tier on model switch 2026-06-26 01:46:57 +00:00
Rod Boev
24cf7069a6 fix(#4536): persist OpenAI priority tier in main model settings 2026-06-26 01:46:57 +00:00
nesquena-hermes
e96b95d8b2
Release XX (v0.51.668): WebUI<->Desktop session reconciliation (#4834, fixes #4833)
Release XX (v0.51.668): WebUI<->Desktop session reconciliation (#4834, fixes #4833)
2026-06-25 17:49:55 -07:00
nesquena-hermes
f6fc7fc4d1 Release XX (v0.51.668): WebUI<->Desktop session reconciliation (#4834, fixes #4833) 2026-06-26 00:47:00 +00:00
Frank Song
fda5a241b2 Fix sidebar reconciliation review issues 2026-06-26 00:36:15 +00:00
Frank Song
0e2214b6bd Fix WebUI state.db session reconciliation 2026-06-26 00:34:58 +00:00
nesquena-hermes
fce305993a
Release XW (v0.51.667): compaction-marker writeback + code-redaction fix (#4823, fixes #4821)
Release XW (v0.51.667): compaction-marker writeback + code-redaction fix (#4823, fixes #4821)
2026-06-25 17:26:29 -07:00
nesquena-hermes
a0e7f558fd Release XW (v0.51.667): compaction-marker writeback + code-redaction fix (#4823, fixes #4821) 2026-06-26 00:23:19 +00:00
Frank Song
6079b7ba24 Address compaction marker and redaction review 2026-06-26 00:12:09 +00:00
Frank Song
9f0864ea82 Fix compaction marker writeback and code redaction 2026-06-26 00:12:09 +00:00
nesquena-hermes
9dc1395adb
Release XV (v0.51.666): local approvals no longer 409 (#4950, #4771 regression, closes #4948)
Release XV (v0.51.666): local approvals no longer 409 (#4950, #4771 regression, closes #4948)
2026-06-25 15:39:43 -07:00
nesquena-hermes
6bac10c39d Release XV (v0.51.666): local approvals no longer 409 as gateway run unavailable (#4950, #4771 regression, closes #4948) 2026-06-25 22:36:50 +00:00
Nathan Esquenazi
bc12bb43c9 docs(changelog): add [Unreleased] entry for #4771 local-approval 409 fix
AGENTS.md requires a CHANGELOG entry for user-visible behavior changes, and
this is one (local-backend command approvals went from 409-on-every-click to
working). The PR shipped the routes.py gate fix without a changelog note; this
adds the [Unreleased] -> Fixed entry crediting b3nw / claw-io (#4948).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 15:32:21 -07:00
nesquena-hermes
bac8108b38 fix(#4771): don't 409 local approvals as "gateway run unavailable"
#4771 added an explicit relay-failure 409 (code: gateway_run_unavailable)
to /api/approval/respond so a gateway approval whose run is gone keeps its
card actionable instead of silently failing. But the 409 was emitted
regardless of backend.

On the DEFAULT local in-process backend, every guarded command parks an
entry in tools.approval._gateway_queues (via _await_gateway_decision), and
the local streaming notify callback mirrors it into _pending tagged with
_GATEWAY_MIRROR_FLAG. There is no gateway run and no _STREAM_RUN_IDS entry
by design. _gateway_pending_approval_without_run_id() therefore returned
True for that purely local approval, so the handler 409'd
("active run unavailable") and refused to resolve an approval that resolves
perfectly well locally. Local-backend users hit this on every approval
click (reported by b3nw on Discord; independently filed as #4948 by
claw-io, which also notes the local-runtime trigger).

Fix: gate the 409 on the WebUI actually running the gateway chat backend
(webui_gateway_chat_enabled). Local approvals fall through to the local
resolution path; the legitimate #4771 gateway behaviour is unchanged.

Tests:
- New tests/test_issue4771_local_approval_regression.py drives the real
  _handle_approval_respond handler and asserts: local backend -> 200 +
  agent thread unblocked; gateway backend -> 409 preserved. These fail
  before the fix (409) and pass after.
- The two existing #4771 tests that asserted the 409 are updated to pin
  HERMES_WEBUI_CHAT_BACKEND=gateway at respond time, since the 409 is now
  gateway-deployment behaviour.

Reported-by: b3nw
Reported-by: claw-io (#4948)
2026-06-25 22:21:56 +00:00
nesquena-hermes
8dd64eaa27
Release XU (v0.51.665): freeze CLI/cron sidebar cache key during streaming (#4908, #4842)
Release XU (v0.51.665): freeze CLI/cron sidebar cache key during streaming (#4908, #4842)
2026-06-25 15:11:59 -07:00
nesquena-hermes
6114edc7cd Release XU (v0.51.665): freeze CLI/cron sidebar cache key during streaming (#4908, #4842) 2026-06-25 22:09:03 +00:00
nesquena-hermes
7791370d49 docs(#4908): correct cache-invalidation comment per Opus gate — TTL is the backstop for externally-driven changes
Opus SHIP-WITH-FIXES (no code change required): the comment/docstring overstated
that the session-list-change listener covers ALL structural mutations. In-app
mutations fire it, but externally-driven changes (scheduled cron completion,
external CLI writes) don't — for those the 30s streaming TTL is the load-bearing
backstop (≤30s bounded, self-healing lag). Comment-only accuracy fix; logic
unchanged (both gates: perf fix correct, freeze effective, staleness bounded).
2026-06-25 21:57:36 +00:00
nesquena-hermes
b87a605306 perf(#4842): freeze the CLI/cron sidebar cache key during streaming
/api/sessions re-ran the expensive CLI/cron session projection on every
poll while a turn was streaming — a major cause of the multi-second sidebar
latency + 100% CPU on cron-heavy installs (continuing #4672/#4808/#4889).

Root cause: the CLI/cron projection is gated by _CLI_SESSIONS_CACHE, whose
key folds in _sqlite_file_stat_cache_key -> _sqlite_content_fingerprint
(MAX(rowid) FROM messages). During a live turn the gateway writes a message
row per streamed delta, so that fingerprint advances on essentially every
~5s poll — busting the cache and re-running the full candidate-join +
projection (and the lineage-metadata pass) on every poll, contending for the
same SQLite/global lock the streaming worker holds.

The route-level session-list cache already froze its key during streaming
(#4808 _session_list_cache_streaming_freeze_marker), but that freeze never
reached this inner CLI-sessions cache, so the heavy CLI/cron query still
re-ran whenever the outer cache validated.

Fix: while any stream is active, the CLI-sessions cache key folds in the same
stable streaming-freeze marker (keyed only on the set of active stream ids)
instead of the volatile content fingerprint, and the cache TTL widens to a
streaming window. The projection is reused across polls mid-stream and
rebuilt at most once per window. The instant a stream starts/stops the marker
changes, so freshly-finished rows surface promptly. Structural mutations
(cron completion, new/renamed/archived sessions, attention) now also clear
the CLI cache via the existing session-list-changed listener — those signals
never fire per streamed token, which is what makes the freeze safe.

Idle behavior is byte-identical (freeze only engages with active streams).
Applies to both the active-profile and all-profiles cache paths.

Adds tests/test_issue4842_cli_sessions_streaming_freeze.py (6 tests): marker
idle/stable/changes semantics, the core 'key stable across message writes
while streaming' guarantee, idle key still advances with the fingerprint,
streaming TTL > idle TTL, and the structural-mutation listener clears the
CLI cache.
2026-06-25 21:57:36 +00:00
nesquena-hermes
d1106c83e5
Release XT (v0.51.664): transparent stream keeps prose chronological for mixed turns (#4932, #3397)
Release XT (v0.51.664): transparent stream keeps prose chronological for mixed turns (#4932, #3397)
2026-06-25 14:55:03 -07:00
nesquena-hermes
79008b9333 Release XT (v0.51.664): transparent stream keeps prose chronological for mixed turns (#4932, #3397) 2026-06-25 21:52:09 +00:00
nesquena-hermes
30dfd40320 test(#4932): re-anchor #4927 derived-rebuild slice to fallbackToolSources
#4932 added a second 'const resultsByTid={}' (in _collectToolResultSnippetsByTid)
EARLIER in ui.js than the renderMessages derived rebuild, so the #4927 test's
UI_JS.index('const resultsByTid={};') sliced the wrong region and 4 assertions
failed. Re-anchor on 'const fallbackToolSources=[];' which is unique to the
derived-rebuild block. Pure test fix, no behavior change.
2026-06-25 21:40:25 +00:00
nesquena-hermes
c4b9edc84e fix(#4932): wire ordered transparent path to #4927 durable snippet fallback + gate scans to transparent mode
Codex+Opus gate (both reproduced): the new mixed-content ordered render path
resolved tool snippets only from the S.messages join (_collectToolResultSnippetsByTid)
and never consulted persistedSnippetByTid (S.session.tool_calls) — so on a
cold/paginated load where the tool_result join misses (#4927 scenario) the inline
card rendered an EMPTY body, AND its tid was added to transparentOrderedToolIds,
suppressing the post-loop derived card that WOULD have recovered via #4927. Build
a transparentPersistedSnippetByTid map from S.session.tool_calls and thread it as a
5th arg to _transparentOrderedToolCall, falling back in both the live and non-live
branches. Also gate the transparent-only scans (S.toolCalls + _collectToolResultSnippetsByTid)
behind isTransparentStream() so non-transparent renders skip the O(messages×parts)
work (Opus perf finding). Redaction (#4926/#4928) was already safe (shared buildToolCard).
+ cold-load persisted-fallback regression test.
2026-06-25 21:21:28 +00:00
Rod Boev
3a406512b0 Retrigger stalled CI for transparent-stream follow-up 2026-06-25 21:11:56 +00:00
Rod Boev
5f1a2a07de Keep settled transparent turns aligned with anchor-scene ownership 2026-06-25 21:11:56 +00:00
Rod Boev
bef89969ed fix(chat): preserve recycle harness anchors after prose split (#3397) 2026-06-25 21:11:56 +00:00
Rod Boev
b590518cf4 fix(chat): preserve transparent stream prose order for mixed turns (#3397) 2026-06-25 21:11:55 +00:00
nesquena-hermes
ada386acd4
Release XS (v0.51.663): show mobile reload button outside standalone mode (#4939, fixes #4784)
Release XS (v0.51.663): show mobile reload button outside standalone mode (#4939, fixes #4784)
2026-06-25 13:55:08 -07:00
nesquena-hermes
3bc712af97 Release XS (v0.51.663): show mobile reload button outside standalone mode (#4939, fixes #4784) 2026-06-25 20:51:39 +00:00
Rod Boev
779d46a1cf fix(#4784): show the reload button outside standalone mode 2026-06-25 20:41:46 +00:00
nesquena-hermes
a569d6ba3b
Release XR (v0.51.662): ignore DOM-wipe scroll clamp during render (#4934)
Release XR (v0.51.662): ignore DOM-wipe scroll clamp during render (#4934)
2026-06-25 13:38:51 -07:00
nesquena-hermes
2c1b0f00fd Release XR (v0.51.662): ignore DOM-wipe scroll clamp during render (#4934) 2026-06-25 20:35:20 +00:00
allenliang2022
ef6901c947 fix(chat): ignore DOM-wipe scroll clamp during render 2026-06-25 20:19:00 +00:00
nesquena-hermes
a06a63a9c2
Release XQ (v0.51.661): no fabricated turn duration from stale pending_started_at (#4930)
Release XQ (v0.51.661): no fabricated turn duration from stale pending_started_at (#4930)
2026-06-25 13:14:30 -07:00
nesquena-hermes
2e387d0ed1 Release XQ (v0.51.661): no fabricated turn duration from stale pending_started_at (#4930) 2026-06-25 20:11:01 +00:00
nesquena-hermes
12d1cec90a fix(#4930): don't render a fabricated turn duration from a stale pending_started_at
A settled turn that recorded no live duration fell back to
(Date.now()/1000) - session.pending_started_at. But pending_started_at is the
start of an IN-FLIGHT turn, so for a settled turn it is either stale (a session
that sat idle) or about a different pending turn — rendering a bogus
'Processed 15h 32m' on fresh conversations (#4930). Gate the fallback on a turn
actually being in flight (active_stream_id || pending_user_message); otherwise
return undefined (no duration shown) rather than a fabricated one. The recorded
live duration (_turnDuration / base.turn_duration) still takes priority, so this
only removes bogus durations, never a correct one.
2026-06-25 19:53:19 +00:00
nesquena-hermes
9d1b6617c2
Release XP (v0.51.660): durable tool output/diff body on cold reload (#4927, completes #4925)
Release XP (v0.51.660): durable tool output/diff body on cold reload (#4927, completes #4925)
2026-06-25 12:44:48 -07:00
nesquena-hermes
c9c3d8b8e9 Release XP (v0.51.660): durable tool output/diff body on cold reload (#4927, completes #4925) 2026-06-25 19:41:51 +00:00
nesquena-hermes
4030dca989 fix(#4927): persist loaded session.tool_calls so the cold-load fallback has a source
Codex gate (the key one): the persistedSnippetByTid fallback read S.session.tool_calls,
but loadSession() keeps the messages=0 session object (tool_calls []), and the
real messages=1 summary arrives only as the sessionToolCalls argument to
_syncToolCallsForLoadedMessages() — never copied onto S.session. So the fallback
source was empty on exactly the cold-load path it repairs. Persist
sessionToolCalls onto S.session.tool_calls in _syncToolCallsForLoadedMessages
(covering _ensureMessagesLoaded, older-message paging, and full-history loaders)
so renderMessages builds a populated persistedSnippetByTid. + guard test.
2026-06-25 19:29:06 +00:00
nesquena-hermes
0caa57d8b7 fix(#4927): cover the Anthropic tool_use derived push with the persisted fallback
Codex gate: the third derived-push site (Anthropic tool_use content-array branch,
ui.js) still read resultsByTid[tid]||'' without the persisted fallback, so
Anthropic-format sessions could still cold-reload with an empty tool body. Add
the persistedSnippetByTid fallback there too; now all derived result-snippet
reads (OpenAI top-level, Anthropic tool_use, partial) chain to the durable
persisted snippet. Added a guard test asserting EVERY derived resultSnippet read
includes the fallback.
2026-06-25 19:23:31 +00:00
nesquena-hermes
dad0a3554d fix(#4927): durable fallback for tool output/diff body on cold reload
In the transparent stream, terminal output and patch diffs intermittently
vanished after a cold/paginated reload. A settled tool row renders its body from
`snippet`, but the raw-assistant-envelope 'derived' rebuild only joined the
result by tool id via resultsByTid (role:tool messages); when that join missed
(id mismatch, recovery-rebuilt turn) the snippet was empty, and #4622's live
enrichment only helps when S.toolCalls still matches (empty on cold load).

Build a persistedSnippetByTid lookup from the durable session.tool_calls compact
summary (which carries a bounded result snippet per tid) and use it as a
fallback in all three derived-push sites, so the persisted record is the
reliable source and the live join becomes a fast-path, not the only path that
shows the body. Part of #4925.
2026-06-25 19:14:23 +00:00
nesquena-hermes
ccab526528
Release XO (v0.51.659): one-click extension install works with no env config (#4935, closes #4933)
Release XO (v0.51.659): one-click extension install works with no env config (#4935, closes #4933)
2026-06-25 11:59:25 -07:00