cte 11 месяцев назад
Родитель
Сommit
85d1d4a77b

+ 1 - 0
.env.integration.example

@@ -0,0 +1 @@
+OPEN_ROUTER_API_KEY=sk-or-v1-...

+ 27 - 8
.github/workflows/code-qa.yml

@@ -1,6 +1,7 @@
 name: Code QA Roo Code
 
 on:
+  workflow_dispatch:
   push:
     branches: [main]
   pull_request:
@@ -13,33 +14,51 @@ jobs:
     steps:
       - name: Checkout code
         uses: actions/checkout@v4
-
       - name: Setup Node.js
         uses: actions/setup-node@v4
         with:
           node-version: '18'
           cache: 'npm'
-
       - name: Install dependencies
         run: npm run install:all
-
-      - name: Compile TypeScript
+      - name: Compile
         run: npm run compile
+      - name: Check types
+        run: npm run check-types
+      - name: Lint
+        run: npm run lint
 
   unit-test:
     runs-on: ubuntu-latest
     steps:
       - name: Checkout code
         uses: actions/checkout@v4
-
       - name: Setup Node.js
         uses: actions/setup-node@v4
         with:
           node-version: '18'
           cache: 'npm'
-
       - name: Install dependencies
         run: npm run install:all
-
       - name: Run unit tests
-        run: npm test
+        run: npm test
+
+  integration-test:
+    strategy:
+      matrix:
+        os: [macos-latest] # ubuntu-latest, windows-latest
+    runs-on: ${{ matrix.os }}
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v4
+      - name: Setup Node.js
+        uses: actions/setup-node@v4
+        with:
+          node-version: '18'
+          cache: 'npm'
+      - name: Install dependencies
+        run: npm run install:all
+      - run: xvfb-run -a npm test:integration
+        if: runner.os == 'Linux'
+      - run: npm test:integration
+        if: runner.os != 'Linux'

+ 5 - 1
.gitignore

@@ -1,5 +1,6 @@
-out
 dist
+out
+out-integration
 node_modules
 coverage/
 
@@ -18,3 +19,6 @@ roo-cline-*.vsix
 
 # Docs
 docs/_site/
+
+# Dotenv
+.env.integration

+ 7 - 2
.vscode-test.mjs

@@ -1,11 +1,16 @@
+/**
+ * See: https://code.visualstudio.com/api/working-with-extensions/testing-extension
+ */
+
 import { defineConfig } from '@vscode/test-cli';
 
 export default defineConfig({
-	files: 'src/test/extension.test.ts',
+	label: 'integrationTest',
+	files: 'out-integration/test/**/*.test.js',
 	workspaceFolder: '.',
 	mocha: {
+		ui: 'tdd',
 		timeout: 60000,
-		ui: 'tdd'
 	},
 	launchArgs: [
 		'--enable-proposed-api=RooVeterinaryInc.roo-cline',

+ 27 - 0
flake.lock

@@ -0,0 +1,27 @@
+{
+  "nodes": {
+    "nixpkgs": {
+      "locked": {
+        "lastModified": 1737569578,
+        "narHash": "sha256-6qY0pk2QmUtBT9Mywdvif0i/CLVgpCjMUn6g9vB+f3M=",
+        "owner": "nixos",
+        "repo": "nixpkgs",
+        "rev": "47addd76727f42d351590c905d9d1905ca895b82",
+        "type": "github"
+      },
+      "original": {
+        "owner": "nixos",
+        "ref": "nixos-24.11",
+        "repo": "nixpkgs",
+        "type": "github"
+      }
+    },
+    "root": {
+      "inputs": {
+        "nixpkgs": "nixpkgs"
+      }
+    }
+  },
+  "root": "root",
+  "version": 7
+}

+ 33 - 0
flake.nix

@@ -0,0 +1,33 @@
+{
+  description = "Roo Code development environment";
+
+  inputs = {
+    nixpkgs.url = "github:nixos/nixpkgs/nixos-24.11";
+  };
+
+  outputs = { self, nixpkgs, ... }: let
+    systems = [ "aarch64-darwin" "x86_64-linux" ];
+
+    forAllSystems = nixpkgs.lib.genAttrs systems;
+
+    mkDevShell = system: let
+      pkgs = import nixpkgs { inherit system; };
+    in pkgs.mkShell {
+      name = "roo-code";
+      
+      packages = with pkgs; [
+        zsh
+        nodejs_18
+        corepack_18
+      ];
+
+      shellHook = ''
+        exec zsh
+      '';
+    };
+  in {
+    devShells = forAllSystems (system: {
+      default = mkDevShell system;
+    });
+  };
+}

+ 175 - 1
package-lock.json

@@ -55,6 +55,7 @@
 			"devDependencies": {
 				"@changesets/cli": "^2.27.10",
 				"@changesets/types": "^6.0.0",
+				"@dotenvx/dotenvx": "^1.34.0",
 				"@types/diff": "^5.2.1",
 				"@types/diff-match-patch": "^1.0.36",
 				"@types/jest": "^29.5.14",
@@ -65,7 +66,6 @@
 				"@typescript-eslint/parser": "^7.11.0",
 				"@vscode/test-cli": "^0.0.9",
 				"@vscode/test-electron": "^2.4.0",
-				"dotenv": "^16.4.7",
 				"esbuild": "^0.24.0",
 				"eslint": "^8.57.0",
 				"husky": "^9.1.7",
@@ -3030,6 +3030,110 @@
 				"url": "https://github.com/prettier/prettier?sponsor=1"
 			}
 		},
+		"node_modules/@dotenvx/dotenvx": {
+			"version": "1.34.0",
+			"resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.34.0.tgz",
+			"integrity": "sha512-+Dp/xaI3IZ4eKv+b2vg4V89VnqLKbmJ7UZ7unnZxMu9SNLOSc2jYaXey1YHCJM+67T0pOr2Gbej3TewnuoqTWQ==",
+			"dev": true,
+			"license": "BSD-3-Clause",
+			"dependencies": {
+				"commander": "^11.1.0",
+				"dotenv": "^16.4.5",
+				"eciesjs": "^0.4.10",
+				"execa": "^5.1.1",
+				"fdir": "^6.2.0",
+				"ignore": "^5.3.0",
+				"object-treeify": "1.1.33",
+				"picomatch": "^4.0.2",
+				"which": "^4.0.0"
+			},
+			"bin": {
+				"dotenvx": "src/cli/dotenvx.js",
+				"git-dotenvx": "src/cli/dotenvx.js"
+			},
+			"funding": {
+				"url": "https://dotenvx.com"
+			}
+		},
+		"node_modules/@dotenvx/dotenvx/node_modules/commander": {
+			"version": "11.1.0",
+			"resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz",
+			"integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==",
+			"dev": true,
+			"license": "MIT",
+			"engines": {
+				"node": ">=16"
+			}
+		},
+		"node_modules/@dotenvx/dotenvx/node_modules/fdir": {
+			"version": "6.4.3",
+			"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz",
+			"integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==",
+			"dev": true,
+			"license": "MIT",
+			"peerDependencies": {
+				"picomatch": "^3 || ^4"
+			},
+			"peerDependenciesMeta": {
+				"picomatch": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/@dotenvx/dotenvx/node_modules/isexe": {
+			"version": "3.1.1",
+			"resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz",
+			"integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==",
+			"dev": true,
+			"license": "ISC",
+			"engines": {
+				"node": ">=16"
+			}
+		},
+		"node_modules/@dotenvx/dotenvx/node_modules/picomatch": {
+			"version": "4.0.2",
+			"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
+			"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
+			"dev": true,
+			"license": "MIT",
+			"engines": {
+				"node": ">=12"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/jonschlinkert"
+			}
+		},
+		"node_modules/@dotenvx/dotenvx/node_modules/which": {
+			"version": "4.0.0",
+			"resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz",
+			"integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==",
+			"dev": true,
+			"license": "ISC",
+			"dependencies": {
+				"isexe": "^3.1.1"
+			},
+			"bin": {
+				"node-which": "bin/which.js"
+			},
+			"engines": {
+				"node": "^16.13.0 || >=18.0.0"
+			}
+		},
+		"node_modules/@ecies/ciphers": {
+			"version": "0.2.2",
+			"resolved": "https://registry.npmjs.org/@ecies/ciphers/-/ciphers-0.2.2.tgz",
+			"integrity": "sha512-ylfGR7PyTd+Rm2PqQowG08BCKA22QuX8NzrL+LxAAvazN10DMwdJ2fWwAzRj05FI/M8vNFGm3cv9Wq/GFWCBLg==",
+			"dev": true,
+			"license": "MIT",
+			"engines": {
+				"bun": ">=1",
+				"deno": ">=2",
+				"node": ">=16"
+			},
+			"peerDependencies": {
+				"@noble/ciphers": "^1.0.0"
+			}
+		},
 		"node_modules/@esbuild/darwin-arm64": {
 			"version": "0.24.0",
 			"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz",
@@ -3964,6 +4068,48 @@
 				"zod": "^3.23.8"
 			}
 		},
+		"node_modules/@noble/ciphers": {
+			"version": "1.2.1",
+			"resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.2.1.tgz",
+			"integrity": "sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA==",
+			"dev": true,
+			"license": "MIT",
+			"engines": {
+				"node": "^14.21.3 || >=16"
+			},
+			"funding": {
+				"url": "https://paulmillr.com/funding/"
+			}
+		},
+		"node_modules/@noble/curves": {
+			"version": "1.8.1",
+			"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.8.1.tgz",
+			"integrity": "sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ==",
+			"dev": true,
+			"license": "MIT",
+			"dependencies": {
+				"@noble/hashes": "1.7.1"
+			},
+			"engines": {
+				"node": "^14.21.3 || >=16"
+			},
+			"funding": {
+				"url": "https://paulmillr.com/funding/"
+			}
+		},
+		"node_modules/@noble/hashes": {
+			"version": "1.7.1",
+			"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.1.tgz",
+			"integrity": "sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==",
+			"dev": true,
+			"license": "MIT",
+			"engines": {
+				"node": "^14.21.3 || >=16"
+			},
+			"funding": {
+				"url": "https://paulmillr.com/funding/"
+			}
+		},
 		"node_modules/@nodelib/fs.scandir": {
 			"version": "2.1.5",
 			"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -7772,6 +7918,24 @@
 				"safe-buffer": "^5.0.1"
 			}
 		},
+		"node_modules/eciesjs": {
+			"version": "0.4.13",
+			"resolved": "https://registry.npmjs.org/eciesjs/-/eciesjs-0.4.13.tgz",
+			"integrity": "sha512-zBdtR4K+wbj10bWPpIOF9DW+eFYQu8miU5ypunh0t4Bvt83ZPlEWgT5Dq/0G6uwEXumZKjfb5BZxYUZQ2Hzn/Q==",
+			"dev": true,
+			"license": "MIT",
+			"dependencies": {
+				"@ecies/ciphers": "^0.2.2",
+				"@noble/ciphers": "^1.0.0",
+				"@noble/curves": "^1.6.0",
+				"@noble/hashes": "^1.5.0"
+			},
+			"engines": {
+				"bun": ">=1",
+				"deno": ">=2",
+				"node": ">=16"
+			}
+		},
 		"node_modules/eight-colors": {
 			"version": "1.3.1",
 			"resolved": "https://registry.npmjs.org/eight-colors/-/eight-colors-1.3.1.tgz",
@@ -12247,6 +12411,16 @@
 				"node": ">= 0.4"
 			}
 		},
+		"node_modules/object-treeify": {
+			"version": "1.1.33",
+			"resolved": "https://registry.npmjs.org/object-treeify/-/object-treeify-1.1.33.tgz",
+			"integrity": "sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A==",
+			"dev": true,
+			"license": "MIT",
+			"engines": {
+				"node": ">= 10"
+			}
+		},
 		"node_modules/object.assign": {
 			"version": "4.1.5",
 			"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz",

+ 5 - 5
package.json

@@ -221,16 +221,16 @@
 		"build:webview": "cd webview-ui && npm run build",
 		"changeset": "changeset",
 		"check-types": "tsc --noEmit",
-		"compile": "npm run check-types && npm run lint && node esbuild.js",
-		"compile-tests": "tsc -p . --outDir out",
+		"compile": "tsc -p . --outDir out && node esbuild.js",
+		"compile:integration": "tsc -p tsconfig.integration.json",
 		"install:all": "npm install && cd webview-ui && npm install",
 		"lint": "eslint src --ext ts && npm run lint --prefix webview-ui",
 		"package": "npm run build:webview && npm run check-types && npm run lint && node esbuild.js --production",
-		"pretest": "npm run compile-tests && npm run compile && npm run lint",
+		"pretest": "npm run compile && npm run compile:integration",
 		"dev": "cd webview-ui && npm run dev",
 		"test": "jest && npm run test:webview",
 		"test:webview": "cd webview-ui && npm run test",
-		"test:extension": "vscode-test",
+		"test:extension": "npm run build && npm run compile:integration && npx dotenvx run -f .env.integration -- vscode-test",
 		"prepare": "husky",
 		"publish:marketplace": "vsce publish && ovsx publish",
 		"publish": "npm run build && changeset publish && npm install --package-lock-only",
@@ -245,6 +245,7 @@
 	"devDependencies": {
 		"@changesets/cli": "^2.27.10",
 		"@changesets/types": "^6.0.0",
+		"@dotenvx/dotenvx": "^1.34.0",
 		"@types/diff": "^5.2.1",
 		"@types/diff-match-patch": "^1.0.36",
 		"@types/jest": "^29.5.14",
@@ -255,7 +256,6 @@
 		"@typescript-eslint/parser": "^7.11.0",
 		"@vscode/test-cli": "^0.0.9",
 		"@vscode/test-electron": "^2.4.0",
-		"dotenv": "^16.4.7",
 		"esbuild": "^0.24.0",
 		"eslint": "^8.57.0",
 		"husky": "^9.1.7",

+ 10 - 12
src/core/webview/ClineProvider.ts

@@ -19,15 +19,7 @@ import { findLast } from "../../shared/array"
 import { ApiConfigMeta, ExtensionMessage } from "../../shared/ExtensionMessage"
 import { HistoryItem } from "../../shared/HistoryItem"
 import { WebviewMessage } from "../../shared/WebviewMessage"
-import {
-	Mode,
-	modes,
-	CustomModePrompts,
-	PromptComponent,
-	ModeConfig,
-	defaultModeSlug,
-	getModeBySlug,
-} from "../../shared/modes"
+import { Mode, CustomModePrompts, PromptComponent, defaultModeSlug } from "../../shared/modes"
 import { SYSTEM_PROMPT } from "../prompts/system"
 import { fileExistsAtPath } from "../../utils/fs"
 import { Cline } from "../Cline"
@@ -37,7 +29,7 @@ import { getUri } from "./getUri"
 import { playSound, setSoundEnabled, setSoundVolume } from "../../utils/sound"
 import { checkExistKey } from "../../shared/checkExistApiConfig"
 import { singleCompletionHandler } from "../../utils/single-completion-handler"
-import { getCommitInfo, searchCommits, getWorkingState } from "../../utils/git"
+import { searchCommits } from "../../utils/git"
 import { ConfigManager } from "../config/ConfigManager"
 import { CustomModesManager } from "../config/CustomModesManager"
 import { EXPERIMENT_IDS, experiments as Experiments, experimentDefault, ExperimentId } from "../../shared/experiments"
@@ -404,7 +396,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 		)
 	}
 
-	public async postMessageToWebview(message: ExtensionMessage) {
+	public async postMessageToWebview(message: ExtensionMessage | WebviewMessage) {
 		await this.view?.webview.postMessage(message)
 	}
 
@@ -2422,7 +2414,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 
 	// secrets
 
-	private async storeSecret(key: SecretKey, value?: string) {
+	public async storeSecret(key: SecretKey, value?: string) {
 		if (value) {
 			await this.context.secrets.store(key, value)
 		} else {
@@ -2476,4 +2468,10 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 		await this.postStateToWebview()
 		await this.postMessageToWebview({ type: "action", action: "chatButtonClicked" })
 	}
+
+	// integration tests
+
+	get messages() {
+		return this.cline?.clineMessages || []
+	}
 }

+ 32 - 300
src/test/extension.test.ts

@@ -1,121 +1,18 @@
-const assert = require("assert")
-const vscode = require("vscode")
-const path = require("path")
-const fs = require("fs")
-const dotenv = require("dotenv")
+import * as assert from "assert"
+import * as vscode from "vscode"
 
-// Load test environment variables
-const testEnvPath = path.join(__dirname, ".test_env")
-dotenv.config({ path: testEnvPath })
-
-suite("Roo Code Extension Test Suite", () => {
-	vscode.window.showInformationMessage("Starting Roo Code extension tests.")
-
-	test("Extension should be present", () => {
-		const extension = vscode.extensions.getExtension("RooVeterinaryInc.roo-cline")
-		assert.notStrictEqual(extension, undefined)
-	})
-
-	test("Extension should activate", async () => {
-		const extension = vscode.extensions.getExtension("RooVeterinaryInc.roo-cline")
-		if (!extension) {
-			assert.fail("Extension not found")
+suite("Roo Code Extension", () => {
+	test("OPEN_ROUTER_API_KEY environment variable is set", () => {
+		if (!process.env.OPEN_ROUTER_API_KEY) {
+			assert.fail("OPEN_ROUTER_API_KEY environment variable is not set")
 		}
-		await extension.activate()
-		assert.strictEqual(extension.isActive, true)
-	})
-
-	test("OpenRouter API key and models should be configured correctly", function (done) {
-		// @ts-ignore
-		this.timeout(60000) // Increase timeout to 60s for network requests
-		;(async () => {
-			try {
-				// Get extension instance
-				const extension = vscode.extensions.getExtension("RooVeterinaryInc.roo-cline")
-				if (!extension) {
-					done(new Error("Extension not found"))
-					return
-				}
-
-				// Verify API key is set and valid
-				const apiKey = process.env.OPEN_ROUTER_API_KEY
-				if (!apiKey) {
-					done(new Error("OPEN_ROUTER_API_KEY environment variable is not set"))
-					return
-				}
-				if (!apiKey.startsWith("sk-or-v1-")) {
-					done(new Error("OpenRouter API key should have correct format"))
-					return
-				}
-
-				// Activate extension and get provider
-				const api = await extension.activate()
-				if (!api) {
-					done(new Error("Extension API not found"))
-					return
-				}
-
-				// Get the provider from the extension's exports
-				const provider = api.sidebarProvider
-				if (!provider) {
-					done(new Error("Provider not found"))
-					return
-				}
-
-				// Set up the API configuration
-				await provider.updateGlobalState("apiProvider", "openrouter")
-				await provider.storeSecret("openRouterApiKey", apiKey)
-
-				// Set up timeout to fail test if models don't load
-				const timeout = setTimeout(() => {
-					done(new Error("Timeout waiting for models to load"))
-				}, 30000)
-
-				// Wait for models to be loaded
-				const checkModels = setInterval(async () => {
-					try {
-						const models = await provider.readOpenRouterModels()
-						if (!models) {
-							return
-						}
-
-						clearInterval(checkModels)
-						clearTimeout(timeout)
-
-						// Verify expected Claude models are available
-						const expectedModels = [
-							"anthropic/claude-3.5-sonnet:beta",
-							"anthropic/claude-3-sonnet:beta",
-							"anthropic/claude-3.5-sonnet",
-							"anthropic/claude-3.5-sonnet-20240620",
-							"anthropic/claude-3.5-sonnet-20240620:beta",
-							"anthropic/claude-3.5-haiku:beta",
-						]
-
-						for (const modelId of expectedModels) {
-							assert.strictEqual(modelId in models, true, `Model ${modelId} should be available`)
-						}
-
-						done()
-					} catch (error) {
-						clearInterval(checkModels)
-						clearTimeout(timeout)
-						done(error)
-					}
-				}, 1000)
-
-				// Trigger model loading
-				await provider.refreshOpenRouterModels()
-			} catch (error) {
-				done(error)
-			}
-		})()
 	})
 
 	test("Commands should be registered", async () => {
-		const commands = await vscode.commands.getCommands(true)
+		const timeout = 10 * 1_000
+		const interval = 1_000
+		const startTime = Date.now()
 
-		// Test core commands are registered
 		const expectedCommands = [
 			"roo-cline.plusButtonClicked",
 			"roo-cline.mcpButtonClicked",
@@ -128,204 +25,39 @@ suite("Roo Code Extension Test Suite", () => {
 			"roo-cline.improveCode",
 		]
 
+		while (Date.now() - startTime < timeout) {
+			const commands = await vscode.commands.getCommands(true)
+			const missingCommands = []
+
+			for (const cmd of expectedCommands) {
+				if (!commands.includes(cmd)) {
+					missingCommands.push(cmd)
+				}
+			}
+
+			if (missingCommands.length === 0) {
+				break
+			}
+
+			await new Promise((resolve) => setTimeout(resolve, interval))
+		}
+
+		const commands = await vscode.commands.getCommands(true)
+
 		for (const cmd of expectedCommands) {
-			assert.strictEqual(commands.includes(cmd), true, `Command ${cmd} should be registered`)
+			assert.ok(commands.includes(cmd), `Command ${cmd} should be registered`)
 		}
 	})
 
-	test("Views should be registered", () => {
+	test("Webview panel can be created", () => {
 		const view = vscode.window.createWebviewPanel(
 			"roo-cline.SidebarProvider",
 			"Roo Code",
 			vscode.ViewColumn.One,
 			{},
 		)
-		assert.notStrictEqual(view, undefined)
-		view.dispose()
-	})
-
-	test("Should handle prompt and response correctly", async function () {
-		// @ts-ignore
-		this.timeout(60000) // Increase timeout for API request
-
-		const timeout = 30000
-		const interval = 1000
-
-		// Get extension instance
-		const extension = vscode.extensions.getExtension("RooVeterinaryInc.roo-cline")
-		if (!extension) {
-			assert.fail("Extension not found")
-			return
-		}
 
-		// Activate extension and get API
-		const api = await extension.activate()
-		if (!api) {
-			assert.fail("Extension API not found")
-			return
-		}
-
-		// Get provider
-		const provider = api.sidebarProvider
-		if (!provider) {
-			assert.fail("Provider not found")
-			return
-		}
-
-		// Set up API configuration
-		await provider.updateGlobalState("apiProvider", "openrouter")
-		await provider.updateGlobalState("openRouterModelId", "anthropic/claude-3.5-sonnet")
-		const apiKey = process.env.OPEN_ROUTER_API_KEY
-		if (!apiKey) {
-			assert.fail("OPEN_ROUTER_API_KEY environment variable is not set")
-			return
-		}
-		await provider.storeSecret("openRouterApiKey", apiKey)
-
-		// Create webview panel with development options
-		const extensionUri = extension.extensionUri
-		const panel = vscode.window.createWebviewPanel("roo-cline.SidebarProvider", "Roo Code", vscode.ViewColumn.One, {
-			enableScripts: true,
-			enableCommandUris: true,
-			retainContextWhenHidden: true,
-			localResourceRoots: [extensionUri],
-		})
-
-		try {
-			// Initialize webview with development context
-			panel.webview.options = {
-				enableScripts: true,
-				enableCommandUris: true,
-				localResourceRoots: [extensionUri],
-			}
-
-			// Initialize provider with panel
-			provider.resolveWebviewView(panel)
-
-			// Set up message tracking
-			let webviewReady = false
-			let messagesReceived = false
-			const originalPostMessage = provider.postMessageToWebview.bind(provider)
-			// @ts-ignore
-			provider.postMessageToWebview = async (message) => {
-				if (message.type === "state") {
-					webviewReady = true
-					console.log("Webview state received:", message)
-					if (message.state?.clineMessages?.length > 0) {
-						messagesReceived = true
-						console.log("Messages in state:", message.state.clineMessages)
-					}
-				}
-				await originalPostMessage(message)
-			}
-
-			// Wait for webview to launch and receive initial state
-			let startTime = Date.now()
-			while (Date.now() - startTime < timeout) {
-				if (webviewReady) {
-					// Wait an additional second for webview to fully initialize
-					await new Promise((resolve) => setTimeout(resolve, 1000))
-					break
-				}
-				await new Promise((resolve) => setTimeout(resolve, interval))
-			}
-
-			if (!webviewReady) {
-				throw new Error("Timeout waiting for webview to be ready")
-			}
-
-			// Send webviewDidLaunch to initialize chat
-			await provider.postMessageToWebview({ type: "webviewDidLaunch" })
-			console.log("Sent webviewDidLaunch")
-
-			// Wait for webview to fully initialize
-			await new Promise((resolve) => setTimeout(resolve, 2000))
-
-			// Restore original postMessage
-			provider.postMessageToWebview = originalPostMessage
-
-			// Wait for OpenRouter models to be fully loaded
-			startTime = Date.now()
-			while (Date.now() - startTime < timeout) {
-				const models = await provider.readOpenRouterModels()
-				if (models && Object.keys(models).length > 0) {
-					console.log("OpenRouter models loaded")
-					break
-				}
-				await new Promise((resolve) => setTimeout(resolve, interval))
-			}
-
-			// Send prompt
-			const prompt = "Hello world, what is your name?"
-			console.log("Sending prompt:", prompt)
-
-			// Start task
-			try {
-				await api.startNewTask(prompt)
-				console.log("Task started")
-			} catch (error) {
-				console.error("Error starting task:", error)
-				throw error
-			}
-
-			// Wait for task to appear in history with tokens
-			startTime = Date.now()
-			while (Date.now() - startTime < timeout) {
-				const state = await provider.getState()
-				const task = state.taskHistory?.[0]
-				if (task && task.tokensOut > 0) {
-					console.log("Task completed with tokens:", task)
-					break
-				}
-				await new Promise((resolve) => setTimeout(resolve, interval))
-			}
-
-			// Wait for messages to be processed
-			startTime = Date.now()
-			let responseReceived = false
-			while (Date.now() - startTime < timeout) {
-				// Check provider.clineMessages
-				const messages = provider.clineMessages
-				if (messages && messages.length > 0) {
-					console.log("Provider messages:", JSON.stringify(messages, null, 2))
-					// @ts-ignore
-					const hasResponse = messages.some(
-						(m: { type: string; text: string }) =>
-							m.type === "say" && m.text && m.text.toLowerCase().includes("cline"),
-					)
-					if (hasResponse) {
-						console.log('Found response containing "Cline" in provider messages')
-						responseReceived = true
-						break
-					}
-				}
-
-				// Check provider.cline.clineMessages
-				const clineMessages = provider.cline?.clineMessages
-				if (clineMessages && clineMessages.length > 0) {
-					console.log("Cline messages:", JSON.stringify(clineMessages, null, 2))
-					// @ts-ignore
-					const hasResponse = clineMessages.some(
-						(m: { type: string; text: string }) =>
-							m.type === "say" && m.text && m.text.toLowerCase().includes("cline"),
-					)
-					if (hasResponse) {
-						console.log('Found response containing "Cline" in cline messages')
-						responseReceived = true
-						break
-					}
-				}
-
-				await new Promise((resolve) => setTimeout(resolve, interval))
-			}
-
-			if (!responseReceived) {
-				console.log("Final provider state:", await provider.getState())
-				console.log("Final cline messages:", provider.cline?.clineMessages)
-				throw new Error('Did not receive expected response containing "Cline"')
-			}
-		} finally {
-			panel.dispose()
-		}
+		assert.ok(view, "Failed to create webview panel")
+		view.dispose()
 	})
 })

+ 152 - 0
src/test/task.test.ts

@@ -0,0 +1,152 @@
+import * as assert from "assert"
+import * as vscode from "vscode"
+
+import { ClineAPI } from "../exports/cline"
+import { ClineProvider } from "../core/webview/ClineProvider"
+
+suite("Roo Code Task", () => {
+	test("Should handle prompt and response correctly", async function () {
+		const timeout = 30000
+		const interval = 1000
+
+		const extension = vscode.extensions.getExtension("RooVeterinaryInc.roo-cline")
+
+		if (!extension) {
+			assert.fail("Extension not found")
+		}
+
+		const api: ClineAPI = await extension.activate()
+
+		if (!api) {
+			assert.fail("Extension API not found")
+		}
+
+		const provider = api.sidebarProvider as ClineProvider
+
+		if (!provider) {
+			assert.fail("Provider not found")
+		}
+
+		await provider.updateGlobalState("apiProvider", "openrouter")
+		await provider.updateGlobalState("openRouterModelId", "anthropic/claude-3.5-sonnet")
+		const apiKey = process.env.OPEN_ROUTER_API_KEY
+
+		if (!apiKey) {
+			assert.fail("OPEN_ROUTER_API_KEY environment variable is not set")
+		}
+
+		await provider.storeSecret("openRouterApiKey", apiKey)
+
+		// Create webview panel with development options.
+		const extensionUri = extension.extensionUri
+
+		const panel = vscode.window.createWebviewPanel("roo-cline.SidebarProvider", "Roo Code", vscode.ViewColumn.One, {
+			enableScripts: true,
+			enableCommandUris: true,
+			retainContextWhenHidden: true,
+			localResourceRoots: [extensionUri],
+		})
+
+		try {
+			// Initialize webview with development context.
+			panel.webview.options = {
+				enableScripts: true,
+				enableCommandUris: true,
+				localResourceRoots: [extensionUri],
+			}
+
+			// Initialize provider with panel.
+			provider.resolveWebviewView(panel)
+
+			// Set up message tracking.
+			let webviewReady = false
+			const originalPostMessage = provider.postMessageToWebview.bind(provider)
+
+			provider.postMessageToWebview = async (message: any) => {
+				if (message.type === "state") {
+					webviewReady = true
+				}
+
+				await originalPostMessage(message)
+			}
+
+			// Wait for webview to launch and receive initial state.
+			let startTime = Date.now()
+
+			while (Date.now() - startTime < timeout) {
+				if (webviewReady) {
+					// Wait an additional second for webview to fully initialize.
+					await new Promise((resolve) => setTimeout(resolve, 1000))
+					break
+				}
+
+				await new Promise((resolve) => setTimeout(resolve, interval))
+			}
+
+			if (!webviewReady) {
+				assert.fail("Webview never became ready")
+			}
+
+			// Send webviewDidLaunch to initialize chat.
+			await provider.postMessageToWebview({ type: "webviewDidLaunch" })
+
+			// Wait for webview to fully initialize.
+			await new Promise((resolve) => setTimeout(resolve, 2000))
+
+			// Restore original postMessage.
+			provider.postMessageToWebview = originalPostMessage
+
+			// Wait for OpenRouter models to be fully loaded.
+			startTime = Date.now()
+
+			while (Date.now() - startTime < timeout) {
+				const models = await provider.readOpenRouterModels()
+
+				if (models && Object.keys(models).length > 0) {
+					break
+				}
+
+				await new Promise((resolve) => setTimeout(resolve, interval))
+			}
+
+			// Send prompt.
+			const prompt = "Hello world, what is your name? Respond with 'My name is ...'"
+
+			// Start task.
+			try {
+				await api.startNewTask(prompt)
+			} catch (error) {
+				console.error(error)
+				assert.fail("Error starting task")
+			}
+
+			// Wait for task to appear in history with tokens.
+			startTime = Date.now()
+
+			while (Date.now() - startTime < timeout) {
+				const state = await provider.getState()
+				const task = state.taskHistory?.[0]
+
+				if (task && task.tokensOut > 0) {
+					// console.log("Task completed with tokens:", task)
+					break
+				}
+
+				await new Promise((resolve) => setTimeout(resolve, interval))
+			}
+
+			if (provider.messages.length === 0) {
+				assert.fail("No messages received")
+			}
+
+			// console.log("Provider messages:", JSON.stringify(provider.messages, null, 2))
+
+			assert.ok(
+				provider.messages.some(({ type, text }) => type === "say" && text?.includes("My name is Roo")),
+				"Did not receive expected response containing 'My name is Roo'",
+			)
+		} finally {
+			panel.dispose()
+		}
+	})
+})

+ 0 - 19
src/test/tsconfig.json

@@ -1,19 +0,0 @@
-{
-	"compilerOptions": {
-		"module": "commonjs",
-		"target": "ES2020",
-		"lib": ["ES2020"],
-		"sourceMap": true,
-		"rootDir": "../..",
-		"strict": false,
-		"noImplicitAny": false,
-		"noImplicitThis": false,
-		"alwaysStrict": false,
-		"skipLibCheck": true,
-		"baseUrl": "../..",
-		"paths": {
-			"*": ["*", "src/*"]
-		}
-	},
-	"exclude": ["node_modules", ".vscode-test"]
-}

+ 17 - 0
tsconfig.integration.json

@@ -0,0 +1,17 @@
+{
+	"compilerOptions": {
+		"module": "CommonJS",
+		"moduleResolution": "Node",
+		"esModuleInterop": true,
+		"target": "ES2022",
+		"lib": ["ES2022", "ESNext.Disposable", "DOM"],
+		"sourceMap": true,
+		"strict": true,
+		"skipLibCheck": true,
+		"useUnknownInCatchVariables": false,
+		"rootDir": "src",
+		"outDir": "out-integration"
+	},
+	"include": ["**/*.ts"],
+	"exclude": [".vscode-test", "benchmark", "dist", "**/node_modules/**", "out", "out-integration", "webview-ui"]
+}