fix(craft): restore progress and pending connections after refresh

Merged
WE
1d ago
+658-71

Description

fixes: https://linear.app/onyx-app/issue/ENG-4404/craft-session-dies-after-a-refresh-mid-run-with-no-progress-indicator

Refreshing Craft during a tool call can hide progress and consume the only announcement for a pending connection request. The session then appears stuck even while its turn remains active.

Show the existing flashing cursor while the turn is active, including quiet tool calls and final history loading. Preserve its original vertical alignment and avoid a duplicate cursor beside streaming text. Restore pending Connect app cards from cache when the browser attaches, without duplicating live announcements. Expired, resolved, and ended-turn requests are excluded. Turn cleanup removes only its own requests. The session index covers the longest pending request, including configured waits longer than 30 minutes. Cache failures retry in the announcement pump without closing the event stream.

If an active-turn status read fails, keep the session in its existing retry state with typing and submission disabled. Ignore late history errors after a newer turn takes ownership.

This uses the existing attachment and cache paths. It adds no client polling, packet replay buffer, or database migration.

How Has This Been Tested?

  • 39 backend unit tests passed across the stream merger, tenant context, and permission handling suites.
  • 4 external-dependency tests passed against the local kind Postgres and Redis services. Coverage includes concurrent pending requests, consumed announcements, payload preservation, TTL bounds, resolution, expiry, and long-lived requests added alongside shorter requests. The cancellation regression verifies that stopping a turn removes its card without removing another producer’s request.
  • 312 frontend tests passed across the message list, session store, streaming hook, chat panel, and real input component. Coverage includes failed status reads, stale failures, duplicate and distinct connection requests, progress indicators, and disabled typing and submission.
  • Temporarily removed restoration to confirm that the restored-only regression test fails; restored the implementation and reran the backend tests successfully.
  • Changed-file pre-commit checks passed, including typing, lint, formatting, and feature-map integrity.

Full deployed Kubernetes reconnect and Playwright refresh flows were not run. The current Kubernetes lane lacks deterministic connection-request setup; the real-cache tests are external-dependency tests, not deployed integration coverage.

Additional Options

  • [Optional] Please cherry-pick this PR to the latest release version.

Summary by cubic

Fixes Craft's refresh behavior so a page refresh no longer drops pending connection cards or makes an active turn look idle.

Restores pending connection cards after refresh

  • Connect-app requests are now indexed per session in Redis so new attachments can restore still-actionable cards.
  • Pending records and the session index expire with the permission wait; resolved, expired, and cross-session requests are excluded.
  • Turn cleanup removes only its own requests, including when the user stops the turn.
  • Restored cards are deduplicated against live announcements by request ID, and malformed permission requests are rejected.

Keeps an active turn visible after refresh

  • The message list reuses the existing flashing cursor while a turn is active, including after refresh during a quiet tool call; streaming text keeps its own cursor without a duplicate.
  • Failed active-turn status reads now trigger session-load recovery instead of treating the turn as idle.
  • Typing and submission stay disabled until recovery succeeds, with an explicit retry action exposed.

Summary by CodeRabbit

  • New Features

    • Pending Connect app requests now return after refreshing or reconnecting to a session, while resolved or expired requests are excluded.
    • Active-turn progress and cursor indicators are preserved across refreshes.
    • Recovered requests are prevented from appearing twice.
  • Bug Fixes

    • Failed session loads expose retry actions and disable message submission until recovery succeeds.
    • Draft messages remain preserved while input is temporarily disabled.
    • Malformed permission requests are rejected safely, and cancelled turns clean up only their own requests.
  • Documentation

    • Updated documentation to describe session recovery, request restoration, expiration, and streaming behavior.