The retry notification added in this PR re-invokes forkBranch with the same
key, but forkBranch stamps idempotency_key into the params of EVERY branch
flavor — including session.branch_stored, whose params contract is
extra=forbid and did not declare the key. The desktop's right-click branch
(that spec's exact path) answered 4000 'idempotency_key: Extra inputs are
not permitted', no child row was created, and e2e/core/lineage-sidebar.spec.ts
failed at 'the branch created a session row' (0 rows, 60s poll).
- SessionBranchStoredParams: accept idempotency_key (optional; an older
client that omits it is unchanged).
- session.branch / session.branch_whole: accept idempotency_key and dedupe
via the same _idempotency_keys registry as session.create, so a
lost-response retry of a LIVE branch also returns the SAME child instead
of a duplicate (the desktop retry covers those flavors too, but the key
never reached the backend).
- _build_branch_agent records parent_session_id/branch_title on the child
runtime so the idempotent hit answers the same parent/title as the fresh
reply, without a DB round-trip.
- The session.create idempotent-hit early return now honors
copy_parent_history: a retried session.branch_stored answers
messages_omitted=true and never puts the copied transcript on the wire
(its result contract forbids 'messages', and re-serializing the parent
transcript through the renderer is what branch_stored exists to avoid).
- Regenerated gateway-contract artifacts; two use-session-actions tests
updated to expect the (opaque) key in branch params.
Red on main: tests/tui_gateway/test_protocol.py::
test_session_branch_stored_accepts_idempotency_key fails with the exact
4000 the e2e saw. Green with the fix, plus
test_session_branch_idempotency_key_dedupes_retry (server suite).
e2e lineage-sidebar passes locally on macOS (14-19s; CI PR run failed at 1.2m).