feat(desktop+acp+mcp): deterministic nested thread replies via persisted reply context#322
feat(desktop+acp+mcp): deterministic nested thread replies via persisted reply context#322tlongwell-block merged 11 commits intomainfrom
Conversation
thomaspblock
commented
Apr 14, 2026
- Adds deterministic nested thread UX in Desktop: top-level timeline + thread summary rows + right-side thread panel with nested thread navigation.
- Introduces threadPanel utilities to build stable main timeline entries and thread panel branch data.
- Scopes typing indicators to thread branches by adding root/reply tags and splitting typing display between main timeline and open thread panel.
- Adds ancestor backfilling (useLoadMissingAncestors) to fetch missing root/parent events referenced by thread tags.
- Persists per-channel reply scope in ACP via a JSON reply-context sidecar file and publishes its path to MCP via SPROUT_REPLY_CONTEXT_FILE.
- Updates MCP server to read the reply-context store and force parent_event_id (and disable broadcast) to keep agent/tool replies in the correct nested thread level.
- Adjusts ACP thread context fetching to anchor to the reply parent when available.
- Updates E2E messaging tests and file-size checks.
Add Slack-style thread summaries and drilldown navigation for channel replies, and scope typing indicators to the active thread so deeper conversations stay isolated from the main timeline. Made-with: Cursor
Reduce thread panel visual noise so the nested reply column feels lighter and more focused while preserving the existing thread behavior. Made-with: Cursor
Bring human-reply up to date with the latest main branch changes while keeping the thread panel cleanup and message-row visual tweaks. Made-with: Cursor
Make ACP publish the active thread parent for each channel turn and have sprout-mcp treat that context as authoritative so @agent replies stay at the current depth unless the user explicitly drills deeper. Made-with: Cursor
Clean up the nested thread panel follow-up changes so the desktop checks pass again by removing the unused thread prop, extracting ancestor loading out of ChannelScreen, and aligning the affected files with the current formatting and size guardrails. Made-with: Cursor
Keep bot follow-up messages anchored to the current reply branch after the initial turn, and tighten the desktop reply summary spacing so branch counts stay visually attached to their message. Made-with: Cursor
Use the immediate reply parent as the deterministic thread context anchor so deeper nested agent replies stay scoped to the drilled branch instead of falling back to the top-level root. Made-with: Cursor
Interrupt stale owner-triggered agent turns so fresh mentions in the desktop client don't appear to hang behind older work. Auto-scroll thread panels and tighten reply summary alignment so new responses stay visible in the branch the user is reading. Made-with: Cursor
|
@codex please review |
|
@tlongwell-block I think this has ACP changes that you might want to review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: caae96f527
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| .parent_event_id | ||
| .as_deref() | ||
| .or(thread_tags.root_event_id.as_deref()) |
There was a problem hiding this comment.
Use root_event_id when fetching thread context
conversation_context_anchor_event_id now prefers parent_event_id, but GET /api/channels/{id}/threads/{event_id} is implemented as a root-thread lookup (get_thread binds the path param as root_id_bytes, and get_thread_replies filters WHERE tm.root_event_id = $1 in crates/sprout-relay/src/api/messages.rs and crates/sprout-db/src/thread.rs). In nested replies where parent_event_id != root_event_id, this change passes a non-root ID and the ACP context fetch drops the actual thread history (typically returning only the parent event), which degrades reply quality for nested-thread prompts.
Useful? React with 👍 / 👎.
Add robust websocket infrastructure to the mobile app, mirroring the desktop's Nostr NIP-01/NIP-42 architecture. This enables real-time messaging, typing indicators, and presence — the foundation for all future mobile features. Core infrastructure: - RelaySocket: low-level websocket + NIP-42 AUTH handshake with challenge/response signing and auth_token support - RelaySession: subscription manager with history fetch (REQ/EOSE), live subscriptions, 16ms event batching, exponential backoff reconnect (1s→30s), and replay with since:lastSeen-5s - AppLifecycleProvider: disconnect on background (30s grace), reconnect on foreground/network restore via connectivity_plus - NostrEvent/NostrFilter models and EventKind constants Feature providers: - ChannelMessagesProvider: per-channel message list with history + live updates, pagination via fetchOlder() - ChannelTypingProvider: per-channel typing indicators (8s TTL) - SendMessageProvider: signs events with user's nsec, POSTs to relay - UserCacheProvider: batched profile lookups for names/avatars - ChannelsProvider migrated from 30s polling to hybrid HTTP + live Channel detail page: - Message list with avatars, display names, timestamps - Typing indicator bar - Compose bar with send button Pairing changes (desktop + mobile): - Desktop includes nsec in pairing payload for NIP-42 signing - Mobile stores nsec in FlutterSecureStorage - Removed debug-mode auth bypass (pairing flow is now required) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
tlongwell-block
left a comment
There was a problem hiding this comment.
let's look at how we get threading info to mcp first
… instruction Remove the reply-context sidecar file mechanism (~560 lines) that forced the MCP to use a specific parent_event_id on every send_message / send_diff_message call. The agent already receives the triggering event's ID in the prompt — the sidecar was solving a prompt engineering problem with cross-process file IPC, mutexes, and duplicate struct definitions. Replace with a single prompt instruction appended to the context hints for thread replies (both channel and DM): IMPORTANT: When responding, pass parent_event_id="<event_id>" on EVERY send_message and send_diff_message call in this turn. Do not set broadcast_to_channel. This also: - Reverts conversation_context_anchor_event_id to the original root-only behavior (the PR's parent-first preference broke context fetching for nested replies because the relay's /threads/ endpoint queries by root_event_id, not arbitrary event IDs) - Updates send_diff_message tool description to mention parent_event_id for threading (pre-existing gap the sidecar was papering over) - Adds 7 tests covering channel threads, DM threads, nested threads, top-level messages, DM non-replies, and batched-event edge cases Removed: - crates/sprout-acp/src/reply_context.rs (157 lines) - crates/sprout-mcp/src/reply_context.rs (131 lines) - ReplyContextStore, install_reply_context, select_parent_event_id, select_broadcast, effective_reply_context, SPROUT_REPLY_CONTEXT_FILE - All sidecar-related tests and plumbing in main.rs, pool.rs, server.rs Added: - append_reply_instruction() helper in queue.rs - triggering_event_id parameter on format_context_hints - 7 new prompt-format tests
* origin/main: [codex] Fix authz, scope propagation, and shell-injection bugs (#320) feat(mobile): implement Activity tab with personalized feed (#337) feat(mobile): upgrade mobile_scanner to v7 (Apple Vision) (#336) feat(mobile): app branding — icon, name, launch screen (#335) fix: cancel TTS on remote human speech in multi-participant huddles (#332) feat(mobile): design refresh — navigation, search, reactions (#334) feat(desktop+acp+mcp): deterministic nested thread replies via persisted reply context (#322) feat(mobile): channel management — create, browse, join/leave, DMs, canvas (#331) fix: derive staging ports from worktree to avoid collisions (#329) fix: mentions survive copy/paste from chat into composer (#328) feat(home): add activity and agent feed sections with deep-linking (#330)