fix: resync session state after SSE reconnect to prevent stuck subagent UI (#817)

* fix: resync session state after SSE reconnect to prevent stuck subagent UI

When a subagent completes while the page is in the background (common on
mobile PWA and desktop webview), the final SSE events are lost. The UI
then stays stuck on 'Waiting for subagent activity...' because:

- part.state.status never transitions to 'completed'
- session_status is never updated to 'idle'
- activeLatched remains true indefinitely

Fix:
- Add onReconnect callback to event pipeline, fired after SSE reconnect
- Add pageshow listener for bfcache restores (mobile PWA back-forward)
- On reconnect, re-fetch session list for directories with active sessions
- Pass explicit directory to useSessionActivity in ToolPart for subagents
  to ensure the correct child store is queried

Closes #810

* fix(chat): resolve pending subagent task binding before metadata arrives

* fix: restore subagent activity and tool visibility after reconnect

- Resyncs session status and child session data after SSE reconnect
- Ensures child task tool messages are read from the correct directory
- Prevents stale assistant fallback from keeping sessions stuck as active

---------

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
This commit is contained in:
jwcrystal
2026-04-03 00:29:49 +03:00
committed by GitHub
co-authored by Bohdan Triapitsyn
parent aef7b206ed
commit e63450ae2c
6 changed files with 595 additions and 22 deletions
+10 -7
View File
@@ -21,14 +21,14 @@ const IDLE_RESULT: SessionActivityResult = {
/**
* Determines if a session is actively working.
* Checks session_status and, as a narrow fallback, only the trailing
* assistant message when its completion update has not landed yet.
* Checks session_status and, only when status is missing, falls back to the
* trailing assistant message when its completion update has not landed yet.
* Returns idle when permissions are pending (permission indicator takes priority).
*/
export function useSessionActivity(sessionId: string | null | undefined): SessionActivityResult {
const status = useSessionStatus(sessionId ?? '');
const messages = useSessionMessages(sessionId ?? '');
const permissions = useSessionPermissions(sessionId ?? '');
export function useSessionActivity(sessionId: string | null | undefined, directory?: string): SessionActivityResult {
const status = useSessionStatus(sessionId ?? '', directory);
const messages = useSessionMessages(sessionId ?? '', directory);
const permissions = useSessionPermissions(sessionId ?? '', directory);
return React.useMemo<SessionActivityResult>(() => {
if (!sessionId) return IDLE_RESULT;
@@ -47,9 +47,12 @@ export function useSessionActivity(sessionId: string | null | undefined): Sessio
&& typeof (lastMessage as { time?: { completed?: number } }).time?.completed !== 'number',
);
const statusWorking = phase !== 'idle';
const hasAuthoritativeStatus = status !== undefined;
const statusWorking = hasAuthoritativeStatus && phase !== 'idle';
const isWorking = statusWorking || hasPendingAssistant;
if (hasAuthoritativeStatus && !statusWorking) return IDLE_RESULT;
if (!isWorking) return IDLE_RESULT;
return {