Skip to content

feat: sync desktop streaming with backend persistence across refreshes#45

Open
cyzus wants to merge 2 commits into
mainfrom
claude/desktop-streaming-sync-vRQXS
Open

feat: sync desktop streaming with backend persistence across refreshes#45
cyzus wants to merge 2 commits into
mainfrom
claude/desktop-streaming-sync-vRQXS

Conversation

@cyzus
Copy link
Copy Markdown
Owner

@cyzus cyzus commented Jun 1, 2026

  • Route retry and steer through background queue (/chat/send, /chat/steer-send)
    so streams are reconnectable after page refresh via EventBus
  • sessionStorage seed: save streaming parts on visibilitychange/beforeunload
    and restore them on reconnect, including pending approval dialogs
  • Persist pending tool approvals to DB (chat.config._pending_approvals) so
    approval state survives disconnect; clear on stream completion
  • Route tool approval resumes through /chat/send background queue instead of
    direct SSE, with current parts saved to abandonedPartsRef for visual continuity
  • Add /chat/steer-send endpoint that runs process_steer via background queue
  • Expose restorePartsFromSeed in useAGUI for seeding parts without starting stream

https://claude.ai/code/session_01UYzkRjFAHjkX1PJqc2s6qi

claude added 2 commits June 1, 2026 07:25
- Route retry and steer through background queue (/chat/send, /chat/steer-send)
  so streams are reconnectable after page refresh via EventBus
- sessionStorage seed: save streaming parts on visibilitychange/beforeunload
  and restore them on reconnect, including pending approval dialogs
- Persist pending tool approvals to DB (chat.config._pending_approvals) so
  approval state survives disconnect; clear on stream completion
- Route tool approval resumes through /chat/send background queue instead of
  direct SSE, with current parts saved to abandonedPartsRef for visual continuity
- Add /chat/steer-send endpoint that runs process_steer via background queue
- Expose restorePartsFromSeed in useAGUI for seeding parts without starting stream

https://claude.ai/code/session_01UYzkRjFAHjkX1PJqc2s6qi
After every tool batch completes during a streaming run, reconstruct a
valid ModelResponse + ModelRequest from accumulated PartEndEvent parts
and ToolReturnParts, then fire-and-forget save to agent_state in DB.

This means if the stream is interrupted mid-run (disconnect, crash) the
next turn resumes from after the last completed tool batch rather than
from the start of the entire run. partial_history is also updated
immediately so the finally-block crash recovery path has the latest state.

Implementation uses pydantic-ai's PartEndEvent (complete parts, not
deltas) so no lossy delta-accumulation is needed. FunctionToolCallEvent
tracks in-flight count; FunctionToolResultEvent triggers the checkpoint
when all tools in the batch have resolved.

https://claude.ai/code/session_01UYzkRjFAHjkX1PJqc2s6qi
Copilot AI review requested due to automatic review settings June 1, 2026 13:43
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bac65e9037

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +877 to +881
const resp = await fetch(`${getApiBase()}/chat/send`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Allow queued approval resumes to attach to live stream

When this new queued resume path is used after tool approval, useToolApproval has already set streamingChatIdRef.current = targetChatId before calling resumeViaQueue, but the /chat/live connector skips tryConnect() whenever that ref matches the mounted chat. Because this path no longer opens a direct SSE reader, approving the final pending tool posts /chat/send successfully but never attaches to the background stream, leaving the UI stuck on the pending/running tool while the backend continues unseen; the same pre-set ref pattern also affects the new retry/steer queued paths.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates Suzent’s streaming architecture so desktop (frontend) streaming can reconnect after refreshes by routing turns/steers through a background stream queue, seeding UI state from sessionStorage, and persisting tool-approval state server-side.

Changes:

  • Add mid-run checkpointing during streaming and persist pending tool approvals to the DB so state survives disconnects/refreshes.
  • Add a reconnectable background-queue steer endpoint (POST /chat/steer-send) and wire it into the server routes.
  • Add frontend stream seeding (save/restore AG-UI parts) and expose restorePartsFromSeed() from useAGUI.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/suzent/streaming.py Adds mid-run checkpoint persistence and DB persistence/cleanup for pending tool approvals.
src/suzent/server.py Registers the new /chat/steer-send route.
src/suzent/routes/chat_routes.py Adds /chat/steer-send background-queue endpoint and extends /chat/send to accept resume_approvals.
frontend/src/hooks/useAGUI.ts Exposes restorePartsFromSeed() for restoring parts without starting a stream.
frontend/src/components/ChatWindow.tsx Implements sessionStorage stream seeding and routes retry/steer/resume through the background queue.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/suzent/streaming.py
Comment on lines +437 to +442
def _sync_save() -> None:
_st = serialize_state(
messages, model_id=_model_id, tool_names=_tool_names
)
_ckpt_get_db().update_chat(chat_id, agent_state=_st)

Comment thread src/suzent/streaming.py
Comment on lines +528 to +530
asyncio.create_task(
_save_mid_run_checkpoint(_checkpoint)
)
Comment thread src/suzent/streaming.py
Comment on lines +1039 to +1053
async def _clear_pending_approvals_task():
try:
def _sync_clear():
_db2 = _get_db2()
_chat2 = _db2.get_chat(chat_id)
if _chat2 is not None:
_cfg2 = dict(_chat2.config or {})
if "_pending_approvals" in _cfg2:
del _cfg2["_pending_approvals"]
_db2.update_chat(chat_id, config=_cfg2)
await asyncio.to_thread(_sync_clear)
except Exception:
pass

asyncio.create_task(_clear_pending_approvals_task())
Comment on lines +308 to +311
stream_queue = try_register_background_stream(chat_id)
if stream_queue is None:
return JSONResponse({"error": "Chat is already streaming"}, status_code=409)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants