Release: bound fuzzy matching for large sessions — source-aware workspace-prefix dedup (#7128, @Dandandad) (#7143)

Co-authored-by: n <a@n>
This commit is contained in:
nesquena-hermesandn authored and GitHub committed 2026-08-19 03:44:51 +00:00
1 parent af36f409b0
commit 963799158a
1 file changed
+1
+1
View File
@@ -5,6 +5,7 @@
### Fixed
- **Large sessions no longer replay a stale duplicate prompt (or drop a real turn) when reconciling the sidecar against the model transcript.** Matching one logical turn across the two stores relied on an O(n²) fuzzy fallback that was bounded to the first 1,000 keys for performance — but above that bound a workspace-prefixed prompt (state.db stores the model-facing `[Workspace::v1: …]` wrapper while the WebUI sidecar owns the bare text) stopped deduping, so a 1,001-key session replayed the prefixed prompt after its answer. The workspace prefix is now folded into the exact key so protocol-equivalent turns match in O(1) above the cap, and — critically — that normalization is source-aware: only the model-facing state.db key strips the wrapper, while the sidecar-owned visible key preserves literal text verbatim, so a user who genuinely types `[Workspace: …]` isn't collapsed into a different turn. Thanks @Dandandad. (#7128)
- **The Skills panel no longer shows every skill as enabled (and silently drop your other disabled skills on a toggle) when `skills.disabled` is stored as a JSON-array string.** `hermes config set skills.disabled …` and JSON-mode config saves persist the list as a quoted string (`'["skill-a","skill-b"]'` or the Python-literal `"['a']"`), which the panel read as a single literal skill name — so no skill matched, all rendered enabled, and toggling one rewrote `skills.disabled` to a one-entry list, discarding the rest. The panel read and the toggle write now decode the JSON-array/Python-literal string form into a real list (reusing the agent's shared parser when present so the two surfaces cannot drift, with a safe `ast.literal_eval` fallback), while a genuine scalar skill name still means one entry. Thanks @webtecnica. (#7120)
- **Loading several large sessions at once can no longer spike memory into an out-of-memory crash.** Full sidecar resolution (the expensive JSON parse for a large transcript) is now bounded by a small semaphore, and concurrent requests for the same session elect a single leader that parses once while the others wait for its cached result instead of each parsing their own copy — so a burst of tabs or reconnects against big sessions can't multiply transient memory. Metadata-only reads and cache hits stay on the lock-free fast path, and a failed parse always releases its slot (no capacity leak). Thanks @Dandandad. (#7127)
- **Reconnecting to a live chat stream now resumes from the client's last received event instead of losing the tail or replaying the whole turn.** The `/api/chat/stream` endpoint honors the SSE `Last-Event-ID` header (and `after_event_id` / `after_seq` / `replay=1`) with a resume cursor that carries provenance, so a dropped connection re-subscribes at the right place: an in-range cursor replays only the gap, an unparseable/foreign/ahead cursor fails closed to replay-from-start (never silently skipping frames), and a genuinely truncated buffer still emits `recovery_control`. A prior edge where an ahead cursor with no known snapshot cutoff installed itself as the live dedup bound — discarding every queued frame including the terminal `stream_end` and stalling the reconnect on heartbeats — is fixed by treating an unknown cutoff as fence 0 before normalization. Thanks @Paladin173. (#6886)