@@ -16,6 +16,7 @@ import {
1616import { TelemetryService } from "@roo-code/telemetry"
1717
1818import { Task } from "../Task"
19+ import { SYSTEM_PROMPT } from "../../prompts/system"
1920import { createRateLimitClock } from "../RateLimitClock"
2021import { summarizeConversation } from "../../condense"
2122import { ClineProvider } from "../../webview/ClineProvider"
@@ -223,6 +224,15 @@ vi.mock("../../condense", async (importOriginal) => {
223224 } ) ,
224225 }
225226} )
227+
228+ vi . mock ( "../../prompts/system" , async ( importOriginal ) => {
229+ const actual = await importOriginal < typeof import ( "../../prompts/system" ) > ( )
230+ return {
231+ ...actual ,
232+ SYSTEM_PROMPT : vi . fn ( actual . SYSTEM_PROMPT ) ,
233+ }
234+ } )
235+
226236// Mock storagePathManager to prevent dynamic import issues.
227237vi . mock ( "../../../utils/storage" , ( ) => ( {
228238 getTaskDirectoryPath : vi
@@ -451,6 +461,73 @@ describe("Cline", () => {
451461 } )
452462 } )
453463
464+ describe ( "task-local configuration isolation" , ( ) => {
465+ it ( "uses the task mode and API configuration when focused provider state differs" , async ( ) => {
466+ const taskApiConfiguration : ProviderSettings = {
467+ ...mockApiConfig ,
468+ todoListEnabled : true ,
469+ }
470+ vi . spyOn ( mockProvider , "getState" ) . mockResolvedValue ( { mode : "architect" , mcpEnabled : false } )
471+
472+ const task = new Task ( {
473+ provider : mockProvider ,
474+ apiConfiguration : taskApiConfiguration ,
475+ task : "test task" ,
476+ startTask : false ,
477+ } )
478+ await task . getTaskMode ( )
479+
480+ vi . spyOn ( mockProvider , "getState" ) . mockResolvedValue ( {
481+ mode : "code" ,
482+ mcpEnabled : false ,
483+ apiConfiguration : { ...mockApiConfig , todoListEnabled : false } ,
484+ } )
485+ vi . mocked ( SYSTEM_PROMPT ) . mockResolvedValueOnce ( "mock system prompt" )
486+
487+ await getTaskTestAccess ( task ) . getSystemPrompt ( )
488+
489+ const systemPromptCall = requireDefined ( vi . mocked ( SYSTEM_PROMPT ) . mock . calls . at ( - 1 ) )
490+ expect ( systemPromptCall [ 5 ] ) . toBe ( "architect" )
491+ expect ( systemPromptCall [ 12 ] ) . toMatchObject ( { todoListEnabled : true } )
492+ } )
493+
494+ it ( "uses the task mode in request metadata when focused provider state differs" , async ( ) => {
495+ vi . spyOn ( mockProvider , "getState" ) . mockResolvedValue ( {
496+ mode : "ask" ,
497+ mcpEnabled : false ,
498+ autoApprovalEnabled : true ,
499+ requestDelaySeconds : 0 ,
500+ } )
501+ const task = new Task ( {
502+ provider : mockProvider ,
503+ apiConfiguration : mockApiConfig ,
504+ task : "test task" ,
505+ startTask : false ,
506+ } )
507+ await task . getTaskMode ( )
508+ vi . spyOn ( getTaskTestAccess ( task ) , "getSystemPrompt" ) . mockResolvedValue ( "mock system prompt" )
509+
510+ vi . spyOn ( mockProvider , "getState" ) . mockResolvedValue ( {
511+ mode : "code" ,
512+ mcpEnabled : false ,
513+ autoApprovalEnabled : true ,
514+ requestDelaySeconds : 0 ,
515+ } )
516+ const stream = ( async function * ( ) {
517+ yield { type : "text" , text : "response" } as ApiStreamChunk
518+ } ) ( )
519+ const createMessage = vi . spyOn ( task . api , "createMessage" ) . mockReturnValue ( stream )
520+ task . apiConversationHistory = [
521+ { role : "user" , content : [ { type : "text" , text : "test message" } ] , ts : Date . now ( ) } ,
522+ ]
523+
524+ await task . attemptApiRequest ( ) . next ( )
525+
526+ const metadata = requireDefined ( createMessage . mock . calls [ 0 ] ) [ 2 ]
527+ expect ( metadata ?. mode ) . toBe ( "ask" )
528+ } )
529+ } )
530+
454531 describe ( "sayAndCreateMissingParamError" , ( ) => {
455532 it ( "surfaces a localized error notice and returns the missing-parameter tool error for both relPath branches" , async ( ) => {
456533 const cline = new Task ( {
@@ -750,7 +827,7 @@ describe("Cline", () => {
750827 expect ( mockDelay ) . toHaveBeenCalledWith ( 1000 )
751828 } )
752829
753- it ( "should respect rate limit window in retry backoff" , async ( ) => {
830+ it ( "uses the task rate limit in retry backoff when focused provider state differs " , async ( ) => {
754831 const clock = createRateLimitClock ( )
755832 const rateLimitConfig = {
756833 ...mockApiConfig ,
@@ -815,15 +892,19 @@ describe("Cline", () => {
815892 const providerState = await mockProvider . getState ( )
816893 vi . spyOn ( mockProvider , "getState" ) . mockResolvedValue ( {
817894 ...providerState ,
818- apiConfiguration : rateLimitConfig ,
895+ apiConfiguration : {
896+ ...mockApiConfig ,
897+ rateLimitSeconds : 1 ,
898+ } ,
819899 autoApprovalEnabled : true ,
820900 requestDelaySeconds : 3 ,
821901 } )
822902
823903 const iterator = cline . attemptApiRequest ( 0 )
824904 await iterator . next ( )
825905
826- // rateLimitSeconds=10 > exponentialDelay=ceil(3*2^0)=3, so
906+ // The task rateLimitSeconds=10 (rather than the focused provider's 1)
907+ // exceeds exponentialDelay=ceil(3*2^0)=3, so
827908 // finalDelay=10 and the countdown loop fires delay(1000) ten times.
828909 expect ( mockDelay ) . toHaveBeenCalledWith ( 1000 )
829910 expect ( mockDelay ) . toHaveBeenCalledTimes ( 10 )
0 commit comments