Commit graph

3607 commits

Author SHA1 Message Date
nesquena-hermes
b6e4899887
Merge pull request #3353 from nesquena/release/stage-batch11
v0.51.199: pinned-scroll recovery + inline-math currency false-positive
2026-06-01 12:25:17 -07:00
nesquena-hermes
5d4ce75041 fix(#3330): init scroll-intent timestamps to -Infinity so load-time isn't read as intent
Codex follow-up finding: _lastMessageUpwardIntentMs/_lastNonMessageScrollIntentMs
initialized to 0, so _recentMessageUpwardIntent() returned true for the first 2s
after load (performance.now() < MESSAGE_UPWARD_INTENT_MS=2000) even with no user
scroll — which would disable the new #3319 retry guard during initial load. Sentinel
-Infinity makes 'no event recorded yet' read as no-intent. Also tightens the
pre-existing scrollIfPinned/settle callers that read the same helpers.
2026-06-01 19:19:08 +00:00
nesquena-hermes
3160b7bc36 docs: note digit-leading inline-math workaround in v0.51.199 changelog (Opus nit) 2026-06-01 19:18:08 +00:00
nesquena-hermes
fd3c4696e7 fix(#3330): guard the pinned-scroll rAF retry against user scroll-up during streaming
Codex regression-gate finding: the new requestAnimationFrame retry in
_setMessageScrollToBottom re-asserted scrollTop=scrollHeight + _scrollPinned=true
on the next layout frame unconditionally, so a user who scrolled up in that ~16ms
window during streaming would be snapped back and re-pinned, bypassing the
scrollIfPinned early-return guards. Re-check _messageUserUnpinned / upward-intent /
non-message-scroll-intent / !_scrollPinned inside the retry; on hit, only release
the programmatic-scroll latch and bail.
2026-06-01 19:14:14 +00:00
nesquena-hermes
14e447964f docs: v0.51.199 release header (stage-batch11 — #3330 #3311) 2026-06-01 19:08:51 +00:00
nesquena-hermes
04ce7599e3 stage-batch11: #3330 pinned-scroll + #3311 inline-math currency
#3330 Fix pinned chat scroll after message rebuild
Co-authored-by: jianongHe <jianongHe@users.noreply.github.com>

#3311 fix: reject inline math when $ is followed by a digit (currency)
Co-authored-by: toanalien <toanalien@users.noreply.github.com>
2026-06-01 19:08:30 +00:00
nesquena-hermes
9cad8980ff
Merge pull request #3350 from nesquena/release/stage-batch10
v0.51.198: custom-provider reasoning model-id normalize + profile skill counts + run-adapter RFC slice
2026-06-01 11:41:21 -07:00
nesquena-hermes
76da1d144a fix(profiles): TOCTOU-safe _SKILLS_STATS_CACHE read via .get() (Opus pre-release nit on #3341) 2026-06-01 18:38:39 +00:00
nesquena-hermes
49fce9af16 docs: v0.51.198 release header (stage-batch10 — #3327 #3334 #3341) 2026-06-01 18:24:25 +00:00
nesquena-hermes
de22c607bc stage-batch10: #3327 model-id normalize + #3334 RFC slice doc + #3341 profile skill counts
#3327 fix(reasoning): normalize custom-provider model ids for fallback heuristics
Co-authored-by: Carry00 <Carry00@users.noreply.github.com>

#3334 docs(rfc): mark run-adapter Slice 4f shipped, define Slice 4g gate
Co-authored-by: Michaelyklam <Michaelyklam@users.noreply.github.com>

#3341 fix(profiles): show enabled vs compatible skill counts
Co-authored-by: b3nw <b3nw@users.noreply.github.com>
2026-06-01 18:24:05 +00:00
nesquena-hermes
c5aed7654c
Merge pull request #3349 from nesquena/release/stage-batch9
v0.51.197: stop agent replaying edited/undone messages (#3102)
2026-06-01 11:21:09 -07:00
nesquena-hermes
bce1f9430b fix(#3102): don't let persisted watermark permanently drop future state.db recovery rows
Codex regression-gate finding: since Session.save() no longer auto-clears the
truncation_watermark, the unconditional 'timestamp > watermark' skip in
merge_session_messages_append_only became a permanent ceiling — a genuine future
state.db-only row (recovery/compaction, missed by the sidecar) would be silently
dropped from /api/session and model-context reconstruction forever. Only apply the
above-watermark skip while the sidecar has NOT advanced past the watermark. Preserves
the #2914 deleted-tail filtering (revert-verified). Adds 2 regression tests.
2026-06-01 18:14:48 +00:00
nesquena-hermes
a755786314 docs: v0.51.197 release header (stage-batch9 — #3102 only; #3268 + #3300 dropped pending fixes) 2026-06-01 18:05:17 +00:00
nesquena-hermes
778652713c Fix #2914: stop agent replaying edited/undone messages; sync context_messages truncation (#3102)
Co-authored-by: AlexeyDsov <AlexeyDsov@users.noreply.github.com>
2026-06-01 18:05:02 +00:00
nesquena-hermes
65d1728c81
Merge pull request #3348 from nesquena/release/stage-batch8
v0.51.196: file-manager external sessions + artifacts tool metadata + edge-toggle icon + type hints
2026-06-01 10:34:48 -07:00
nesquena-hermes
1c4365ce2a fix(artifacts): guard malformed tool_calls entries in collectSessionArtifacts (#3329)
Codex regression-gate finding: the OpenAI tool_calls loop dereferenced
tc.function with no null/type guard, so a persisted message.tool_calls
array containing a null or non-object entry would throw and abort artifact
collection. Mirror the existing tool_use-block guard. Adds a node-driven
regression test.

Co-authored-by: mysoul12138 <mysoul12138@users.noreply.github.com>
2026-06-01 17:27:02 +00:00
nesquena-hermes
89a72c2e3b docs: v0.51.196 release header (stage-batch8 — #3314 #3329 #3318 #3323) 2026-06-01 17:20:17 +00:00
kuishou68
23c51e8e64 fix: use Optional[T] for default None parameters in state_sync.py
Fixes type annotations where non-optional types (str, int) are used with
None defaults. All affected parameters now use Optional[T] = None.

Closes #3322
2026-06-01 17:20:17 +00:00
Sanjay Santhanam
92bddca3c7 fix: file manager falls back to state.db for external Telegram/CLI sessions (#3280) 2026-06-01 17:19:48 +00:00
mysoul12138
fae5ada40d fix: Artifacts tab cannot open files when messages carry structured tool metadata
Two bugs prevented clicking Artifacts entries from opening files:

1. collectSessionArtifacts() only read S.toolCalls, but
   _syncToolCallsForLoadedMessages clears it when messages carry
   their own tool_calls/tool_use metadata. Fix: also scan messages'
   structured tool data (OpenAI tool_calls array + Anthropic
   tool_use content blocks).

2. openArtifactPath() only stripped ~/ and ./ prefixes. When artifact
   paths were absolute (e.g. /mnt/.../workspace/file.js), /api/list
   received the full absolute path and returned 404. Fix: strip the
   session workspace prefix before calling _workspacePathExists.

Bonus: renderSessionArtifacts() now displays workspace-relative paths
instead of cluttered absolute paths in the artifact list.
2026-06-01 17:19:48 +00:00
xz-dev
2303aa1023 Fix workspace panel edge arrow direction 2026-06-01 17:19:48 +00:00
nesquena-hermes
1fcd81e38e
Merge pull request #3310 from nesquena/release/stage-batch7
v0.51.195: hide attachment path markers in chat UI (#3296)
2026-05-31 22:40:22 -07:00
nesquena-hermes
c1a3c3ef28 docs: v0.51.195 release header (stage-batch7 — #3296 hide attachment path markers) 2026-06-01 05:32:33 +00:00
AJV20
ec704356ac fix: hide attachment path markers in chat UI 2026-06-01 05:31:53 +00:00
nesquena-hermes
36fac9d0a3
Merge pull request #3309 from nesquena/release/stage-batch6
v0.51.194: profiles config-import-cycle fix (#3303)
2026-05-31 22:13:10 -07:00
nesquena-hermes
1cf6460cd9 docs: v0.51.194 release header (stage-batch6 — #3303 only; #3300 dropped pending compaction-marker fix) 2026-06-01 05:05:29 +00:00
AJV20
d8471a7d87 fix(profiles): avoid config import cycle 2026-06-01 05:05:16 +00:00
nesquena-hermes
4591f53b0e
Merge pull request #3308 from nesquena/release/stage-batch5
v0.51.193: ctl dotenv opt-out + workspace inline-open + gateway reply polish (3 PRs)
2026-05-31 21:53:32 -07:00
nesquena-hermes
7003acc036 docs: v0.51.193 release header (stage-batch5) 2026-06-01 04:42:42 +00:00
AJV20
ca99d12e04 fix: keep WebUI reply guidance language-neutral 2026-06-01 04:42:23 +00:00
AJV20
7a1b2bfe72 fix: carry WebUI reply polish into gateway chat 2026-06-01 04:42:23 +00:00
xz-dev
239f913485 Fix workspace open in browser inline sandbox 2026-06-01 04:42:05 +00:00
AJV20
18eea5b2ab test(ctl): allow dotenv-free launches 2026-06-01 04:42:05 +00:00
nesquena-hermes
763756ca55
Merge pull request #3302 from nesquena/docs/markdown-refresh
docs: refresh markdown, regenerate contributor tally (137→194), README IA pass
2026-05-31 19:40:01 -07:00
nesquena-hermes
099dcf52fe test: point gateway_chat doc-contract test at docs/advanced-chat-setup.md
The README IA pass moved the Gateway-backed-chat operator docs out of the README
into docs/advanced-chat-setup.md (niche self-hosted feature). The contract test
test_gateway_chat_health_payload_is_documented_as_operator_diagnostic_only
asserted those phrases live in README.md — update it to follow the content to its
new home (CHANGELOG entry unchanged). Contract test moves with the content per
docs/CONTRACTS.md. Full suite: 7138 passed, 0 failed.
2026-06-01 02:36:25 +00:00
nesquena-hermes
ec168b3c67 docs(readme): re-sequence IA — pull Features up, consolidate access, extract niche docs
Information-architecture pass on the 840-line README so the most important
things come first and secondary/niche content is linked rather than inline:

- Reorder: Why -> Quick start -> FEATURES (was at line 502, now right after
  Quick start) -> Configuration & access -> Docker -> Running tests ->
  Architecture -> Docs -> Contributors. Readers see what it does before the
  deployment minutiae.
- Consolidate the scattered access sections (start.sh discovery, overrides,
  remote/SSH, Tailscale, manual launch) under one '## Configuration & access'
  H2 with H3 subsections.
- Extract two genuinely-niche blocks to new linked docs (nothing deleted):
  - docs/advanced-chat-setup.md — dynamic recall-prefill + Gateway-backed chat
  - docs/remote-access.md — SSH tunnel + Tailscale + ARM64-Android field report
  Quick start keeps a one-line pointer to each.
- Update Contents TOC + Docs index for the new order and new files.

README 840 -> 705 lines; content preserved (verified moved-not-dropped); all
internal links + new docs verified to resolve; docs/*.md gitignore-allowlisted.
2026-06-01 02:26:18 +00:00
nesquena-hermes
9d1b7b1d52 docs: freshen ROADMAP + ARCHITECTURE content + README notable-contrib counts
- ROADMAP.md: 8-themes -> 11-skins in status snapshot; replace stale per-file
  LOC architecture table with role descriptions + CI gate list (ruff/shard/
  browser/docker); note LOC drifts.
- ARCHITECTURE.md: refresh File Inventory — drop chronically-stale per-file LOC,
  add missing modules (state_sync.py, updates.py, i18n/login/icons/sw.js,
  pyproject.toml, ctl.sh, CONTRIBUTORS.md), refresh test count to ~7,150.
- README.md: update the 8 'Notable contributions' blurb headers (PR counts +
  release ranges) to match the regenerated tally.
2026-06-01 02:16:57 +00:00
nesquena-hermes
f14efc4368 docs: regenerate contributor tally (137->194, union, nobody dropped)
Regenerate CONTRIBUTORS.md + README contributors section from a verified
3-source union: GitHub merged-PR list, CHANGELOG.md attribution lines, and
Co-authored-by trailers on master commits (the canonical signal for a CLOSED
contributor PR whose commits were cherry-picked/absorbed and attributed).

- New tally: 194 contributors / 843 PR credits (was a stale 137 / 646).
- The increase: ~135 releases since the v0.51.58 pin PLUS newly-detected
  absorbed-CLOSED PRs the prior hand-count missed (e.g. franksong2702 148 =
  129 merged + 19 cherry-picked-and-attributed).
- UNION with the existing hand-curated file as a floor: 27 old contributors
  had no machine-readable signal (very old closed PRs) — preserved, ZERO dropped.
- Refreshed special-thanks PR counts to match.
- Generator committed to the maintainer workspace as scripts/regen_contributors.py
  (--merge-existing keeps it safe for all future refreshes).

Verified: every one of the original 137 logins still present (+57 new).
2026-06-01 02:14:01 +00:00
nesquena-hermes
31fcdfa39a docs: refresh + reorganize README and key markdown files
README:
- Add a Contents table of contents for navigability (800-line doc).
- Freshen stale snapshots: test count 5303/488 files -> ~7,150/~700 files;
  reframe contributor counts to point at CONTRIBUTORS.md as the live source.
- Rebuild the Architecture section: drop per-file exact LOC (drifts every
  release -> chronic staleness) in favor of a stable backend/frontend role map;
  add pyproject.toml + the ruff/browser/docker CI gates.
- Reorganize the Docs index by purpose (Start here / Using / Deploying /
  Contributing & design / Release history); add missing CONTRIBUTING.md,
  DESIGN.md, docs/workspace-git.md; convert bare paths to working links.

ARCHITECTURE.md: header v0.51.54/5303 -> v0.51.192/~7,150 + note that the
  numbers are a periodic snapshot (authoritative source = git tag + collect-only).

TESTING.md: header + footer test counts refreshed; drop the stale
  'through v0.50.21' framing; note the ruff/browser/docker gates.

ROADMAP.md: refresh the 'Last updated' stamp (v0.51.31/5028 -> v0.51.192/~7,150)
  with recent themes.

Markdown-only; all internal links verified to resolve.
2026-06-01 01:42:20 +00:00
nesquena-hermes
4c7c65192c
Merge pull request #3298 from nesquena/release/stage-batch4
v0.51.192: per-model context_length default-only guard (#3263, closes #3256) (stage-batch4)
2026-05-31 18:28:07 -07:00
nesquena-hermes
7e9f674edf fix(#3263): provider-compatible default match — reject same-bare-name on different providers (Codex over-match MUST-FIX)
Prior round stripped provider prefixes from both sides and matched bare-only,
which over-matched: openai/gpt-4o would match default openrouter/gpt-4o. Now the
matcher compares bare model ids AND rejects when both sides identify DIFFERENT
providers (from provider/ prefix, @provider: qualifier, or the explicit provider
arg). Same-provider / unknown-session-provider still match. Added cross-provider
rejection regression tests.

Co-authored-by: allenliang2022 <allenliang2022@users.noreply.github.com>
2026-06-01 01:19:49 +00:00
nesquena-hermes
4147e7f3df fix(#3263): provider-aware default-model match (Codex final-gate MUST-FIX)
The default-only context_length guard compared model.default to the session
model with exact string equality. But model.default and the session model can
be stored in equivalent-but-different shapes (bare 'claude-opus-4.8',
provider-prefixed 'anthropic/claude-opus-4.8', or '@anthropic:claude-opus-4.8').
An exact compare wrongly treats the actual default model as non-default and
drops its configured context_length cap for provider-prefixed configs.

Add api/routes._model_matches_configured_default(session_model, cfg_default,
provider) that normalizes all three shapes, and use it at all 6 guard sites
(routes resolver + the 5 api/streaming.py sites: live-usage snapshot, persistence
_skip_cc_cl, persistence fallback _apply_cfg_ctx, SSE-done _dropped_stale_cap_sse,
SSE fallback _apply_cfg_ctx). Imported function-scoped in streaming to avoid the
routes<->streaming module-level circular import. 10 helper unit tests + a
behavioral test that a prefixed default still receives its cap.

Co-authored-by: allenliang2022 <allenliang2022@users.noreply.github.com>
2026-06-01 01:14:22 +00:00
nesquena-hermes
17e7794923 fix(#3263): init _skip_cc_cl/_cc_cl before compressor block (UnboundLocalError on no-compressor path) + relax brittle #1318 source-assertion
The threshold-rescale block runs unconditionally after the fallback and
references _skip_cc_cl/_cc_cl, which were only defined inside 'if _cc_for_save:'.
On the no-compressor path (fresh agent / interrupted stream) that raised
UnboundLocalError (caught by test_issue1857_usage_overwrite). Hoist both inits
above the block (no-op rescale when no compressor). Also widen the #1318
source-assertion test to accept the widened fallback gate (still asserts the
falsy-check invariant).

Co-authored-by: allenliang2022 <allenliang2022@users.noreply.github.com>
2026-06-01 00:14:13 +00:00
nesquena-hermes
18a459726b fix(#3263): apply Codex dual-gate MUST-FIX — recompute persisted stale cap + rescale terminal threshold
Codex regression gate (+Opus, both independently) found the default-only guard
dropped the stale compressor cap but two sibling paths stayed inconsistent:
1. Per-turn persistence: fallback resolver only ran when context_length was
   falsy, so a previously-persisted stale 232K survived forever on non-default
   sessions. Now also runs when _skip_cc_cl, and rescales threshold_tokens to
   the recomputed real cap (or clears it).
2. Terminal SSE 'done' payload: re-emitted the stale compressor threshold, so
   messages.js overwrote S.lastUsage and the indicator reverted on stream end.
   Now rescales threshold to the resolved window when the stale cap was dropped.
Added 3 source-structure regression tests pinning both fixes; bumped the brittle
test_pr1341 distance limit 13000→15000 (+ noted it should become structural).

Co-authored-by: allenliang2022 <allenliang2022@users.noreply.github.com>
2026-06-01 00:09:21 +00:00
nesquena-hermes
7a734462b0 docs(changelog): v0.51.192 Release FL (stage-batch4) 2026-05-31 23:52:28 +00:00
nesquena-hermes
5768a4bfc8 test(#3263): bump brittle save-distance limit + add default-only context_length guard regression test
- test_pr1341 distance limit 9000→13000 (the PR legitimately added the
  default-only pre-save guard block; the test is designed to be bumped when a
  new pre-save mutation block lands — this was the only CI-red cause on shard 1).
- new tests/test_issue3256_context_length_default_only_guard.py: verifies the
  global model.context_length cap applies ONLY to model.default (revert-fix-
  verified — fails on master, passes with the fix).

Co-authored-by: allenliang2022 <allenliang2022@users.noreply.github.com>
2026-05-31 23:51:49 +00:00
allenliang2022
cba69cd415 fix(context): rescale threshold_tokens to real cap in live usage snapshot
The default-only guard corrected context_length to the real per-model cap
(e.g. 1M for claude-opus-4.7-1m) but left threshold_tokens pointing at the
ContextCompressor's stale value (computed from the global 232K cap → 197.2k
@ 85%). UI then showed 'auto-compress at 197.2k / 1M' which is misleading.

Rescale threshold_tokens by the real/orig ratio so the displayed trigger
reflects the actual window (e.g. ~850k @ 1M).

NOTE: this only corrects the SSE display payload. The real auto-compress
trigger lives inside ContextCompressor in hermes-agent (agent_init.py:1446
constructs it with the global cap). A full fix requires a parallel change
upstream — tracked separately.
2026-05-31 23:49:41 +00:00
allenliang2022
1263cf03cf perf(context): cache real per-model context_length in live usage snapshot
_live_usage_snapshot() runs on every metering tick (~10x/sec while streaming).
The #3256 default-only guard recomputed get_model_context_length() there on
every tick for non-default models, which does a config read + potential
metadata/network probe — freezing claude-opus-4.7-1m streams while the default
model (4.8) stayed fast (guard not triggered for it). Resolve the real cap at
most once per stream via _real_ctx_cache. Backend-only, no frontend changes.
2026-05-31 23:49:41 +00:00
allenliang2022
4785aa542a fix(context): re-apply default-only context_length guard after v0.51.185 upgrade
Re-applies cb0065eb + b34311b3 (context_length default-only guard) which were
dropped by the upgrade reset to v0.51.185. Fixes 4.7-1m context window showing
as stale global cap (232K) instead of real 1M metadata. Backend-only: touches
api/routes.py + api/streaming.py, zero frontend/render changes.
2026-05-31 23:49:40 +00:00
nesquena-hermes
0488adc695
Merge pull request #3292 from nesquena/release/stage-batch3
v0.51.191: skills-detail markdown styling (#3284) + launchd duplicate-start guard (#3291) (stage-batch3)
2026-05-31 16:20:44 -07:00