SIGN IN SIGN UP

fix(chat): stop a fork naming the wrong turn or sweeping a live one

Three defects found reviewing the fork work for production readiness.

The codex parser read `session_meta` last-record-wins for everything but
`parent_id`. A by-reference fork keeps its history in the parent file, so
the splice puts the parent's header into the child's stream and the
child's summary was filed under the PARENT's id, cwd and branch — two
rollouts claiming one id, which the conversation list dedups down to one
and the fork disappears. Latch every identity field on the first header.

`source_turn_id` was assigned by whole-batch tail alignment, which cannot
say which live reply produced a surplus of parsed turns. A surplus is
equally a sub-turn split or a turn this client never streamed (an async
sub-agent reply, a co-controlling client), so locals [A,B] against parsed
[A,B1,B2] named A "B1" and forking from A forked past A's own reply.
First-write-wins made it permanent. Name a turn only when the transcript
has caught up — a trailing user turn proves the just-completed reply has
not flushed — and the parse holds exactly as many assistant turns as this
session streamed. Anything else stays unnamed and falls back to a tail
fork, which is at least the fork the user can see.

The post-fork refetch cleared the live buffers on its own schedule, so a
reply the user sent immediately after forking vanished if it completed
first. The removal now rides on the detail dispatch: the panel snapshots
the pre-fork COMPLETED turns before the fork RPC and passes them as
`dropLiveTurnIds`. One dispatch, so no frame shows the new history beside
the old turns; a failed fetch removes nothing; and an optimistic turn is
never swept, because a fork is refused while a turn is in flight and so
its prompt provably runs on the fork.
X
xintaofei committed
f4b17289dddadebb5a10d78691e0ca72608d2178
Parent: 9101360