Agent pane: an attached chat whose session is recreated or moved reads the session listed now

TE
9d ago
+297-6

Lawrence's list item, word for word: "start new VMs from acp chat - decouple persistence of acp trajectories from the VM itself, in case the VM is resuming". A follow-up to the peer snapshot stack (#25475, #25620) for manaflow-ai/cmuxterm-hq#4381, which reads peer chats through the _acpmux/peer_snapshot hook.

Fixes #25968.

What changes

A chat that is attached, with no attach or copy read out, now notices when its row names another session: the machine recreated it (another createdAt), or it moved to another machine (another peer, or a peer's chat now on this Mac). Before, the old rows stayed and the new session's records were dropped as already seen (apply takes a record by its id and seq alone), so the user kept looking at the old journal.

Now sessionChanged compares the row with what the rows on screen are proven of (rowsPeer, rowsGeneration, which an attach sets). When they differ, reattachMovedRow:

  • drops the rows and attaches the session listed now from nothing, as a switch would;
  • runs behind the same snapshot hold, so the pane keeps what it drew until the page is in;
  • owns the attach like any other (copyAttaches), so a reconnect that starts meanwhile decides;
  • if the machine is down, draws the Mac's copy of the new session when it has one (keepPeerCopy), and otherwise says the chat could not be read.

The re-read takes a selection generation of its own (++selectionGeneration), as a switch does, and moves historyEpoch on, so every read still out for the session before (an older page, its command list, the reads after its attach) lands on nothing.

attachWatched now sends the command list's read back (fetchCommands) only once the attempt is kept, after its last check that can retry it. An attempt the row moves on from during its opener reads is retried under the same generation, so a read it had sent could answer for the retry's session.

A row that only changes its status or names no createdAt changes nothing. Rows no attach proved (after a failed read) are not compared, so a failed read is not retried on every row change.

Tests

In agent-pane-peer-copy.test.ts, run on the fleet:

  • an attached chat whose machine recreates the session reads the new one instead of keeping the old rows;
  • an attached chat whose session moves to another machine reads it from that machine;
  • an attached chat whose row changes only its status keeps its rows and is not read again;
  • an attached chat whose row names another session and then this one again ends on this one, read a bounded number of times;
  • an older page read of the session before it was recreated is not drawn above the new one;
  • the command list read of the session before it was recreated is not shown for the new one;
  • an attach retried because the row moved on while its reads were out shows the command list of the session it ends on.

The existing test "a row recreated while the reads after the attach are out has the chat read again" now uses a log longer than the page: the command list's read is no longer one of the reads after the attach, so the turn opener's read is the one out when the row changes. It still passes with this PR's sessionChanged change reverted, so it still covers the retry path.

With the fix reverted, 5 of these fail. With only the generation bump, the epoch bump or the moved command read reverted, the test for it fails. The pane's 193 tests pass on the fleet and the webview typecheck is clean.

No capture: the change is in the client's row handling. What it draws is the chat as any switch to it draws it, with no new UI.

Risk: low. The comparison runs only for the selected, attached chat with nothing out, on a row change whose machine or createdAt differs from the proven one.

Timers, threads and loops

No new timer or loop. One attach per row change that names another session. Its stop condition is the attach's answer or failure; the snapshot hold it takes is released in finally (and the hold's own limit is SWITCH_HOLD_MS).

The two other points from the same review are filed as #25967 and #25969.

Agent session: agent session

🤖 Generated with Claude Code