add support ro save paused tasks mode and change back to it once the task is resumed, also added subtasks logging

This commit is contained in:
ShayBC 2025-02-27 23:33:54 +02:00
parent f835b8c3e2
commit c8b78dc7bf
3 changed files with 41 additions and 12 deletions

View file

@ -92,6 +92,7 @@ export class Cline {
private isSubTask: boolean = false
// a flag that indicated if this Cline instance is paused (waiting for provider to resume it after subtask completion)
private isPaused: boolean = false
private pausedModeSlug: string = defaultModeSlug
api: ApiHandler
private terminalManager: TerminalManager
private urlContentFetcher: UrlContentFetcher
@ -2642,15 +2643,24 @@ export class Cline {
break
}
// before switching roo mode (currently a global settings), save the current mode so we can
// resume the parent task (this Cline instance) later with the same mode
const currentMode =
(await this.providerRef.deref()?.getState())?.mode ?? defaultModeSlug
this.pausedModeSlug = currentMode
// Switch mode first, then create new task instance
const provider = this.providerRef.deref()
if (provider) {
await provider.handleModeSwitch(mode)
this.providerRef
.deref()
?.log(`[subtasks] Task: ${this.taskNumber} creating new task in '${mode}' mode`)
await provider.initClineWithSubTask(message)
pushToolResult(
`Successfully created new task in ${targetMode.name} mode with message: ${message}`,
)
// pasue the current task and start the new task
// set the isPaused flag to true so the parent task can wait for the sub-task to finish
this.isPaused = true
} else {
pushToolResult(
@ -2899,7 +2909,20 @@ export class Cline {
// in this Cline request loop, we need to check if this cline (Task) instance has been asked to wait
// for a sub-task (it has launched) to finish before continuing
if (this.isPaused) {
this.providerRef.deref()?.log(`[subtasks] Task: ${this.taskNumber} has paused`)
await this.waitForResume()
this.providerRef.deref()?.log(`[subtasks] Task: ${this.taskNumber} has resumed`)
// waiting for resume is done, resume the task mode
const currentMode = (await this.providerRef.deref()?.getState())?.mode ?? defaultModeSlug
if (currentMode !== this.pausedModeSlug) {
// the mode has changed, we need to switch back to the paused mode
await this.providerRef.deref()?.handleModeSwitch(this.pausedModeSlug)
this.providerRef
.deref()
?.log(
`[subtasks] Task: ${this.taskNumber} has switched back to mode: '${this.pausedModeSlug}' from mode: '${currentMode}'`,
)
}
}
// 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

View file

@ -94,7 +94,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
// Adds a new Cline instance to clineStack, marking the start of a new task.
// The instance is pushed to the top of the stack (LIFO order).
// When the task is completed, the top instance is removed, reactivating the previous task.
addClineToStack(cline: Cline): void {
async addClineToStack(cline: Cline) {
// if cline.getTaskNumber() is -1, it means it is a new task
if (cline.getTaskNumber() === -1) {
// increase last cline number by 1
@ -107,6 +107,10 @@ export class ClineProvider implements vscode.WebviewViewProvider {
}
// push the cline instance to the stack
this.clineStack.push(cline)
// get the current mode
const currentMode = (await this.getState()).mode
// log the task number and the mode
this.log(`[subtasks] Task: ${cline.getTaskNumber()} started at '${currentMode}' mode`)
}
// Removes and destroys the top Cline instance (the current finished task), activating the previous one (resuming the parent task).
@ -114,9 +118,11 @@ export class ClineProvider implements vscode.WebviewViewProvider {
// pop the top Cline instance from the stack
var clineToBeRemoved = this.clineStack.pop()
if (clineToBeRemoved) {
const removedTaskNumber = clineToBeRemoved.getTaskNumber()
await clineToBeRemoved.abortTask()
// make sure no reference kept, once promises end it will be garbage collected
clineToBeRemoved = undefined
this.log(`[subtasks] Task: ${removedTaskNumber} stopped`)
}
// if the stack is empty, reset the last task number
if (this.clineStack.length === 0) {
@ -417,7 +423,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
images,
experiments,
})
this.addClineToStack(newCline)
await this.addClineToStack(newCline)
}
public async initClineWithHistoryItem(historyItem: HistoryItem) {
@ -449,7 +455,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
})
// get this cline task number id from the history item and set it to newCline
newCline.setTaskNumber(historyItem.number)
this.addClineToStack(newCline)
await this.addClineToStack(newCline)
}
public async postMessageToWebview(message: ExtensionMessage) {

View file

@ -415,7 +415,7 @@ describe("ClineProvider", () => {
const mockCline = new Cline() // Create a new mocked instance
// add the mock object to the stack
provider.addClineToStack(mockCline)
await provider.addClineToStack(mockCline)
// get the stack size before the abort call
const stackSizeBeforeAbort = provider.getClineStackSize()
@ -433,7 +433,7 @@ describe("ClineProvider", () => {
expect(stackSizeBeforeAbort - stackSizeAfterAbort).toBe(1)
})
test("addClineToStack adds multiple Cline instances to the stack", () => {
test("addClineToStack adds multiple Cline instances to the stack", async () => {
// Setup Cline instance with auto-mock from the top of the file
const { Cline } = require("../../Cline") // Get the mocked class
const mockCline1 = new Cline() // Create a new mocked instance
@ -442,8 +442,8 @@ describe("ClineProvider", () => {
Object.defineProperty(mockCline2, "taskId", { value: "test-task-id-2", writable: true })
// add Cline instances to the stack
provider.addClineToStack(mockCline1)
provider.addClineToStack(mockCline2)
await provider.addClineToStack(mockCline1)
await provider.addClineToStack(mockCline2)
// verify cline instances were added to the stack
expect(provider.getClineStackSize()).toBe(2)
@ -847,7 +847,7 @@ describe("ClineProvider", () => {
const mockCline = new Cline() // Create a new mocked instance
mockCline.clineMessages = mockMessages // Set test-specific messages
mockCline.apiConversationHistory = mockApiHistory // Set API history
provider.addClineToStack(mockCline) // Add the mocked instance to the stack
await provider.addClineToStack(mockCline) // Add the mocked instance to the stack
// Mock getTaskWithId
;(provider as any).getTaskWithId = jest.fn().mockResolvedValue({
@ -894,7 +894,7 @@ describe("ClineProvider", () => {
const mockCline = new Cline() // Create a new mocked instance
mockCline.clineMessages = mockMessages
mockCline.apiConversationHistory = mockApiHistory
provider.addClineToStack(mockCline)
await provider.addClineToStack(mockCline)
// Mock getTaskWithId
;(provider as any).getTaskWithId = jest.fn().mockResolvedValue({
@ -921,7 +921,7 @@ describe("ClineProvider", () => {
const mockCline = new Cline() // Create a new mocked instance
mockCline.clineMessages = [{ ts: 1000 }, { ts: 2000 }]
mockCline.apiConversationHistory = [{ ts: 1000 }, { ts: 2000 }]
provider.addClineToStack(mockCline)
await provider.addClineToStack(mockCline)
// Trigger message deletion
const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as jest.Mock).mock.calls[0][0]
@ -1424,7 +1424,7 @@ describe("ClineProvider", () => {
// Setup Cline instance with auto-mock from the top of the file
const { Cline } = require("../../Cline") // Get the mocked class
const mockCline = new Cline() // Create a new mocked instance
provider.addClineToStack(mockCline)
await provider.addClineToStack(mockCline)
const testApiConfig = {
apiProvider: "anthropic" as const,