Skip to content

feat(desktop+acp+mcp): deterministic nested thread replies via persisted reply context#322

Merged
tlongwell-block merged 11 commits intomainfrom
bot-reply
Apr 15, 2026
Merged

feat(desktop+acp+mcp): deterministic nested thread replies via persisted reply context#322
tlongwell-block merged 11 commits intomainfrom
bot-reply

Conversation

@thomaspblock
Copy link
Copy Markdown
Collaborator

  • 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
@wesbillman
Copy link
Copy Markdown
Collaborator

@codex please review

@wesbillman
Copy link
Copy Markdown
Collaborator

@tlongwell-block I think this has ACP changes that you might want to review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread crates/sprout-acp/src/pool.rs Outdated
Comment on lines +1233 to +1235
.parent_event_id
.as_deref()
.or(thread_tags.root_event_id.as_deref())
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

wesbillman added a commit that referenced this pull request Apr 15, 2026
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>
Copy link
Copy Markdown
Collaborator

@tlongwell-block tlongwell-block left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
@tlongwell-block tlongwell-block merged commit f03bbe3 into main Apr 15, 2026
10 checks passed
@tlongwell-block tlongwell-block deleted the bot-reply branch April 15, 2026 21:58
tlongwell-block added a commit that referenced this pull request Apr 16, 2026
* 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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants