feat(web): add state watches for projects, chat history and the agent roster
Summary
- The project list, the chat-history index and the agent roster reach web clients as
seq = 0events. A client can miss one, receive it twice, or never receive it on a socket with no session subscription. A client cannot rebuild its state from them. - This change adds state watches. A watch sends the full state first, then deltas under a contiguous revision. A client that sees a gap asks again for fresh state.
Related Issue
Refs #1070
Searched open and closed PRs for the same problem. No PR covers it. This is the fourth of several PRs for the read-model work in #1070. It builds on #1093. A second PR switches the web client to the watches and retires the old events.
Type of Change
- feat: new feature
- fix: bug fix
- docs: documentation only
- style: formatting or non-functional styling changes
- refactor: internal cleanup with no behavior change
- perf: performance improvement
- test: adds or updates tests
- build: build or dependency changes
- ci: CI/CD workflow changes
- chore: maintenance or tooling
- revert: reverts a previous change
What Changed
- The host has a watch hub. A client sends
watch{watch, scope?}forprojects,chat_historyoragents. The host queues one full-state frame and then replies. Later changes arrive as delta frames. - Each frame carries a revision
(hostEpoch, n).ncounts up by one for each connection and watch. A new serve start gets a newhostEpoch. - A socket receives each watch once. It needs no session subscription. A second
watchreplaces the first and sends fresh state.unwatchreleases it. - The host never waits to queue a delta. A delta that finds the connection queue full is dropped but keeps its
n. A resync marker then takes the nextn, so the client always sees the gap. - The host publishes to the hub where the project list, the history index and the agent roster change. The old
projects_changed,chat_history_changedand agent events still arrive in this PR. WsAcpTransportrouteswatchframes to a newWatchClient. The client applies a delta only when it is the next revision of the same epoch. A skipped revision, a new epoch, a resync marker or a reconnect makes it resubscribe.- View-only connections can watch.
WS_PROTOCOL_VERSIONgoes from 4 to 5.
How It Was Tested
-
bun run ci(Biome lint + format + imports, strict mode) -
bun run typecheck -
bun run test -
cargo clippy --all-targets -- -D warnings(in src-tauri) -
cargo test(in src-tauri) - Manual verification completed
- Not applicable
New tests cover: contiguous revisions and the state frame first; one delivery per socket, also without a session subscription; a resubscribe that returns fresh state at n = 1; a delta dropped for a full queue that leaves a gap and a resync marker, over the real relay loop; a marker that does not outlive its subscription; release when the connection closes; the publish after add, update, remove and set-default of a project; the history delta after a delete and after a host record; the client resubscribing on a gap, a new epoch, a resync marker and a reconnect.
Screenshots or Recordings
No user-visible UI change. The new client code has no consumer in this PR. The Rust and client tests above cover the behavior.
CI & Review Gate
- All CI checks pass (PR Validation, Rust Checks, Build Verification, Security Scans)
- Code review comments from CodeRabbit / Claude have been addressed or resolved
- No unresolved review findings remain
Checklist
- My PR title follows the conventional commit format used by this repo
- I linked the related issue or explained why none exists
- I updated docs when needed
- I added or updated tests when needed
- I verified the change does not introduce unrelated modifications
- I read AGENTS.md and followed the contributor guidelines
- A human reviewed the complete diff before submission
Summary by cubic
Adds recoverable state watches for projects, chat history, and the agent roster, so clients can rebuild state instead of relying on easy-to-miss seq = 0 events.
- Each watch sends a full snapshot followed by revisioned deltas; clients resubscribe after gaps, host restarts, reconnects, or handler failures.
- Watches work per socket without a session subscription and are allowed on view-only connections.
- Intentional agent kills now publish roster removals, marked intentional under the agents lock before the announcement so a racing teardown can't emit a late
agent_disconnected; existing change events remain during the transition. WS_PROTOCOL_VERSIONis now 5.
Summary by CodeRabbit
- New Features
- Added real-time watches for project lists, chat-history indexes, and agent rosters, delivering an initial snapshot followed by updates.
- Watches resynchronize after reconnects, revision gaps, or server changes. Existing change notifications remain available.
- Project, history, and agent updates are reflected in their respective watches.
- Documentation
- Documented the watch protocol and recovery behavior. WebSocket protocol version is now 5.