SIGN IN SIGN UP

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