Files
hermes-webui/tests
nesquena-hermesandnesquena-hermes 603375d71c fix(#5472): preserve composer draft when a send fails (#5484)
* fix(#5472): preserve composer draft when a send fails

When a provider/background error aborts a send, send() has already cleared
the composer and the persisted draft before /api/chat/start durably accepts
the turn. On a start-time throw the turn is never persisted, so the user's
typed message is lost and must be retyped.

Add _restoreComposerDraftAfterFailedSend(text, sid): restores the typed text
into the composer, keeps staged files in S.pendingFiles, and re-persists the
draft (via _saveComposerDraftNow) so it also survives a reload. Guarded to
no-op when there's nothing to restore and to never clobber a new message the
user began typing during the async send window. Wired into the /api/chat/start
throw handler's general-error branch. Mirrors the existing _trySteer /
_stashClarifyDraft draft-restore idioms.

Tests: tests/test_issue5472_preserve_draft_on_failed_send.py — 3 static wiring
assertions + 4 node-vm behavioral tests of the helper's branching logic.

* fix(#5472): address gate findings — original-payload snapshot, re-stage files, session-guard

Codex + Opus gate on the first commit converged on three real edges:

1. /moa and bundle sends rewrite `text` before /api/chat/start, so restoring
   `text` replayed the transformed payload, not the user's typed command.
   Fix: capture an immutable _failedSendDraftText snapshot right after the
   post-flush trim, before any slash rewrite, and restore that.

2. uploadPendingFiles() clears S.pendingFiles=[] before the start call, so the
   original 'keep staged files' assumption was false — a resend silently
   dropped attachments. Fix: snapshot S.pendingFiles before the upload drain
   (_failedSendFilesSnapshot) and re-stage from it (+ renderTray()) on failure.

3. A start failure after a session switch could write the failed old session's
   draft into the currently-visible composer. Fix: the helper now only mutates
   the visible composer/tray when sid === the visible session; the sid-keyed
   _saveComposerDraftNow persist stays unconditional so the draft survives a
   switch-back/reload.

Helper signature is now (draftText, filesSnapshot, sid). Tests expanded to 11
(5 static + 6 node-vm behavioral) covering all three edges: original-vs-mutated
payload, re-stage drained attachments, and cross-session no-pollution.

* test(#5472): bound send() body slice to LIVE_STREAMS instead of fixed char window

The #5472 snapshot-capture lines added ~400 chars near the top of send(), which
pushed 'cancelStream' (in the busy-interrupt branch) past the arbitrary
[send_idx:send_idx+5000] window that test_1062's structural assertions used —
a real CI failure (shard 1, all 3 Pythons) caused by a brittle test, not by the
fix logic. Bound both send()-body slices in test_1062 to the actual function
extent (up to the 'const LIVE_STREAMS=' decl that immediately follows send()) so
future additions near the top of send() don't false-fail these assertions.

---------

Co-authored-by: nesquena-hermes <nesquena-hermes@users.noreply.github.com>
2026-07-03 15:57:33 -07:00
..
2026-05-05 01:51:05 +00:00
2026-04-29 21:34:27 -07:00