Notify subscribers when thread read state changes (#52384)

## What changed

Add the experimental `thread/readState/changed` notification with `threadId` and the committed `readState` receipt. Send it to existing thread subscribers when terminal activity updates read state or a revert clears a removed unread position. Emit terminal read-state updates before `turn/completed` so completion remains the last notification for that result.

Expose the notification in generated JSON schemas, TypeScript types, and Python SDK models. Document experimental capability gating, subscription lifetime, notification opt-out, and refreshing receipts when concurrent responses disagree.

## Testing

Extend the read-state integration test to assert that the notification receipt matches the receipt returned by `thread/read` after terminal activity.

GitOrigin-RevId: 28935352a881b7a5e2e7f629173c3b2061bd7941
This commit is contained in:
Brent Traut
2026-10-09 02:59:21 +00:00
committed by copyberry
parent 5b3c1522f8
commit 82883da25e
22 changed files with 382 additions and 11 deletions
@@ -2303,12 +2303,24 @@ impl ThreadRequestProcessor {
codex_state::ReadStateOperation::Read,
)
.await
.map(|_| ()),
Ok(None) => Ok(()),
.map(|update| matches!(update, codex_state::ReadStateUpdate::Applied(_))),
Ok(None) => Ok(false),
Err(err) => Err(err),
};
if let Err(err) = retirement {
tracing::warn!("reverted history but could not update unread position: {err}")
match retirement {
Ok(true) => {
super::thread_read_state::notify(
db,
&self.thread_state_manager,
&self.outgoing,
thread_id,
)
.await;
}
Ok(false) => {}
Err(err) => {
tracing::warn!("reverted history but could not update unread position: {err}")
}
}
}
let response = self