Skip to content

Commit 4b6def5

Browse files
committed
ContextProxy fix - constructor should not be async
1 parent b73bc39 commit 4b6def5

File tree

23 files changed

+531
-562
lines changed

23 files changed

+531
-562
lines changed

.changeset/seven-apricots-watch.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"roo-cline": patch
3+
---
4+
5+
ContextProxy fix - constructor should not be async

.github/workflows/code-qa.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,9 @@ jobs:
110110
cache: 'npm'
111111
- name: Install dependencies
112112
run: npm run install:all
113-
- name: Create env.integration file
113+
- name: Create .env.local file
114114
working-directory: e2e
115-
run: echo "OPENROUTER_API_KEY=${{ secrets.OPENROUTER_API_KEY }}" > .env.integration
115+
run: echo "OPENROUTER_API_KEY=${{ secrets.OPENROUTER_API_KEY }}" > .env.local
116116
- name: Run integration tests
117117
working-directory: e2e
118118
run: xvfb-run -a npm run ci

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ docs/_site/
2222

2323
# Dotenv
2424
.env
25-
.env.integration
25+
.env.*
26+
!.env.*.sample
27+
2628

2729
#Local lint config
2830
.eslintrc.local.json
File renamed without changes.

e2e/VSCODE_INTEGRATION_TESTS.md

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ The test runner (`runTest.ts`) is responsible for:
3030

3131
### Environment Setup
3232

33-
1. Create a `.env.integration` file in the root directory with required environment variables:
33+
1. Create a `.env.local` file in the root directory with required environment variables:
3434

3535
```
3636
OPENROUTER_API_KEY=sk-or-v1-...
@@ -67,7 +67,7 @@ declare global {
6767

6868
## Running Tests
6969

70-
1. Ensure you have the required environment variables set in `.env.integration`
70+
1. Ensure you have the required environment variables set in `.env.local`
7171

7272
2. Run the integration tests:
7373

@@ -117,8 +117,10 @@ const interval = 1000
117117
2. **State Management**: Reset extension state before/after tests:
118118

119119
```typescript
120-
await globalThis.provider.updateGlobalState("mode", "Ask")
121-
await globalThis.provider.updateGlobalState("alwaysAllowModeSwitch", true)
120+
await globalThis.api.setConfiguration({
121+
mode: "Ask",
122+
alwaysAllowModeSwitch: true,
123+
})
122124
```
123125

124126
3. **Assertions**: Use clear assertions with meaningful messages:
@@ -141,8 +143,12 @@ try {
141143

142144
```typescript
143145
let startTime = Date.now()
146+
144147
while (Date.now() - startTime < timeout) {
145-
if (condition) break
148+
if (condition) {
149+
break
150+
}
151+
146152
await new Promise((resolve) => setTimeout(resolve, interval))
147153
}
148154
```

e2e/package.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@
33
"version": "0.1.0",
44
"private": true,
55
"scripts": {
6-
"build": "cd .. && npm run build",
7-
"compile": "tsc -p tsconfig.json",
6+
"build": "cd .. && npm run compile && npm run build:webview",
7+
"compile": "rm -rf out && tsc -p tsconfig.json",
88
"lint": "eslint src --ext ts",
99
"check-types": "tsc --noEmit",
10-
"test": "npm run compile && npx dotenvx run -f .env.integration -- node ./out/runTest.js",
11-
"ci": "npm run build && npm run test"
10+
"test": "npm run compile && npx dotenvx run -f .env.local -- node ./out/runTest.js",
11+
"ci": "npm run build && npm run test",
12+
"clean": "rimraf out"
1213
},
1314
"dependencies": {},
1415
"devDependencies": {

e2e/src/suite/index.ts

Lines changed: 19 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,46 @@
11
import * as path from "path"
22
import Mocha from "mocha"
33
import { glob } from "glob"
4-
import { RooCodeAPI, ClineProvider } from "../../../src/exports/roo-code"
54
import * as vscode from "vscode"
65

6+
import { RooCodeAPI } from "../../../src/exports/roo-code"
7+
8+
import { waitUntilReady } from "./utils"
9+
710
declare global {
8-
var api: RooCodeAPI
9-
var provider: ClineProvider
1011
var extension: vscode.Extension<RooCodeAPI> | undefined
11-
var panel: vscode.WebviewPanel | undefined
12+
var api: RooCodeAPI
1213
}
1314

14-
export async function run(): Promise<void> {
15-
const mocha = new Mocha({
16-
ui: "tdd",
17-
timeout: 600000, // 10 minutes to compensate for time communicating with LLM while running in GHA.
18-
})
19-
15+
export async function run() {
16+
const mocha = new Mocha({ ui: "tdd", timeout: 300_000 })
2017
const testsRoot = path.resolve(__dirname, "..")
2118

2219
try {
2320
// Find all test files.
2421
const files = await glob("**/**.test.js", { cwd: testsRoot })
25-
26-
// Add files to the test suite.
2722
files.forEach((f: string) => mocha.addFile(path.resolve(testsRoot, f)))
2823

29-
// Set up global extension, api, provider, and panel.
30-
globalThis.extension = vscode.extensions.getExtension("RooVeterinaryInc.roo-cline")
24+
const extension = vscode.extensions.getExtension<RooCodeAPI>("RooVeterinaryInc.roo-cline")
3125

32-
if (!globalThis.extension) {
26+
if (!extension) {
3327
throw new Error("Extension not found")
3428
}
3529

36-
globalThis.api = globalThis.extension.isActive
37-
? globalThis.extension.exports
38-
: await globalThis.extension.activate()
39-
40-
globalThis.provider = globalThis.api.sidebarProvider
30+
const api = extension.isActive ? extension.exports : await extension.activate()
4131

42-
await globalThis.provider.updateGlobalState("apiProvider", "openrouter")
43-
await globalThis.provider.updateGlobalState("openRouterModelId", "anthropic/claude-3.5-sonnet")
44-
45-
await globalThis.provider.storeSecret(
46-
"openRouterApiKey",
47-
process.env.OPENROUTER_API_KEY || "sk-or-v1-fake-api-key",
48-
)
49-
50-
globalThis.panel = vscode.window.createWebviewPanel(
51-
"roo-cline.SidebarProvider",
52-
"Roo Code",
53-
vscode.ViewColumn.One,
54-
{
55-
enableScripts: true,
56-
enableCommandUris: true,
57-
retainContextWhenHidden: true,
58-
localResourceRoots: [globalThis.extension?.extensionUri],
59-
},
60-
)
61-
62-
await globalThis.provider.resolveWebviewView(globalThis.panel)
63-
64-
let startTime = Date.now()
65-
const timeout = 60000
66-
const interval = 1000
32+
await api.setConfiguration({
33+
apiProvider: "openrouter",
34+
openRouterApiKey: process.env.OPENROUTER_API_KEY!,
35+
openRouterModelId: "anthropic/claude-3.5-sonnet",
36+
})
6737

68-
while (Date.now() - startTime < timeout) {
69-
if (globalThis.provider.viewLaunched) {
70-
break
71-
}
38+
await waitUntilReady(api)
7239

73-
await new Promise((resolve) => setTimeout(resolve, interval))
74-
}
40+
globalThis.api = api
41+
globalThis.extension = extension
7542

76-
// Run the mocha test.
77-
return new Promise((resolve, reject) => {
43+
return new Promise<void>((resolve, reject) => {
7844
try {
7945
mocha.run((failures: number) => {
8046
if (failures > 0) {

e2e/src/suite/modes.test.ts

Lines changed: 26 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,102 +1,60 @@
11
import * as assert from "assert"
22

3+
import { waitForMessage } from "./utils"
4+
35
suite("Roo Code Modes", () => {
46
test("Should handle switching modes correctly", async function () {
5-
const timeout = 30000
6-
const interval = 1000
7+
const timeout = 300_000
8+
const api = globalThis.api
79

810
const testPrompt =
9-
"For each mode (Code, Architect, Ask) respond with the mode name and what it specializes in after switching to that mode, do not start with the current mode, be sure to say 'I AM DONE' after the task is complete"
10-
11-
if (!globalThis.extension) {
12-
assert.fail("Extension not found")
13-
}
14-
15-
let startTime = Date.now()
16-
17-
// Ensure the webview is launched.
18-
while (Date.now() - startTime < timeout) {
19-
if (globalThis.provider.viewLaunched) {
20-
break
21-
}
22-
23-
await new Promise((resolve) => setTimeout(resolve, interval))
24-
}
25-
26-
await globalThis.provider.updateGlobalState("mode", "Ask")
27-
await globalThis.provider.updateGlobalState("alwaysAllowModeSwitch", true)
28-
await globalThis.provider.updateGlobalState("autoApprovalEnabled", true)
29-
30-
// Start a new task.
31-
await globalThis.api.startNewTask(testPrompt)
11+
"For each mode (Code, Architect, Ask) respond with the mode name and what it specializes in after switching to that mode, do not start with the current mode, be sure to say 'I AM DONE' after the task is complete."
3212

33-
// Wait for task to appear in history with tokens.
34-
startTime = Date.now()
13+
await api.setConfiguration({ mode: "Code", alwaysAllowModeSwitch: true, autoApprovalEnabled: true })
14+
await api.startNewTask(testPrompt)
3515

36-
while (Date.now() - startTime < timeout) {
37-
const messages = globalThis.provider.messages
16+
await waitForMessage(api, { include: "I AM DONE", exclude: "be sure to say", timeout })
3817

39-
if (
40-
messages.some(
41-
({ type, text }) =>
42-
type === "say" && text?.includes("I AM DONE") && !text?.includes("be sure to say"),
43-
)
44-
) {
45-
break
46-
}
47-
48-
await new Promise((resolve) => setTimeout(resolve, interval))
49-
}
50-
51-
if (globalThis.provider.messages.length === 0) {
18+
if (api.getMessages().length === 0) {
5219
assert.fail("No messages received")
5320
}
5421

5522
// Log the messages to the console.
56-
globalThis.provider.messages.forEach(({ type, text }) => {
23+
api.getMessages().forEach(({ type, text }) => {
5724
if (type === "say") {
5825
console.log(text)
5926
}
6027
})
6128

6229
// Start Grading Portion of test to grade the response from 1 to 10.
63-
await globalThis.provider.updateGlobalState("mode", "Ask")
64-
let output = globalThis.provider.messages.map(({ type, text }) => (type === "say" ? text : "")).join("\n")
65-
66-
await globalThis.api.startNewTask(
67-
`Given this prompt: ${testPrompt} grade the response from 1 to 10 in the format of "Grade: (1-10)": ${output} \n Be sure to say 'I AM DONE GRADING' after the task is complete`,
68-
)
69-
70-
startTime = Date.now()
30+
await api.setConfiguration({ mode: "Ask" })
7131

72-
while (Date.now() - startTime < timeout) {
73-
const messages = globalThis.provider.messages
32+
let output = api
33+
.getMessages()
34+
.map(({ type, text }) => (type === "say" ? text : ""))
35+
.join("\n")
7436

75-
if (
76-
messages.some(
77-
({ type, text }) =>
78-
type === "say" && text?.includes("I AM DONE GRADING") && !text?.includes("be sure to say"),
79-
)
80-
) {
81-
break
82-
}
37+
await api.startNewTask(
38+
`Given this prompt: ${testPrompt} grade the response from 1 to 10 in the format of "Grade: (1-10)": ${output}\nBe sure to say 'I AM DONE GRADING' after the task is complete.`,
39+
)
8340

84-
await new Promise((resolve) => setTimeout(resolve, interval))
85-
}
41+
await waitForMessage(api, { include: "I AM DONE GRADING", exclude: "be sure to say", timeout })
8642

87-
if (globalThis.provider.messages.length === 0) {
43+
if (api.getMessages().length === 0) {
8844
assert.fail("No messages received")
8945
}
9046

91-
globalThis.provider.messages.forEach(({ type, text }) => {
47+
api.getMessages().forEach(({ type, text }) => {
9248
if (type === "say" && text?.includes("Grade:")) {
9349
console.log(text)
9450
}
9551
})
9652

97-
const gradeMessage = globalThis.provider.messages.find(
98-
({ type, text }) => type === "say" && !text?.includes("Grade: (1-10)") && text?.includes("Grade:"),
99-
)?.text
53+
const gradeMessage = api
54+
.getMessages()
55+
.find(
56+
({ type, text }) => type === "say" && !text?.includes("Grade: (1-10)") && text?.includes("Grade:"),
57+
)?.text
10058

10159
const gradeMatch = gradeMessage?.match(/Grade: (\d+)/)
10260
const gradeNum = gradeMatch ? parseInt(gradeMatch[1]) : undefined

e2e/src/suite/subtasks.test.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import * as assert from "assert"
2+
3+
import { sleep, waitForToolUse, waitForMessage } from "./utils"
4+
5+
suite("Roo Code Subtasks", () => {
6+
test.skip("Should handle subtask cancellation and resumption correctly", async function () {
7+
const api = globalThis.api
8+
9+
await api.setConfiguration({
10+
mode: "Code",
11+
alwaysAllowModeSwitch: true,
12+
alwaysAllowSubtasks: true,
13+
autoApprovalEnabled: true,
14+
})
15+
16+
// Start a parent task that will create a subtask.
17+
await api.startNewTask(
18+
"You are the parent task. " +
19+
"Create a subtask by using the new_task tool with the message 'You are the subtask'. " +
20+
"After creating the subtask, wait for it to complete and then respond with 'Parent task resumed'.",
21+
)
22+
23+
await waitForToolUse(api, "new_task")
24+
25+
// Cancel the current task (which should be the subtask).
26+
await api.cancelTask()
27+
28+
// Check if the parent task is still waiting (not resumed). We need to
29+
// wait a bit to ensure any task resumption would have happened.
30+
await sleep(5_000)
31+
32+
// The parent task should not have resumed yet, so we shouldn't see
33+
// "Parent task resumed".
34+
assert.ok(
35+
!api.getMessages().some(({ type, text }) => type === "say" && text?.includes("Parent task resumed")),
36+
"Parent task should not have resumed after subtask cancellation.",
37+
)
38+
39+
// Start a new task with the same message as the subtask.
40+
await api.startNewTask("You are the subtask")
41+
42+
// Wait for the subtask to complete.
43+
await waitForMessage(api, { include: "Task complete" })
44+
45+
// Verify that the parent task is still not resumed. We need to wait a
46+
// bit to ensure any task resumption would have happened.
47+
await sleep(5_000)
48+
49+
// The parent task should still not have resumed.
50+
assert.ok(
51+
!api.getMessages().some(({ type, text }) => type === "say" && text?.includes("Parent task resumed")),
52+
"Parent task should not have resumed after subtask completion.",
53+
)
54+
55+
// Clean up - cancel all tasks.
56+
await api.cancelTask()
57+
})
58+
})

0 commit comments

Comments
 (0)