fix: sync mobile-initiated messages and threads back to desktop - #55
Merged
Merged
Conversation
When prompts are sent from the mobile dashboard (via proxy), messages and threads are stored only in the proxy's local JSON. The desktop never sees them because the sync was one-directional (host -> proxy). Add reverse sync (proxy -> host) that runs when the desktop loads data: - getMessages() imports missing messages from the proxy into host DB - findByProject() imports missing threads from the proxy into host DB - findById() syncs thread status from proxy when it's more recent - Add fetchThreadMessages, fetchThread, fetchProjectThreads methods to ProxyProjectsService for pulling data from the proxy API Made-with: Cursor
When a mobile-initiated agent run is active, the desktop didn't see new messages in real-time because the proxy bridge path bypasses the host's agent WebSocket. Add a 5s proxy poller that runs for Daytona projects with subscribed desktop clients: - Detects new messages imported from the proxy (via getMessages) - Emits them as agent_message events to subscribed desktop clients - Syncs thread status changes from the proxy - Skips threads with active host-side handlers (avoids duplicates) - Starts on subscribe_project, stops when last client disconnects Made-with: Cursor
The previous agent_message approach didn't work for user text prompts because the dashboard only processes user messages containing tool_result blocks. Switch to a dedicated proxy_sync event that sends full DB-format messages (all roles). The dashboard handler deduplicates by message ID and merges into the active thread's message list. Made-with: Cursor
The bridge previously stored a single state.ws reference, overwritten by each new connection. When the proxy connected to execute a mobile prompt, it replaced the desktop orchestrator's connection. After the proxy disconnected, state.ws became null, causing "Bridge did not acknowledge the prompt" for desktop prompts. Convert to a multi-client model: - Track all connected clients in a wsClients Set - Add broadcastWs() helper that sends to all connected clients - On close, remove from Set and fall back to another client for state.ws - Replace all state.ws.send() calls with broadcastWs() Both the desktop orchestrator and proxy can now be connected simultaneously. Made-with: Cursor
Two issues fixed: 1. Desktop send_prompt never persisted the user's text message to the DB, so it was invisible to the proxy/webapp. Now addMessage is called before executeAgainstSandbox. 2. User messages are synced to the proxy immediately when added (not just on agent completion), so the webapp sees the prompt right away even while the agent is still running. Made-with: Cursor
1. Desktop duplication: proxy_sync handler now de-duplicates user messages by text content (not just ID), since the optimistically added message has a different ID than the server-persisted one. 2. Webapp live updates: sync all messages to the proxy on every addMessage call (not just user prompts), so assistant responses appear on the webapp during the agent run without needing a refresh. Made-with: Cursor
The mobile dashboard only polled for new messages after sending a prompt itself. When the desktop sent a prompt, the webapp required a manual refresh. Add a 5s background poll that: - Fetches new messages and updates the view when count changes - Checks promptStatus to detect desktop-initiated agent runs - Switches to fast 2s polling when an agent run is detected - Bump PROXY_SCRIPT_VERSION to 39 to deploy updated mobile dashboard Made-with: Cursor
The proxy poller was emitting all new messages including system result rows (empty content, just cost/usage metadata) and tool_result user messages, which rendered as empty lines with error icons on the desktop. Filter to only emit messages with renderable text content. Made-with: Cursor
The proxy poller used slice(prevCount) to find new messages, but getMessages() returns results ordered by createdAt. When proxy-imported messages have timestamps that sort before existing desktop messages, they land in the middle of the sorted list and slice misses them. Switch to tracking known message IDs in a Set. New messages are found by set difference, which is order-independent and catches messages regardless of where they sort in the timeline. Made-with: Cursor
The text-based dedup matched ALL user messages in the thread history, so repeated prompts like "are you there?" were silently dropped if the same text had been sent before. This is wrong — users can legitimately send the same text multiple times. ID-based dedup is sufficient because: - Desktop-sent prompts: server-persisted ID enters knownIds before proxy_sync detects it, so no duplicate is emitted - Webapp-sent prompts: unique proxy IDs aren't in the desktop store, so they correctly get added Made-with: Cursor
Document the full bidirectional data flow between desktop, proxy, and mobile dashboard including proxy poller, import mechanisms, bridge multi-client support, and the proxy_sync WebSocket event. Made-with: Cursor
vedranjukic
force-pushed
the
fix/proxy-to-host-message-sync
branch
from
April 12, 2026 13:19
00c0631 to
ce65ac9
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
POST /prompts), messages and threads are stored only in the proxy's local JSON store. The desktop app never sees them because the sync was one-directional (host -> proxy only).getMessages()pulls missing messages from the proxy and imports them into the host SQLite DBfindByProject()pulls missing threads from the proxy and imports themfindById()syncs thread status from the proxy when the proxy has a more recent update (e.g. mobile prompt completed)ProxyProjectsServicemethods:fetchThreadMessages,fetchThread,fetchProjectThreadsfor pulling data from the proxy APIonConflictDoNothingso duplicate IDs are safely ignoredTest plan
Made with Cursor