Skip to content

Commit c410443

Browse files
committed
test: unskip subtasks e2e suite
1 parent bacb853 commit c410443

3 files changed

Lines changed: 155 additions & 58 deletions

File tree

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { LLMock } from "@copilotkit/aimock"
2+
3+
export const SUBTASK_PARENT_PROMPT = "SUBTASK_PARENT_CANCELLATION_SMOKE"
4+
export const SUBTASK_CHILD_PROMPT = "SUBTASK_CHILD_CALCULATOR_SMOKE"
5+
export const SUBTASK_CHILD_FOLLOWUP_ANSWER = "9"
6+
7+
export function addSubtaskFixtures(mock: InstanceType<typeof LLMock>) {
8+
mock.addFixture({
9+
match: {
10+
userMessage: new RegExp(SUBTASK_PARENT_PROMPT),
11+
},
12+
response: {
13+
toolCalls: [
14+
{
15+
name: "new_task",
16+
arguments: JSON.stringify({
17+
mode: "ask",
18+
message: SUBTASK_CHILD_PROMPT,
19+
}),
20+
id: "call_subtasks_parent_new_task_001",
21+
},
22+
],
23+
},
24+
})
25+
26+
mock.addFixture({
27+
match: {
28+
userMessage: new RegExp(SUBTASK_CHILD_PROMPT),
29+
},
30+
response: {
31+
toolCalls: [
32+
{
33+
name: "ask_followup_question",
34+
arguments: JSON.stringify({
35+
question: "What is the square root of 81?",
36+
follow_up: [{ text: SUBTASK_CHILD_FOLLOWUP_ANSWER }],
37+
}),
38+
id: "call_subtasks_child_followup_001",
39+
},
40+
],
41+
},
42+
})
43+
44+
mock.addFixture({
45+
match: {
46+
toolCallId: "call_subtasks_child_followup_001",
47+
},
48+
response: {
49+
toolCalls: [
50+
{
51+
name: "attempt_completion",
52+
arguments: JSON.stringify({ result: "9" }),
53+
id: "call_subtasks_child_completion_002",
54+
},
55+
],
56+
},
57+
})
58+
59+
mock.addFixture({
60+
match: {
61+
toolCallId: "call_subtasks_parent_new_task_001",
62+
},
63+
response: {
64+
toolCalls: [
65+
{
66+
name: "attempt_completion",
67+
arguments: JSON.stringify({ result: "Parent task resumed" }),
68+
id: "call_subtasks_parent_completion_003",
69+
},
70+
],
71+
},
72+
})
73+
}

apps/vscode-e2e/src/runTest.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { addExecuteCommandResultFixtures } from "./fixtures/execute-command"
1010
import { addListFilesResultFixtures } from "./fixtures/list-files"
1111
import { addReadFileResultFixtures } from "./fixtures/read-file"
1212
import { addSearchFilesResultFixtures } from "./fixtures/search-files"
13+
import { addSubtaskFixtures } from "./fixtures/subtasks"
1314
import { addUseMcpToolResultFixtures } from "./fixtures/use-mcp-tool"
1415
import { addWriteToFileResultFixtures } from "./fixtures/write-to-file"
1516

@@ -71,6 +72,7 @@ async function main() {
7172
addListFilesResultFixtures(mock)
7273
addReadFileResultFixtures(mock)
7374
addSearchFilesResultFixtures(mock)
75+
addSubtaskFixtures(mock)
7476
addUseMcpToolResultFixtures(mock, testWorkspace)
7577
addWriteToFileResultFixtures(mock)
7678

apps/vscode-e2e/src/suite/subtasks.test.ts

Lines changed: 80 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -2,73 +2,95 @@ import * as assert from "assert"
22

33
import { RooCodeEventName, type ClineMessage } from "@roo-code/types"
44

5+
import { setDefaultSuiteTimeout } from "./test-utils"
56
import { sleep, waitFor, waitUntilCompleted } from "./utils"
7+
import { SUBTASK_CHILD_FOLLOWUP_ANSWER, SUBTASK_CHILD_PROMPT, SUBTASK_PARENT_PROMPT } from "../fixtures/subtasks"
8+
9+
suite("Roo Code Subtasks", function () {
10+
setDefaultSuiteTimeout(this)
611

7-
suite.skip("Roo Code Subtasks", () => {
812
test("Should handle subtask cancellation and resumption correctly", async () => {
913
const api = globalThis.api
10-
14+
const asks: Record<string, ClineMessage[]> = {}
1115
const messages: Record<string, ClineMessage[]> = {}
16+
const waitForStage = async (label: string, condition: Parameters<typeof waitFor>[0]) => {
17+
try {
18+
await waitFor(condition)
19+
} catch (error) {
20+
const message = error instanceof Error ? error.message : String(error)
21+
throw new Error(`${label}: ${message}`)
22+
}
23+
}
24+
25+
const messageHandler = ({ taskId, message }: { taskId: string; message: ClineMessage }) => {
26+
if (message.type === "ask") {
27+
asks[taskId] = asks[taskId] || []
28+
asks[taskId].push(message)
29+
}
1230

13-
api.on(RooCodeEventName.Message, ({ taskId, message }) => {
1431
if (message.type === "say" && message.partial === false) {
1532
messages[taskId] = messages[taskId] || []
1633
messages[taskId].push(message)
1734
}
18-
})
19-
20-
const childPrompt = "You are a calculator. Respond only with numbers. What is the square root of 9?"
21-
22-
// Start a parent task that will create a subtask.
23-
const parentTaskId = await api.startNewTask({
24-
configuration: {
25-
mode: "ask",
26-
alwaysAllowModeSwitch: true,
27-
alwaysAllowSubtasks: true,
28-
autoApprovalEnabled: true,
29-
enableCheckpoints: false,
30-
},
31-
text:
32-
"You are the parent task. " +
33-
`Create a subtask by using the new_task tool with the message '${childPrompt}'.` +
34-
"After creating the subtask, wait for it to complete and then respond 'Parent task resumed'.",
35-
})
36-
37-
let spawnedTaskId: string | undefined = undefined
38-
39-
// Wait for the subtask to be spawned and then cancel it.
40-
api.on(RooCodeEventName.TaskSpawned, (_, childTaskId) => (spawnedTaskId = childTaskId))
41-
await waitFor(() => !!spawnedTaskId)
42-
await sleep(1_000) // Give the task a chance to start and populate the history.
43-
await api.cancelCurrentTask()
44-
45-
// Wait a bit to ensure any task resumption would have happened.
46-
await sleep(2_000)
47-
48-
// The parent task should not have resumed yet, so we shouldn't see
49-
// "Parent task resumed".
50-
assert.ok(
51-
messages[parentTaskId]?.find(({ type, text }) => type === "say" && text === "Parent task resumed") ===
52-
undefined,
53-
"Parent task should not have resumed after subtask cancellation",
54-
)
55-
56-
// Start a new task with the same message as the subtask.
57-
const anotherTaskId = await api.startNewTask({ text: childPrompt })
58-
await waitUntilCompleted({ api, taskId: anotherTaskId })
59-
60-
// Wait a bit to ensure any task resumption would have happened.
61-
await sleep(2_000)
62-
63-
// The parent task should still not have resumed.
64-
assert.ok(
65-
messages[parentTaskId]?.find(({ type, text }) => type === "say" && text === "Parent task resumed") ===
66-
undefined,
67-
"Parent task should not have resumed after subtask cancellation",
68-
)
69-
70-
// Clean up - cancel all tasks.
71-
await api.clearCurrentTask()
72-
await waitUntilCompleted({ api, taskId: parentTaskId })
35+
}
36+
37+
api.on(RooCodeEventName.Message, messageHandler)
38+
39+
try {
40+
const parentTaskId = await api.startNewTask({
41+
configuration: {
42+
mode: "ask",
43+
alwaysAllowModeSwitch: true,
44+
alwaysAllowSubtasks: true,
45+
autoApprovalEnabled: true,
46+
enableCheckpoints: false,
47+
},
48+
text: SUBTASK_PARENT_PROMPT,
49+
})
50+
51+
let spawnedTaskId: string | undefined
52+
await waitForStage("wait for spawned subtask", () => {
53+
const currentTaskId = api.getCurrentTaskStack()[0]
54+
if (currentTaskId && currentTaskId !== parentTaskId) {
55+
spawnedTaskId = currentTaskId
56+
return true
57+
}
58+
return false
59+
})
60+
await waitForStage(
61+
"wait for delegated child followup ask",
62+
() => asks[spawnedTaskId!]?.some(({ type, ask }) => type === "ask" && ask === "followup") ?? false,
63+
)
64+
65+
await api.cancelCurrentTask()
66+
67+
await sleep(2_000)
68+
69+
assert.ok(
70+
messages[parentTaskId]?.find(({ type, text }) => type === "say" && text === "Parent task resumed") ===
71+
undefined,
72+
"Parent task should not have resumed after subtask cancellation",
73+
)
74+
75+
const anotherTaskId = await api.startNewTask({ text: SUBTASK_CHILD_PROMPT })
76+
await waitForStage(
77+
"wait for standalone child followup ask",
78+
() => asks[anotherTaskId]?.some(({ type, ask }) => type === "ask" && ask === "followup") ?? false,
79+
)
80+
await api.sendMessage(SUBTASK_CHILD_FOLLOWUP_ANSWER)
81+
await waitUntilCompleted({ api, taskId: anotherTaskId })
82+
83+
await sleep(2_000)
84+
85+
assert.ok(
86+
messages[parentTaskId]?.find(({ type, text }) => type === "say" && text === "Parent task resumed") ===
87+
undefined,
88+
"Parent task should not have resumed after subtask cancellation",
89+
)
90+
91+
await api.clearCurrentTask()
92+
} finally {
93+
api.off(RooCodeEventName.Message, messageHandler)
94+
}
7395
})
7496
})

0 commit comments

Comments
 (0)