@@ -10,7 +10,6 @@ import pWaitFor from "p-wait-for"
1010import { serializeError } from "serialize-error"
1111
1212import {
13- type RooCodeSettings ,
1413 type TaskLike ,
1514 type TaskMetadata ,
1615 type TaskEvents ,
@@ -42,6 +41,7 @@ import { CloudService, BridgeOrchestrator } from "@roo-code/cloud"
4241// api
4342import { ApiHandler , ApiHandlerCreateMessageMetadata , buildApiHandler } from "../../api"
4443import { ApiStream } from "../../api/transform/stream"
44+ import { maybeRemoveImageBlocks } from "../../api/transform/image-cleaning"
4545
4646// shared
4747import { findLastIndex } from "../../shared/array"
@@ -79,6 +79,7 @@ import { SYSTEM_PROMPT } from "../prompts/system"
7979
8080// core modules
8181import { ToolRepetitionDetector } from "../tools/ToolRepetitionDetector"
82+ import { restoreTodoListForTask } from "../tools/updateTodoListTool"
8283import { FileContextTracker } from "../context-tracking/FileContextTracker"
8384import { RooIgnoreController } from "../ignore/RooIgnoreController"
8485import { RooProtectedController } from "../protect/RooProtectedController"
@@ -88,7 +89,14 @@ import { truncateConversationIfNeeded } from "../sliding-window"
8889import { ClineProvider } from "../webview/ClineProvider"
8990import { MultiSearchReplaceDiffStrategy } from "../diff/strategies/multi-search-replace"
9091import { MultiFileSearchReplaceDiffStrategy } from "../diff/strategies/multi-file-search-replace"
91- import { readApiMessages , saveApiMessages , readTaskMessages , saveTaskMessages , taskMetadata } from "../task-persistence"
92+ import {
93+ type ApiMessage ,
94+ readApiMessages ,
95+ saveApiMessages ,
96+ readTaskMessages ,
97+ saveTaskMessages ,
98+ taskMetadata ,
99+ } from "../task-persistence"
92100import { getEnvironmentDetails } from "../environment/getEnvironmentDetails"
93101import { checkContextWindowExceededError } from "../context/context-management/context-error-handling"
94102import {
@@ -100,12 +108,11 @@ import {
100108 checkpointDiff ,
101109} from "../checkpoints"
102110import { processUserContentMentions } from "../mentions/processUserContentMentions"
103- import { ApiMessage } from "../task-persistence/apiMessages"
104111import { getMessagesSinceLastSummary , summarizeConversation } from "../condense"
105- import { maybeRemoveImageBlocks } from "../../api/transform/image-cleaning"
106- import { restoreTodoListForTask } from "../tools/updateTodoListTool"
107- import { AutoApprovalHandler } from "./AutoApprovalHandler"
108112import { Gpt5Metadata , ClineMessageWithMetadata } from "./types"
113+ import { MessageQueueService } from "../message-queue/MessageQueueService"
114+
115+ import { AutoApprovalHandler } from "./AutoApprovalHandler"
109116
110117const MAX_EXPONENTIAL_BACKOFF_SECONDS = 600 // 10 minutes
111118const DEFAULT_USAGE_COLLECTION_TIMEOUT_MS = 5000 // 5 seconds
@@ -259,6 +266,9 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
259266 // Task Bridge
260267 enableBridge : boolean
261268
269+ // Message Queue Service
270+ public readonly messageQueueService : MessageQueueService
271+
262272 // Streaming
263273 isWaitingForFirstChunk = false
264274 isStreaming = false
@@ -356,9 +366,12 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
356366 TelemetryService . instance . captureTaskCreated ( this . taskId )
357367 }
358368
359- // Initialize the assistant message parser
369+ // Initialize the assistant message parser.
360370 this . assistantMessageParser = new AssistantMessageParser ( )
361371
372+ this . messageQueueService = new MessageQueueService ( )
373+ this . messageQueueService . on ( "stateChanged" , ( ) => this . providerRef . deref ( ) ?. postStateToWebview ( ) )
374+
362375 // Only set up diff strategy if diff is enabled.
363376 if ( this . diffEnabled ) {
364377 // Default to old strategy, will be updated if experiment is enabled.
@@ -759,10 +772,14 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
759772 // The state is mutable if the message is complete and the task will
760773 // block (via the `pWaitFor`).
761774 const isBlocking = ! ( this . askResponse !== undefined || this . lastMessageTs !== askTs )
762- const isStatusMutable = ! partial && isBlocking
775+ const isMessageQueued = ! this . messageQueueService . isEmpty ( )
776+ const isStatusMutable = ! partial && isBlocking && ! isMessageQueued
763777 let statusMutationTimeouts : NodeJS . Timeout [ ] = [ ]
778+ let messageQueueTimeout : NodeJS . Timeout | undefined
764779
765780 if ( isStatusMutable ) {
781+ console . log ( `Task#ask will block -> type: ${ type } ` )
782+
766783 if ( isInteractiveAsk ( type ) ) {
767784 statusMutationTimeouts . push (
768785 setTimeout ( ( ) => {
@@ -797,9 +814,19 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
797814 } , 1_000 ) ,
798815 )
799816 }
817+ } else if ( isMessageQueued ) {
818+ console . log ( "Task#ask will process message queue after a timeout" )
819+
820+ messageQueueTimeout = setTimeout ( ( ) => {
821+ const message = this . messageQueueService . dequeueMessage ( )
822+
823+ if ( message ) {
824+ this . submitUserMessage ( message . text , message . images )
825+ }
826+ } , 1_000 )
800827 }
801828
802- // Wait for askResponse to be set
829+ // Wait for askResponse to be set.
803830 await pWaitFor ( ( ) => this . askResponse !== undefined || this . lastMessageTs !== askTs , { interval : 100 } )
804831
805832 if ( this . lastMessageTs !== askTs ) {
@@ -817,6 +844,10 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
817844 // Cancel the timeouts if they are still running.
818845 statusMutationTimeouts . forEach ( ( timeout ) => clearTimeout ( timeout ) )
819846
847+ if ( messageQueueTimeout ) {
848+ clearTimeout ( messageQueueTimeout )
849+ }
850+
820851 // Switch back to an active state.
821852 if ( this . idleAsk || this . resumableAsk || this . interactiveAsk ) {
822853 this . idleAsk = undefined
@@ -1406,8 +1437,8 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
14061437 newUserContent . push ( ...formatResponse . imageBlocks ( responseImages ) )
14071438 }
14081439
1409- // Ensure we have at least some content to send to the API
1410- // If newUserContent is empty, add a minimal resumption message
1440+ // Ensure we have at least some content to send to the API.
1441+ // If newUserContent is empty, add a minimal resumption message.
14111442 if ( newUserContent . length === 0 ) {
14121443 newUserContent . push ( {
14131444 type : "text" ,
@@ -1417,14 +1448,20 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
14171448
14181449 await this . overwriteApiConversationHistory ( modifiedApiConversationHistory )
14191450
1420- // Task resuming from history item
1421-
1451+ // Task resuming from history item.
14221452 await this . initiateTaskLoop ( newUserContent )
14231453 }
14241454
14251455 public dispose ( ) : void {
14261456 console . log ( `[Task#dispose] disposing task ${ this . taskId } .${ this . instanceId } ` )
14271457
1458+ // Dispose message queue.
1459+ try {
1460+ this . messageQueueService . dispose ( )
1461+ } catch ( error ) {
1462+ console . error ( "Error disposing message queue:" , error )
1463+ }
1464+
14281465 // Remove all event listeners to prevent memory leaks.
14291466 try {
14301467 this . removeAllListeners ( )
0 commit comments