fix(desktop): open a stopped chat in a workspace that opens empty

AV
3d ago
+50-5

Links

  • Follows #9399 (SUPER-3158), which first adopted chats started outside the desktop

Summary

  • A workspace that opens with no tabs and has no live chat now opens its newest stopped chat that nobody closed, instead of opening blank.
  • Fixes cloud workers created with superset ws create --agent … that went to sleep before anyone opened them. It also fixes a CLI-started chat on a local host that restarted, for example after an app update, before the workspace was opened.

Why / Context

A user created several cloud workspaces from the CLI, and each one opened blank. Part of that was #9399, which shipped in 2.0.6 after their report. This PR fixes the case #9399 does not cover.

#9399 adopts a chat only while it is live. A cloud box stops 15–30 minutes after its agent goes idle, and the sleep stops the harness processes. After the wake, listSessions returns the chat with live: false and terminalId: null, so chatSessionsToAdopt drops it. The workspace opens blank, and the chat is only in the history picker.

Reproduced on production with a small test box:

  • Right after superset ws create --agent claude --prompt …, superset sessions list showed live: true.
  • After superset ws sleep, the same chat listed as live: false, terminalId: null.

How It Works

  • chatSessionsToAdopt takes openedEmpty. The flag comes from the value useSyncChatSessionTabs already captures at layout-ready, before any adoption runs.
  • When openedEmpty is true and no adoptable chat is live, the function returns the newest adoptable chat whose closedAt is falsy. It returns at most one.
  • With no target terminal, the chat falls back to its session id, as mobile chats already do. The pane then resumes the agent session through the stopped-chat recovery in AcpChatPane, which persisted panes already use after a restart.
  • Adds an entry to docs/cloud-sandbox-mismatches.md.

Design Decisions

  • Why closedAt is a safe signal: every closeSession sets it, including tab close, orphan cleanup, fork-loader cleanup and replaced sessions (#8919). A chat with no closedAt only lost its process. The chat-runtime schema default is 0, not null, so the check is falsy rather than === null.
  • Why only on an empty workspace, and only one chat: this keeps #9399's live-only rule wherever the user already has tabs. It also stops a workspace with many old chats from filling with tabs after a host restart. Older stopped chats are still in the history picker.

Manual QA Checklist

Not run in the app yet: a dev stack can't provision cloud boxes. The box behaviour above was checked with the CLI against production.

  • Create a cloud workspace with superset ws create --agent claude --prompt …, run superset ws sleep <id>, then open it. Its chat opens as the active tab and resumes on the next prompt.
  • Open the same workspace again after closing that chat tab. It opens without the chat.
  • Open a cloud workspace whose chat is still running. It is adopted as before, and only once.
  • Open a local workspace that has tabs, after an app restart. No extra chat tabs appear.

Testing

  • bun test src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useSyncChatSessionTabs (in apps/desktop): 10 pass. The new test fails without the fix.
  • bun run typecheck (in apps/desktop): exit 0
  • bunx biome check on the changed files: clean

Summary by cubic

Fixes a blank workspace that opens with no tabs and no live chat so it now adopts the newest stopped chat that nobody closed, instead of sitting empty. This covers a cloud worker that went to sleep before being opened and a local host that restarted before its ChatGPT-style chat was opened.

  • Only applies when the workspace opens empty; if the user already has tabs, the live-only adoption rule from #9399 is unchanged.
  • Only one stopped chat is adopted, and chats with a closedAt timestamp are never brought back, so older chats stay in the history picker.
  • The recovered chat uses its session id as the fallback terminal and resumes the agent session via the existing stopped-chat recovery path in AcpChatPane.
  • Adds a note to docs/cloud-sandbox-mismatches.md documenting the mismatch and fix.

Summary by CodeRabbit

  • New Features
    • When a workspace opens without tabs or a live chat, it can now open the newest eligible stopped chat, making that session available to resume.
    • If a live chat is available, it is selected instead of a stopped chat.
  • Documentation
    • Updated lifecycle guidance to explain how stopped chats may appear in a workspace after its sandbox wakes.