Files
OpenResearch/ui
Daniel Kim 5e25afaf98 Surface silently-dropped chat turns (v0.1.87) (#139)
* Surface silently-dropped chat turns (v0.1.86)

A sent message could vanish with no response and no error. Evidence from
the live DB: user messages persisted with no assistant reply at all —
the claude turn loop waits on child events with no watchdog, so a wedged
resident child (seen: expired-OAuth refresh hang) spins forever; the
user's only recourse, Stop/Escape, aborted the turn task without leaving
any trace; and a failed send POST was swallowed by the UI catch.

- claude adapter: watchdog the event wait (120s to first event, 30min
  between events, matching the codex TURN_WATCHDOG), exempting turns
  held on a pending mcp-gate approval; on trip, kill the child and
  surface a visible error (next message resumes via --resume).
- ChatHost::interrupt_by_user: the Stop/Escape path now persists an
  'Interrupted' marker row, so a stopped turn no longer reads as 'orx
  did nothing'. Internal interrupts (plan resume, deletes) stay silent.
- UI: a failed send now renders a local error row instead of silently
  clearing busy; chat SSE reconnects re-seed sessions/busy and the
  active transcript (chat frames are edge-only, gap frames are lost).

* Address review: verified send-failure surfacing, watchdog guard arm, shared consts

- send() catch: restore draft/attachments even when session creation
  failed; an explicit 'session is busy' rejection always surfaces (that
  busy is someone else's turn); a network failure probes server-side
  busy first and undoes the restore when the turn is real.
- claude watchdog: permission re-arm is a guard arm that no longer
  promotes the first-event deadline; error reports minutes on the
  mid-turn path and the login hint only on the first-event path.
- TURN_WATCHDOG hoisted to harness/mod.rs, shared with codex.
- interrupt_by_user: timestamp captured before the abort; WirePart::tool
  constructor (also used by push_error).
- Reconnect repair: shared syncSessionList (deletedIds tombstones,
  contextUsage merge, seenTitles baseline), seedBusy scoped by known
  ids, transcript reseed retries once via msgGen when a flush raced it.

* Guard claude turns against --resume replay output (echo boundary)

Folds in (and supersedes) the unmerged daniel/fix-swallowed-messages
branch: a --resume respawn can replay prior-session/startup output
before touching the new message — fatally a stale result event, which
ended the turn instantly with zero parts while the real reply streamed
into a dropped channel. --replay-user-messages makes the child echo the
submitted message; the turn loop now skips everything before the echo.

Review round (2x empirical against claude 2.1.212): echo is byte-exact
and stdin-scoped (no history replay, so no same-text false positive);
pre-echo lines count as watchdog proof-of-life (API backoff after boot
must not be killed at 120s; only total silence keeps the fast wedge
budget + login hint); pre-echo system/init harvests the session id for
first-turn interrupts; module docs pin the echo contract.

* Reconcile deleted sessions on SSE reconnect; repair after a failed first connect

Addresses review findings 2 and 3 on PR #139:

- A session deleted while SSE was down lost its chat.session.deleted
  frame for good: syncSessionList removed the sidebar row but left the
  cached transcript, loadedSessions, activeId, and — via the known-
  scoped seedBusy merge — a stale busy flag. Now the pre-fetch snapshot
  of shown sessions is diffed against the authoritative list and every
  missing id gets the full forgetSession cleanup, before seedBusy so
  the ghost's flag can't be carried forward. The snapshot is taken
  before the fetch (a session created mid-flight can't be tombstoned),
  and the project-change reset clears the snapshot ref synchronously
  (the old project's rows must not read as deleted under the new one).

- 'reconnected' now also fires when the first successful EventSource
  open follows a FAILED first connect (page loaded while the backend
  was briefly down) — the initial fetches likely failed too, so the
  panel repaired itself only on later drops, never on that first
  recovery. A clean first open still emits nothing.

Verified live on the slot-8 dev stack: session row deleted from the
DB while the backend was down; on reconnect (same page, no reload)
the row, ghost transcript, active selection, and busy flag all clear.
2026-07-29 16:41:13 -07:00
..
2026-07-06 20:05:41 -07:00
2026-07-06 20:05:41 -07:00
2026-07-06 20:05:41 -07:00