Commit graph

5285 commits

Author SHA1 Message Date
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
nesquena-hermes
3e5446ff8a Release XO (v0.51.659): one-click extension install works with no env config (#4935, closes #4933) 2026-06-25 18:55:33 +00:00
nesquena-hermes
0562bdbd31 fix(#4933): one-click extension install works with no env (bootstrap managed default root)
Settings -> Extensions offered an Install button even when extensions were
unconfigured, then dead-ended on 'Extensions not configured' because
install_extension required HERMES_WEBUI_EXTENSION_DIR to be set to an existing
directory. Default to a WebUI-managed root under STATE_DIR/extensions and
bootstrap it on first install so install just works with zero config; the env
var remains an optional override (never auto-created). Gallery-installed
extensions already auto-load via the runtime manifest, so the extension loads
on the next app-shell render with no further setup.

- api/extensions.py: _default_extension_root + _writable_extension_root;
  _extension_root/_extension_root_status fall back to the managed default;
  install_extension bootstraps via _writable_extension_root; dir-unavailable
  warning only fires for an explicitly-set-but-broken env path.
- tests: update disabled-by-default expectations (configured=True, valid=False
  pre-install) + new regression proving install bootstraps + auto-loads with
  no env.
- docs/EXTENSIONS.md: document zero-config one-click install + env now optional.
- CHANGELOG [Unreleased].
2026-06-25 18:45:09 +00:00
nesquena-hermes
670fe37f4c
Release XN (v0.51.658): tool cards keep long content args + reconstruct diffs (#4928, #4925)
Release XN (v0.51.658): tool cards keep long content args + reconstruct diffs (#4928, #4925)
2026-06-25 11:42:30 -07:00
nesquena-hermes
7d5d98a005 Release XN (v0.51.658): tool cards keep long content args + reconstruct diffs (#4928, #4925) 2026-06-25 18:39:00 +00:00
nesquena-hermes
f79c975842 fix(#4928): redact tool args at all render/copy sites now that content args are full-length
Codex gate: raising content args from 120->4000 chars newly surfaces secrets on
non-first lines / past char 120 in render paths that print tc.args directly and
bypass the #4926 label redaction. Apply _redactToolTargetLabel to arg values at
every such site: the transparent Full-tab args render (_transparentToolDetailHtml),
the non-transparent tool-card args block (buildToolCard visibleArgs), the
clipboard copy path (_copyEventToClipboard), and the _toolArgsSnapshot itself.
+ node regression proving a secret past char 120 in a long command is masked in
the Full tab while the non-secret command structure is retained.
2026-06-25 18:28:06 +00:00
nesquena-hermes
bd6e57fb42 fix(#4928): don't cap tool-call content args to 120 chars
Tool-call args were truncated to 120 chars at three sites (_truncate_tool_args,
the live _tool_args_snapshot in api/streaming.py, and _toolArgsSnapshot in
static/ui.js). For incidental args that's fine, but content/diff-bearing keys
(command/cmd/script/code/patch/diff/old_string/new_string/content/path/
file_path) feed the rendered tool card AND the diff reconstruction in
recovery-rebuilt sessions, so the cap silently corrupted long commands/paths and
produced broken/empty diffs. Exempt those keys with a much larger cap
(_TOOL_ARG_CONTENT_CAP = _TOOL_RESULT_SNIPPET_MAX = 4000) at every site, keyed by
key name; incidental args keep the 120 cap; storage stays bounded. Part of #4925.
2026-06-25 18:18:17 +00:00
nesquena-hermes
55a2e698a9
Release XM (v0.51.657): expanded shell tool card full command + broadened redaction (#4926, #4925)
Release XM (v0.51.657): expanded shell tool card shows full command + broadened redaction (#4926, #4925)
2026-06-25 11:11:36 -07:00
nesquena-hermes
a5bf6b9f76 Release XM (v0.51.657): expanded shell tool card shows full command + broadened redaction (#4926, #4925) 2026-06-25 18:08:07 +00:00
nesquena-hermes
64cb4cef4e fix(#4926): env-assignment redaction must not eat URL query tails
Codex re-gate: the env/AUTH '=' redaction regex matched 'token=' INSIDE a URL
(?token=...&mode=debug&signature=...) before the dedicated URL-query redactor,
and its \S+ value swallowed the rest of the URL incl later params + closing
quote. Anchor the env-assignment match to a shell-token boundary (^|[\s;|(]) so
it can't match keys preceded by ? or & — URL query params are left to the
dedicated query-param redactor, which masks the secret-looking ones while
preserving the command tail. + regression tests (URL-tail-preserved,
benign-PATH-not-over-redacted).
2026-06-25 17:58:12 +00:00
nesquena-hermes
ed21bd20cd fix(#4926): broaden _redactToolTargetLabel to mask env/flag/header secrets in the full command
Codex gate: exposing the full multi-line command in the expanded shell card
newly surfaces secrets on non-first lines (the collapsed header only ever showed
line 1). The old redactor masked only password-shaped fields. Broaden it to mask
common secret forms across the WHOLE command: env/colon assignments for
*TOKEN/*API_KEY/*SECRET/*ACCESS_KEY/*PRIVATE_KEY/*CREDENTIAL/*CLIENT_SECRET/
*SESSION_KEY and *AUTH*=, --token/--api-key/--secret style flags,
Authorization: Bearer/Bot/Token headers, and secret-looking URL query params.
Test now drives the REAL redactor (not an identity stub) and asserts non-first-line
secrets are masked while the collapsed header stays first-line.
2026-06-25 17:52:41 +00:00
nesquena-hermes
99b6a5e30b fix(#4926): expanded shell tool card shows the full multi-line command
In the transparent activity stream, a shell tool card's collapsed header
correctly shows only the first line of the command, but the EXPANDED detail
lead (`$ <command>`) reused that same first-line-only value via
_toolTargetLabel, so a multi-line script still rendered as line 1 when expanded.
The full command is available frontend-side; the truncation was purely in the
label derivation.

Adds _toolFullCommandLabel(tc) (full multi-line, redacted, no first-line
collapse) and uses it in _toolDetailLeadText for the shell kind, while the
collapsed header keeps the first-line _toolTargetLabel. Part of #4925.
2026-06-25 17:39:05 +00:00
nesquena-hermes
773a36dd91
Release XL (v0.51.656): stream writeback timing diagnostics (#4923, #4918)
Release XL (v0.51.656): stream writeback timing diagnostics (#4923, #4918)
2026-06-25 09:58:27 -07:00
nesquena-hermes
7d5a7ebd2f Release XL (v0.51.656): stream writeback timing diagnostics (#4923, #4918) 2026-06-25 16:54:53 +00:00
nesquena-hermes
f377215a0d harden: wrap _log_stream_writeback_timings in try/except so a misbehaving log handler can't skip the bg-title thread (Opus gate nicety, #4923) 2026-06-25 16:54:20 +00:00