Message Protocol
The extension coordinates message flow between the webview UI and agent process. Messages are identified by UUIDs and routed based on tab/session mappings.
Message Identity
The system uses two separate identification mechanisms:
Message IDs (UUIDs): Identify specific prompt/response conversations. When a user sends a prompt, the webview generates a UUID message ID. All response chunks for that prompt include the same message ID, allowing the UI to associate chunks with the correct prompt and render them in the right place. Message IDs enable multiple concurrent prompts (user sends prompt in tab A while tab B is still streaming a response).
Message indices (numbers): Monotonically increasing integers assigned by the extension per tab, used exclusively for deduplication. When the webview is hidden and shown, the extension may replay messages to ensure nothing was missed. The webview tracks the last index it saw per tab (via lastSeenIndex map) and ignores messages with index <= lastSeenIndex[tabId]. This prevents duplicate response chunks from appearing in the UI.
Why both? Message IDs provide semantic identity ("which conversation is this?"). Message indices provide delivery tracking ("have I seen this before?"). The extension assigns indices sequentially as messages flow through; the webview uses UUIDs for UI routing and indices for deduplication.
Message Flow Patterns
Opening a New Tab
sequenceDiagram
participant User
participant Webview
participant Extension
participant Agent
User->>Webview: Opens new tab
Webview->>Webview: Generate tab ID (UUID)
Webview->>Extension: new-tab (tabId)
Extension->>Agent: new-session
Agent->>Agent: Initialize session
Agent->>Extension: session-created (sessionId)
Extension->>Extension: Store tabId → sessionId mapping
Why UUID generation in webview? The webview owns tab lifecycle. Generating IDs at the source avoids round-trip coordination with the extension.
Why separate session IDs? The agent owns session identity. Tab IDs are UI concepts; session IDs are agent concepts. The extension maps between them without understanding either.
Sending a Prompt
sequenceDiagram
participant User
participant Webview
participant Extension
participant Agent
User->>Webview: Types message
Webview->>Extension: prompt (tabId, messageId, text)
Extension->>Extension: Lookup sessionId for tabId
Extension->>Agent: process-prompt (sessionId, text)
loop Streaming response
Agent->>Extension: response-chunk (sessionId, chunk)
Extension->>Extension: Lookup tabId for sessionId
alt Webview visible
Extension->>Webview: response-chunk (tabId, messageId, chunk)
Webview->>Webview: Append to message stream
else Webview hidden
Extension->>Extension: Buffer message
end
end
Agent->>Extension: response-complete (sessionId)
Extension->>Webview: response-complete (tabId, messageId)
Webview->>Webview: End message stream
Why streaming? AI responses can take seconds to complete. Streaming provides immediate feedback and allows users to start reading while generation continues.
Why message IDs? Multiple prompts can be in flight simultaneously (user sends prompt in tab A while tab B is still receiving a response). Message IDs ensure response chunks are associated with the correct prompt.
Why buffer when hidden? VSCode can hide webviews at any time (user switches away, collapses sidebar). Buffering ensures the UI sees all messages when it becomes visible again.
Closing a Tab
sequenceDiagram
participant User
participant Webview
participant Extension
participant Agent
User->>Webview: Closes tab
Webview->>Extension: close-tab (tabId)
Extension->>Extension: Lookup sessionId for tabId
Extension->>Agent: close-session (sessionId)
Agent->>Agent: Cleanup session state
Extension->>Extension: Remove tabId → sessionId mapping
Why explicit close messages? Allows agent to clean up resources (free memory, close file handles) rather than leaking session state indefinitely.
Message Identification Strategy
Tab IDs
- Generated by: Webview (when user creates new tab)
- Format: UUID v4
- Scope: UI-only concept
- Lifetime: From tab creation to tab close
Session IDs
- Generated by: Agent (in response to new-session)
- Format: Agent-defined (typically UUID)
- Scope: Agent-only concept
- Lifetime: From session creation to session close
Message IDs
- Generated by: Webview (when user sends prompt)
- Format: UUID v4
- Scope: Used by both webview and extension for response routing
- Lifetime: From prompt send to response complete
Why three separate ID spaces? Each layer owns its identity domain. This avoids coupling and eliminates coordination overhead.
Bidirectional Mapping
The extension maintains two maps:
tabId → sessionId (for extension → agent messages)
sessionId → tabId (for agent → extension messages)
Synchronization: Maps are updated atomically when session creation completes. Both directions always stay consistent.
Cleanup: Both mappings are removed when either tab closes or session ends.
Message Ordering Guarantees
Within a session: Agent processes prompts sequentially. A second prompt won't start processing until the first response completes.
Across sessions: No ordering guarantees. Tabs are independent. Multiple sessions can stream responses simultaneously.
Webview messages: Delivered in order sent, but delivery timing depends on webview visibility. Buffered messages are replayed in order when webview becomes visible.
Error Handling
Agent crashes: Extension detects process exit, notifies all active tabs. Tabs display error state. User can trigger agent restart.
Webview disposal: Extension maintains agent sessions. If webview is recreated (VSCode restart), extension can restore tab → session mappings and continue existing sessions.
Message delivery failure: If webview is disposed while messages are buffered, messages are discarded. Agent sessions may continue running. Next webview instantiation can restore session state.
Design Rationale
Why not request/response? Streaming responses require continuous message flow, not single request/reply pairs. The protocol is inherently asynchronous.
Why not share IDs across layers? Each layer has different lifecycle concerns. Decoupling identity spaces allows independent evolution. Extension acts as impedance matcher between UI tab identity and agent session identity.
Why buffer in extension instead of agent? Agent shouldn't need to know about webview lifecycle. Extension handles VSCode-specific concerns (visibility, disposal) to keep agent implementation portable.