fix(chat): fork a live session at the message the user picked
A turn produced in the current session is named `live-<conversationId>-<liveMessageId>` and keeps that name after it settles. The backend resolves "which turn" against a fresh parse, whose turns are `turn-N`, so the fork point never matched — `resolve_fork_point` returned None and the fork silently fell back to the tail, producing a copy of the parent. Forking from a reloaded conversation worked all along, because those turns come from the parser to begin with. The post-turn reparse already aligns parsed turns onto local ones to backfill usage and duration, so it has the parser's name in hand: carry it too. A patch whose only content is that name is still worth emitting — a plain codex reply records no per-turn usage, and the old guard would have dropped it. Where the parser splits a reply into more sub-turns than the live stream did, the name taken is the MATCHED sub-turn, not the group's first: the stats are summed across the group but a fork point is a position, and "up to and including this reply" ends where the reply does. Also stops a codex fork from destroying the history it just preserved. `session/fork` releases only the child's writer, so the parent thread stays locked by the forking process, and the sibling row codeg creates to keep the pre-fork history fails to load with "already has an active writer". That fell through to `session/new`, rebinding the row to a fresh empty session — the one pointer to that history, gone. Classify it as `session_busy` and stop there: the session is held, not lost, and the lock clears when the fork is closed. Refused for custom agents too, unlike every other load failure, since those all mean the session is really gone.
X
xintaofei committed
f20cc864d8df5d0681248db1094088bea7c27618
Parent: c726d96