HERMES-GIELIE/api
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
..
__init__.py
agent_health.py fix(agent-health): treat stale running gateway as unknown 2026-05-11 23:13:09 +00:00
agent_sessions.py fix: reconcile stale sidebar display titles 2026-05-14 16:18:53 +08:00
auth.py fix: serve PWA manifest from session routes 2026-05-14 11:52:12 -07:00
background.py
clarify.py
commands.py fix(security): drop unsafe-eval + add jsdelivr to CSP, sanitize plugin error 2026-05-11 17:53:02 +00:00
compression_anchor.py docs: clarify compression anchor helpers 2026-05-12 01:43:16 -07:00
config.py stage-364: Opus-caught live SSE event_id fix (side-channel approach) 2026-05-16 03:58:54 +00:00
dashboard_probe.py
extensions.py
gateway_watcher.py
goals.py fix(i18n): localize /goal runtime status strings 2026-05-10 15:21:24 +08:00
helpers.py stage-348: apply Opus SHOULD-FIX-pre-merge — add '://' to _SENSITIVE_LOWER_MARKERS 2026-05-13 16:54:36 +00:00
kanban_bridge.py stage-315 absorb: document handle_kanban_* three-valued return contract 2026-05-07 18:52:01 +00:00
metering.py
models.py Merge pull request #2285 into stage-359 2026-05-15 14:55:19 +00:00
oauth.py fix(oauth): serialize Anthropic env fallback reads 2026-05-07 02:47:19 +00:00
onboarding.py Classify onboarding DNS probe failures consistently 2026-05-15 08:51:30 +08:00
profiles.py Revert "Merge pull request #2323 into stage-362" 2026-05-15 23:22:44 +00:00
providers.py fix(providers): avoid caching transient quota probe failures 2026-05-13 19:01:47 -06:00
request_diagnostics.py fix: add request wedge diagnostics 2026-05-08 15:37:08 +00:00
rollback.py
routes.py stage-364: Opus-caught live SSE event_id fix (side-channel approach) 2026-05-16 03:58:54 +00:00
run_journal.py Address run journal replay review 2026-05-16 02:58:34 +00:00
session_ops.py
session_recovery.py fix: audit turn journal terminal collisions 2026-05-12 05:20:06 -07:00
startup.py
state_sync.py
streaming.py stage-364: Opus-caught live SSE event_id fix (side-channel approach) 2026-05-16 03:58:54 +00:00
system_health.py feat: add VPS resource health panel 2026-05-05 17:30:56 +00:00
terminal.py
turn_journal.py fix: lock turn journal appends 2026-05-15 16:39:45 -07:00
updates.py Keep unknown update summary bullets 2026-05-15 09:16:39 +08:00
upload.py fix: clean session attachment and stream recovery leftovers 2026-05-15 13:30:46 -07:00
workspace.py refactor: reduce stale workspace recovery fix 2026-05-12 06:28:35 -06:00
worktrees.py Harden worktree removal safeguards 2026-05-13 09:49:15 +08:00