Commit graph

194 commits

Author SHA1 Message Date
nesquena-hermes
cc8ef201be Stage 387: PR #2600 2026-05-19 22:10:20 +00:00
starship-s
692ea22f9e fix(streaming): finish auto-compression card after rotation 2026-05-19 14:35:11 -06:00
Lumen Yang
8d2b9d4a16 feat(webui): render indexed context metadata 2026-05-19 18:52:50 +00:00
nesquena-hermes
96cb4a556a Stage 386: PR #2584 2026-05-19 18:20:47 +00:00
nesquena-hermes
0585881511 Stage 386: PR #2583 2026-05-19 18:20:07 +00:00
nesquena-hermes
42c2eda0fc Stage 386: PR #2579 2026-05-19 18:20:06 +00:00
Florian Krause
646f18c696 fix: prevent queued follow-up message from draining into wrong chat
When a queued message was waiting for the active stream to finish,
the 120ms setTimeout drain in setBusy(false) would write the queued
text to the shared #msg composer and call send(), which reads
S.session.session_id at call time. If the user switched to a different
chat during the 120ms window, the queued message was sent to the
wrong session.

Two fixes:

1. setBusy(false) drain: guard the setTimeout callback — if the
   currently viewed session no longer matches the drain session,
   put the message back into the original session's queue instead
   of sending it.

2. _sendInProgress re-queue: track _sendInProgressSid alongside
   _sendInProgress so that when a concurrent send() is caught by the
   guard, the re-queued message targets the in-flight session rather
   than the currently viewed one.
2026-05-19 15:50:12 +02:00
dobby-d-elf
2a95c1e482 Fix profile-aware assistant display names 2026-05-19 07:17:11 -06:00
starship-s
2e9ca283dc fix: display canonical cache hit percentage 2026-05-19 02:27:12 -06:00
dobby-d-elf
11e1e9a342 Fix settled rendering for file markdown links 2026-05-18 22:32:20 -06:00
nesquena-hermes
a978612b76 Update brittle-source-string assertions for widened compressed-event guard 2026-05-19 03:21:26 +00:00
Dennis Soong
ea978a1989 fix: surface auto-compression handoff 2026-05-19 10:45:43 +08:00
Dennis Soong
516d2a588c fix: show auto-compression elapsed time 2026-05-18 13:08:38 +08:00
Frank Song
9646773487 Add selected text reply composer append 2026-05-18 07:13:14 +08:00
Michael Lam
87e3b4e88e fix: cap stream fade done drain 2026-05-17 00:27:54 -07:00
nesquena-hermes
a33cd4195b Stage 374: PR #2431 — fix(chat): render pending assistant placeholder before /api/chat/start returns by @Michaelyklam (fixes #2429)
Co-authored-by: Michael Lam <michael@example.local>
2026-05-17 02:49:35 +00:00
nesquena-hermes
47c210899e Stage 374: PR #2421 — fix(cache-tokens): surface provider prompt-cache read/write tokens in WebUI usage by @Michaelyklam (fixes #2419)
Co-authored-by: Michael Lam <michael@example.local>
2026-05-17 02:49:34 +00:00
Hermes Agent
b937cf3583 Stage 370: PR #2390 — Fix live progress Activity grouping by @franksong2702
# Conflicts:
#	CHANGELOG.md
2026-05-16 20:21:58 +00:00
Hermes Agent
41f8b77a0b Stage 369: PR #2393 — fix: cap live chat stream transports by @Michaelyklam
# Conflicts:
#	CHANGELOG.md
2026-05-16 20:03:44 +00:00
Hermes Agent
8b4f8b0600 Stage 369: PR #2347 — Preserve live agent timeline across session switches by @franksong2702 2026-05-16 20:03:43 +00:00
Michael Lam
0b64e21264 fix: cap live chat stream transports 2026-05-16 10:27:28 -07:00
Frank Song
2dfe3ffb42 Fix live progress activity grouping 2026-05-16 23:37:44 +08:00
Michael Lam
3bb8c7b276 fix: guard localStorage quota writes 2026-05-16 07:31:44 -07:00
Frank Song
faedcab739 Preserve live agent timeline across session switches 2026-05-16 14:12:08 +08:00
Frank Song
d94320b4bf Avoid duplicate Thinking echo helper names 2026-05-16 04:07:03 +00:00
Frank Song
dd5f3ff9b5 Suppress interim text echoes in Thinking cards 2026-05-16 04:07:03 +00:00
Hermes Agent
b293bf8bc5 stage-364: Opus-caught live SSE event_id fix (side-channel approach)
Replace the earlier frontend-reset approach with a backend side-channel
approach that preserves the queue (event, data) tuple shape.

Problem (Opus catch):
- Live SSE frames emitted by _sse() in api/streaming.py:2296 carried no
  'id:' field. Only journal-replay frames (via _sse_with_id) emitted IDs.
- Frontend's _lastRunJournalSeq cursor stayed at 0 during live streaming.
- Mid-stream error → reconnect-to-replay arrived with after_seq=0.
- Server replayed every journaled event from seq 1.
- assistantText (closure-scoped) had accumulated all live tokens already
  → double-rendered output.

Fix:
- api/config.py: STREAM_LAST_EVENT_ID: dict = {} module-level dict.
- api/streaming.py put(): capture journal event_id, write to
  STREAM_LAST_EVENT_ID[stream_id]. Keep queue tuple as (event, data).
- api/routes.py _handle_sse_stream: read STREAM_LAST_EVENT_ID[stream_id]
  at emit time, use _sse_with_id when set.
- api/streaming.py finally block: pop STREAM_LAST_EVENT_ID for cleanup.

Why side-channel instead of 3-tuple:
- Earlier attempt (queue tuple → (event, data, event_id)) broke 4 existing
  tests: test_cancel_interrupt, test_sprint42, test_sprint51,
  test_issue1857_usage_overwrite. These all unpack 'event, data = q.get()'.
- Frontend-reset approach (reset assistantText before replay) broke 3
  other tests: test_smooth_text_fade, test_streaming_markdown,
  test_streaming_race_fix. _wireSSE must NOT reset accumulators because
  legacy reconnect doesn't replay events; only journal-replay does.

Side-channel preserves both invariants:
- Queue contract stays (event, data) — legacy consumers unbroken.
- Frontend accumulators stay alive on _wireSSE — legacy reconnect unbroken.
- Live SSE emits 'id:' so the journal cursor advances correctly.

6 regression tests added in test_stage364_opus_live_sse_event_id.py.
1 existing test (test_run_journal_streaming_static.test_streaming_journals_sse_events_before_queue_delivery) updated to be tuple-shape-agnostic.

Test results:
- Full pytest: 5713 passed, 10 skipped, 1 xfailed, 2 xpassed, 0 failed
- Previously-failing 5 tests: ALL PASS
- 6 new regression tests: ALL PASS
2026-05-16 03:58:54 +00:00
Hermes Agent
f3b0c2cb5f stage-364: Opus-caught replay double-render fix + regression test
Opus advisor caught that the new run-journal replay path could double-render
when the live stream errors mid-stream:

- Live SSE frames emitted by _sse() in api/streaming.py:2296 carry no 'id:'
  field. Only _sse_with_id() (used in _replay_run_journal at routes.py:5853)
  emits IDs.
- During live streaming, EventSource.lastEventId stays empty, so the frontend's
  _lastRunJournalSeq stays at 0.
- If the server dies mid-stream, the error reconnect handler opens replay with
  after_seq=0 — server replays every journaled event from seq 1.
- assistantText accumulator (closure scope in messages.js) carries over from
  the live phase. The token handler unconditionally appends d.text. Double-
  rendered text.

Fix: reset assistantText, reasoningText, liveReasoningText, segmentStart, and
set _smdReconnect=true before opening the replay EventSource. Next live token
clears assistantBody.innerHTML to match the reset accumulator.

4 regression tests added in test_stage364_opus_replay_doublerender_fix.py.
Revert-fix verification confirms 3/4 tests fail against reverted code.

This is the TWO-LAYER catch in action: agent self-verified the producer→
consumer chain works end-to-end (Step 3 in agent-side-empirical-verification.md
PASSED for #2283), and Opus independently caught a separate frontend coupling
issue. Both checks required and both fire.
2026-05-16 03:28:07 +00:00
Frank Song
a3eace4380 Preserve SSE handler test shape 2026-05-16 02:58:34 +00:00
Frank Song
80d8d4b42c Address run journal replay review 2026-05-16 02:58:34 +00:00
Frank Song
3b96035af0 Add WebUI run event journal replay 2026-05-16 02:58:34 +00:00
Hermes Agent
1e6ce3d2e7 Merge pull request #2328 into stage-362
fix: clean session attachment and stream recovery leftovers (Michaelyklam, closes #2325)
2026-05-15 22:55:37 +00:00
Hermes Agent
62e4d9b2f5 Merge pull request #2327 into stage-362
fix: use assistant name in cancel copy (dotBeeps)
2026-05-15 22:55:35 +00:00
Michael Lam
c991f36021 fix: clean session attachment and stream recovery leftovers 2026-05-15 13:30:46 -07:00
dot 🐶
3add6f450f fix: use assistant name in cancel copy
Replace the hardcoded Skyly cancellation wording with the configured bot_name from settings, falling back to Hermes when unset.

Keep the client-side fallback in sync by using window._botName if the session refresh after cancellation fails.

Co-authored-by: Obryn 🐉 <obryn-ai@dotbeeps.dev>
2026-05-15 16:00:30 -04:00
Hermes Agent
ab749575df Merge pull request #2318 into stage-361
fix: defer mobile stream errors while tab is hidden (Michaelyklam, closes #2307)

# Conflicts:
#	CHANGELOG.md
2026-05-15 19:17:06 +00:00
Michael Lam
aeda75271c fix: defer stream errors while mobile tabs are hidden 2026-05-15 11:25:53 -07:00
Frank Song
5f9b9c02b2 Fix WebUI stream completion recovery gaps 2026-05-15 08:36:48 +08:00
Hermes Agent
ec689e32be Merge pull request #2099 into stage-358
feat: add opt-in streaming text fade (dobby-d-elf, off-by-default)
2026-05-14 21:27:52 +00:00
Hermes Agent
3f851051cf Merge pull request #2151 into stage-350
fix: clarify cancelled chat turn status (Jordan-SkyLF)

Conflict resolution on api/streaming.py:4549-4567 (the cancel-handler
ownership guard). Both this PR and the already-shipped PR #2136 add a
guard at the same site against stale stream writebacks, from different
angles:

  - PR #2136 (HEAD): _stream_writeback_is_current(_cs, stream_id) — strictly
    dominates by checking the active_stream_id token equality.
  - PR #2151: 'worker won the race' check via (active_stream_id != stream_id
    and not pending_user_message), with _emit_cancel_event = False to suppress
    the terminal cancel event.

Resolution merges both: keep #2136's strictly-stronger condition for skip
detection, and adopt #2151's _emit_cancel_event = False semantic so the
cancel event isn't emitted in addition to skipping the writeback (when
client may have already received the successful done payload).

55/55 tests pass across cancelled-turn-status + stale-stream-writeback +
the four cancel/data-loss sibling test files.
2026-05-13 20:44:44 +00:00
Jordan SkyLF
0381294f1c feat: add early session provisional titles 2026-05-13 11:37:11 -07:00
Hermes Agent
ef042ad8c2 Merge pull request #2188 into stage-348
fix: refresh context ring after compression (LumenYoung)
2026-05-13 16:34:42 +00:00
Hermes Agent
785b1fe7e9 Merge pull request #2186 into stage-348
fix: prevent concurrent send() from losing messages or swallowing stream output (jasonjcwu, split from #2164)
2026-05-13 16:34:36 +00:00
Xiangzhe
1a4e0522a0
fix: keep background metering from overwriting visible session usage 2026-05-13 20:58:50 +08:00
Lumen Yang
3289c44fb6 fix: refresh context ring after compression 2026-05-13 14:02:28 +02:00
fxd-jason
676d1f965e fix: prevent concurrent send() from losing messages or swallowing stream output
Problem: When two messages are sent in rapid succession, the second
send() can pass the S.busy check because setBusy(true) only runs after
the first await inside send(). This creates a window where two async
send() calls run concurrently, leading to:
- Streaming output from the first response getting swallowed when the
  second response's done event overwrites S.messages
- User messages disappearing when server returns 409 for the duplicate
  chat/start request

Root cause: send() is async and has awaits (uploadPendingFiles,
api('/api/chat/start')) before setBusy(true) at line 198. During those
await yields, S.busy is still false, allowing a second send() to enter.

Fix: Add a synchronous _sendInProgress guard at the very top of send()
(before any await). Concurrent calls re-queue the message instead of
silently dropping it. try/finally ensures the flag resets on all exit
paths.

Also widens the text-extraction window in
test_1062_busy_input_modes.py from 3000 to 5000 chars to accommodate
the new guard block at the top of send().
2026-05-13 19:21:50 +08:00
dobby-d-elf
f23ee79938 Scale fade duration with playback speed 2026-05-12 20:50:38 -06:00
dobby-d-elf
0b51245bd3 Optimize smooth fade word append path 2026-05-12 18:20:08 -06:00
dobby-d-elf
5e2350e128 Harden smooth text fade before merge 2026-05-12 18:18:07 -06:00
dobby-d-elf
afc089c93b Tune smooth text fade playout 2026-05-12 18:06:11 -06:00