Skip to content

Commit c8b78dc

Browse files
author
ShayBC
committed
add support ro save paused tasks mode and change back to it once the task is resumed, also added subtasks logging
1 parent f835b8c commit c8b78dc

File tree

3 files changed

+41
-12
lines changed

3 files changed

+41
-12
lines changed

src/core/Cline.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ export class Cline {
9292
private isSubTask: boolean = false
9393
// a flag that indicated if this Cline instance is paused (waiting for provider to resume it after subtask completion)
9494
private isPaused: boolean = false
95+
private pausedModeSlug: string = defaultModeSlug
9596
api: ApiHandler
9697
private terminalManager: TerminalManager
9798
private urlContentFetcher: UrlContentFetcher
@@ -2642,15 +2643,24 @@ export class Cline {
26422643
break
26432644
}
26442645

2646+
// before switching roo mode (currently a global settings), save the current mode so we can
2647+
// resume the parent task (this Cline instance) later with the same mode
2648+
const currentMode =
2649+
(await this.providerRef.deref()?.getState())?.mode ?? defaultModeSlug
2650+
this.pausedModeSlug = currentMode
2651+
26452652
// Switch mode first, then create new task instance
26462653
const provider = this.providerRef.deref()
26472654
if (provider) {
26482655
await provider.handleModeSwitch(mode)
2656+
this.providerRef
2657+
.deref()
2658+
?.log(`[subtasks] Task: ${this.taskNumber} creating new task in '${mode}' mode`)
26492659
await provider.initClineWithSubTask(message)
26502660
pushToolResult(
26512661
`Successfully created new task in ${targetMode.name} mode with message: ${message}`,
26522662
)
2653-
// pasue the current task and start the new task
2663+
// set the isPaused flag to true so the parent task can wait for the sub-task to finish
26542664
this.isPaused = true
26552665
} else {
26562666
pushToolResult(
@@ -2899,7 +2909,20 @@ export class Cline {
28992909
// in this Cline request loop, we need to check if this cline (Task) instance has been asked to wait
29002910
// for a sub-task (it has launched) to finish before continuing
29012911
if (this.isPaused) {
2912+
this.providerRef.deref()?.log(`[subtasks] Task: ${this.taskNumber} has paused`)
29022913
await this.waitForResume()
2914+
this.providerRef.deref()?.log(`[subtasks] Task: ${this.taskNumber} has resumed`)
2915+
// waiting for resume is done, resume the task mode
2916+
const currentMode = (await this.providerRef.deref()?.getState())?.mode ?? defaultModeSlug
2917+
if (currentMode !== this.pausedModeSlug) {
2918+
// the mode has changed, we need to switch back to the paused mode
2919+
await this.providerRef.deref()?.handleModeSwitch(this.pausedModeSlug)
2920+
this.providerRef
2921+
.deref()
2922+
?.log(
2923+
`[subtasks] Task: ${this.taskNumber} has switched back to mode: '${this.pausedModeSlug}' from mode: '${currentMode}'`,
2924+
)
2925+
}
29032926
}
29042927

29052928
// getting verbose details is an expensive operation, it uses globby to top-down build file structure of project which for large projects can take a few seconds

src/core/webview/ClineProvider.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
9494
// Adds a new Cline instance to clineStack, marking the start of a new task.
9595
// The instance is pushed to the top of the stack (LIFO order).
9696
// When the task is completed, the top instance is removed, reactivating the previous task.
97-
addClineToStack(cline: Cline): void {
97+
async addClineToStack(cline: Cline) {
9898
// if cline.getTaskNumber() is -1, it means it is a new task
9999
if (cline.getTaskNumber() === -1) {
100100
// increase last cline number by 1
@@ -107,16 +107,22 @@ export class ClineProvider implements vscode.WebviewViewProvider {
107107
}
108108
// push the cline instance to the stack
109109
this.clineStack.push(cline)
110+
// get the current mode
111+
const currentMode = (await this.getState()).mode
112+
// log the task number and the mode
113+
this.log(`[subtasks] Task: ${cline.getTaskNumber()} started at '${currentMode}' mode`)
110114
}
111115

112116
// Removes and destroys the top Cline instance (the current finished task), activating the previous one (resuming the parent task).
113117
async removeClineFromStack() {
114118
// pop the top Cline instance from the stack
115119
var clineToBeRemoved = this.clineStack.pop()
116120
if (clineToBeRemoved) {
121+
const removedTaskNumber = clineToBeRemoved.getTaskNumber()
117122
await clineToBeRemoved.abortTask()
118123
// make sure no reference kept, once promises end it will be garbage collected
119124
clineToBeRemoved = undefined
125+
this.log(`[subtasks] Task: ${removedTaskNumber} stopped`)
120126
}
121127
// if the stack is empty, reset the last task number
122128
if (this.clineStack.length === 0) {
@@ -417,7 +423,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
417423
images,
418424
experiments,
419425
})
420-
this.addClineToStack(newCline)
426+
await this.addClineToStack(newCline)
421427
}
422428

423429
public async initClineWithHistoryItem(historyItem: HistoryItem) {
@@ -449,7 +455,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
449455
})
450456
// get this cline task number id from the history item and set it to newCline
451457
newCline.setTaskNumber(historyItem.number)
452-
this.addClineToStack(newCline)
458+
await this.addClineToStack(newCline)
453459
}
454460

455461
public async postMessageToWebview(message: ExtensionMessage) {

src/core/webview/__tests__/ClineProvider.test.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,7 @@ describe("ClineProvider", () => {
415415
const mockCline = new Cline() // Create a new mocked instance
416416

417417
// add the mock object to the stack
418-
provider.addClineToStack(mockCline)
418+
await provider.addClineToStack(mockCline)
419419

420420
// get the stack size before the abort call
421421
const stackSizeBeforeAbort = provider.getClineStackSize()
@@ -433,7 +433,7 @@ describe("ClineProvider", () => {
433433
expect(stackSizeBeforeAbort - stackSizeAfterAbort).toBe(1)
434434
})
435435

436-
test("addClineToStack adds multiple Cline instances to the stack", () => {
436+
test("addClineToStack adds multiple Cline instances to the stack", async () => {
437437
// Setup Cline instance with auto-mock from the top of the file
438438
const { Cline } = require("../../Cline") // Get the mocked class
439439
const mockCline1 = new Cline() // Create a new mocked instance
@@ -442,8 +442,8 @@ describe("ClineProvider", () => {
442442
Object.defineProperty(mockCline2, "taskId", { value: "test-task-id-2", writable: true })
443443

444444
// add Cline instances to the stack
445-
provider.addClineToStack(mockCline1)
446-
provider.addClineToStack(mockCline2)
445+
await provider.addClineToStack(mockCline1)
446+
await provider.addClineToStack(mockCline2)
447447

448448
// verify cline instances were added to the stack
449449
expect(provider.getClineStackSize()).toBe(2)
@@ -847,7 +847,7 @@ describe("ClineProvider", () => {
847847
const mockCline = new Cline() // Create a new mocked instance
848848
mockCline.clineMessages = mockMessages // Set test-specific messages
849849
mockCline.apiConversationHistory = mockApiHistory // Set API history
850-
provider.addClineToStack(mockCline) // Add the mocked instance to the stack
850+
await provider.addClineToStack(mockCline) // Add the mocked instance to the stack
851851

852852
// Mock getTaskWithId
853853
;(provider as any).getTaskWithId = jest.fn().mockResolvedValue({
@@ -894,7 +894,7 @@ describe("ClineProvider", () => {
894894
const mockCline = new Cline() // Create a new mocked instance
895895
mockCline.clineMessages = mockMessages
896896
mockCline.apiConversationHistory = mockApiHistory
897-
provider.addClineToStack(mockCline)
897+
await provider.addClineToStack(mockCline)
898898

899899
// Mock getTaskWithId
900900
;(provider as any).getTaskWithId = jest.fn().mockResolvedValue({
@@ -921,7 +921,7 @@ describe("ClineProvider", () => {
921921
const mockCline = new Cline() // Create a new mocked instance
922922
mockCline.clineMessages = [{ ts: 1000 }, { ts: 2000 }]
923923
mockCline.apiConversationHistory = [{ ts: 1000 }, { ts: 2000 }]
924-
provider.addClineToStack(mockCline)
924+
await provider.addClineToStack(mockCline)
925925

926926
// Trigger message deletion
927927
const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as jest.Mock).mock.calls[0][0]
@@ -1424,7 +1424,7 @@ describe("ClineProvider", () => {
14241424
// Setup Cline instance with auto-mock from the top of the file
14251425
const { Cline } = require("../../Cline") // Get the mocked class
14261426
const mockCline = new Cline() // Create a new mocked instance
1427-
provider.addClineToStack(mockCline)
1427+
await provider.addClineToStack(mockCline)
14281428

14291429
const testApiConfig = {
14301430
apiProvider: "anthropic" as const,

0 commit comments

Comments
 (0)