11import assert from "@/common/utils/assert" ;
2- import type { MuxMessage , DisplayedMessage } from "@/common/types/message" ;
2+ import type { MuxMessage , DisplayedMessage , QueuedMessage } from "@/common/types/message" ;
33import { createMuxMessage } from "@/common/types/message" ;
44import type { FrontendWorkspaceMetadata } from "@/common/types/workspace" ;
55import type { WorkspaceChatMessage } from "@/common/types/ipc" ;
@@ -14,6 +14,8 @@ import {
1414 isStreamError ,
1515 isDeleteMessage ,
1616 isMuxMessage ,
17+ isQueuedMessageChanged ,
18+ isRestoreToInput ,
1719} from "@/common/types/ipc" ;
1820import { MapStore } from "./MapStore" ;
1921import { createDisplayUsage } from "@/common/utils/tokens/displayUsage" ;
@@ -32,6 +34,7 @@ import { createFreshRetryState } from "@/browser/utils/messages/retryState";
3234export interface WorkspaceState {
3335 name : string ; // User-facing workspace name (e.g., "feature-branch")
3436 messages : DisplayedMessage [ ] ;
37+ queuedMessage : QueuedMessage | null ;
3538 canInterrupt : boolean ;
3639 isCompacting : boolean ;
3740 loading : boolean ;
@@ -111,6 +114,7 @@ export class WorkspaceStore {
111114 private historicalMessages = new Map < string , MuxMessage [ ] > ( ) ;
112115 private pendingStreamEvents = new Map < string , WorkspaceChatMessage [ ] > ( ) ;
113116 private workspaceMetadata = new Map < string , FrontendWorkspaceMetadata > ( ) ; // Store metadata for name lookup
117+ private queuedMessages = new Map < string , QueuedMessage | null > ( ) ; // Cached queued messages
114118
115119 /**
116120 * Map of event types to their handlers. This is the single source of truth for:
@@ -201,6 +205,36 @@ export class WorkspaceStore {
201205 aggregator . handleMessage ( data ) ;
202206 this . states . bump ( workspaceId ) ;
203207 } ,
208+ "queued-message-changed" : ( workspaceId , _aggregator , data ) => {
209+ if ( ! isQueuedMessageChanged ( data ) ) return ;
210+
211+ // Create QueuedMessage once here instead of on every render
212+ // Use displayText which handles slash commands (shows /compact instead of expanded prompt)
213+ // Show queued message if there's text OR images (support image-only queued messages)
214+ const hasContent = data . queuedMessages . length > 0 || ( data . imageParts ?. length ?? 0 ) > 0 ;
215+ const queuedMessage : QueuedMessage | null = hasContent
216+ ? {
217+ id : `queued-${ workspaceId } ` ,
218+ content : data . displayText ,
219+ imageParts : data . imageParts ,
220+ }
221+ : null ;
222+
223+ this . queuedMessages . set ( workspaceId , queuedMessage ) ;
224+ this . states . bump ( workspaceId ) ;
225+ } ,
226+ "restore-to-input" : ( workspaceId , _aggregator , data ) => {
227+ if ( ! isRestoreToInput ( data ) ) return ;
228+
229+ // Use INSERT_TO_CHAT_INPUT event with mode="replace"
230+ window . dispatchEvent (
231+ createCustomEvent ( CUSTOM_EVENTS . INSERT_TO_CHAT_INPUT , {
232+ text : data . text ,
233+ mode : "replace" ,
234+ imageParts : data . imageParts ,
235+ } )
236+ ) ;
237+ } ,
204238 } ;
205239
206240 // Cache of last known recency per workspace (for change detection)
@@ -305,6 +339,7 @@ export class WorkspaceStore {
305339 return {
306340 name : metadata ?. name ?? workspaceId , // Fall back to ID if metadata missing
307341 messages : aggregator . getDisplayedMessages ( ) ,
342+ queuedMessage : this . queuedMessages . get ( workspaceId ) ?? null ,
308343 canInterrupt : activeStreams . length > 0 ,
309344 isCompacting : aggregator . isCompacting ( ) ,
310345 loading : ! hasMessages && ! isCaughtUp ,
0 commit comments