From 9f2d070bc58deb8a2af74a0212e20dbf311f37bb Mon Sep 17 00:00:00 2001 From: Elliott de Launay Date: Sun, 26 Apr 2026 20:44:44 -0400 Subject: [PATCH 1/6] feat(ci): adding coverage reporting and config to ci --- .github/workflows/code-qa.yml | 18 ++++++++++++++++-- apps/cli/package.json | 2 ++ apps/cli/vitest.config.ts | 5 +++++ codecov.yml | 16 ++++++++++++++++ package.json | 2 +- packages/cloud/package.json | 2 ++ packages/cloud/vitest.config.ts | 12 ++++++++++++ packages/core/package.json | 2 ++ packages/telemetry/package.json | 2 ++ packages/telemetry/vitest.config.ts | 5 +++++ src/package.json | 2 ++ src/vitest.config.ts | 13 +++++++++++++ turbo.json | 4 ++++ webview-ui/package.json | 2 ++ webview-ui/vitest.config.ts | 13 +++++++++++++ 15 files changed, 97 insertions(+), 3 deletions(-) create mode 100644 codecov.yml diff --git a/.github/workflows/code-qa.yml b/.github/workflows/code-qa.yml index 1592b15669b..b2671bccbbc 100644 --- a/.github/workflows/code-qa.yml +++ b/.github/workflows/code-qa.yml @@ -49,12 +49,26 @@ jobs: include: - os: ubuntu-latest name: ubuntu-latest + codecov-flag: ubuntu - os: windows-latest name: windows-latest + codecov-flag: windows steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup Node.js and pnpm uses: ./.github/actions/setup-node-pnpm - - name: Run unit tests - run: pnpm test + - name: Run unit tests with coverage + run: pnpm test:coverage + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + with: + files: >- + src/coverage/lcov.info, + webview-ui/coverage/lcov.info, + packages/core/coverage/lcov.info, + packages/cloud/coverage/lcov.info, + packages/telemetry/coverage/lcov.info, + apps/cli/coverage/lcov.info + flags: ${{ matrix.codecov-flag }} + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/apps/cli/package.json b/apps/cli/package.json index 9276f170534..d5e94f7794d 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -13,6 +13,7 @@ "lint": "eslint src --ext .ts --max-warnings=0", "check-types": "tsc --noEmit", "test": "vitest run", + "test:coverage": "vitest run --coverage", "test:integration": "tsx scripts/integration/run.ts", "build": "tsup", "build:extension": "pnpm --filter roo-cline bundle", @@ -45,6 +46,7 @@ "ink-testing-library": "^4.0.0", "rimraf": "^6.0.1", "tsup": "^8.4.0", + "@vitest/coverage-v8": "^3.2.3", "vitest": "^3.2.3" } } diff --git a/apps/cli/vitest.config.ts b/apps/cli/vitest.config.ts index 5b6e725d8c6..122f370bcc6 100644 --- a/apps/cli/vitest.config.ts +++ b/apps/cli/vitest.config.ts @@ -13,5 +13,10 @@ export default defineConfig({ watch: false, testTimeout: 120_000, // 2m for integration tests. include: ["src/**/*.test.ts", "src/**/*.test.tsx"], + coverage: { + provider: "v8", + reporter: ["text", "lcov"], + exclude: ["**/*.test.ts", "**/*.test.tsx", "**/*.spec.ts", "**/*.spec.tsx", "**/vitest.config.ts"], + }, }, }) diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 00000000000..f2149e39ec4 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,16 @@ +coverage: + precision: 2 + round: down + status: + project: + default: + target: auto # never regress below current baseline + threshold: 1% + patch: + default: + target: 80% # new lines must be 80% covered + threshold: 5% + +comment: + layout: "diff, flags" + behavior: default diff --git a/package.json b/package.json index de8dff751cb..8237bf7e005 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "lint": "turbo lint --log-order grouped --output-logs new-only", "check-types": "turbo check-types --log-order grouped --output-logs new-only", "test": "turbo test --log-order grouped --output-logs new-only", + "test:coverage": "turbo test:coverage --log-order grouped --output-logs new-only", "format": "turbo format --log-order grouped --output-logs new-only", "build": "turbo build --log-order grouped --output-logs new-only", "bundle": "turbo bundle --log-order grouped --output-logs new-only", @@ -62,7 +63,6 @@ "tar-fs": ">=3.1.1", "esbuild": ">=0.25.0", "undici": ">=5.29.0", - "brace-expansion": "^2.0.2", "form-data": ">=4.0.4", "bluebird": ">=3.7.2", "glob": ">=11.1.0", diff --git a/packages/cloud/package.json b/packages/cloud/package.json index 92ecaa17647..4055e3b8ed2 100644 --- a/packages/cloud/package.json +++ b/packages/cloud/package.json @@ -8,6 +8,7 @@ "lint": "eslint src --ext=ts --max-warnings=0", "check-types": "tsc --noEmit", "test": "vitest run", + "test:coverage": "vitest run --coverage", "clean": "rimraf .turbo" }, "dependencies": { @@ -23,6 +24,7 @@ "@types/node": "^24.1.0", "@types/vscode": "^1.102.0", "globals": "^16.3.0", + "@vitest/coverage-v8": "^3.2.4", "vitest": "^3.2.4" } } diff --git a/packages/cloud/vitest.config.ts b/packages/cloud/vitest.config.ts index 569f1675437..e70def3df58 100644 --- a/packages/cloud/vitest.config.ts +++ b/packages/cloud/vitest.config.ts @@ -5,6 +5,18 @@ export default defineConfig({ globals: true, environment: "node", watch: false, + coverage: { + provider: "v8", + reporter: ["text", "lcov"], + exclude: [ + "**/*.test.ts", + "**/*.test.tsx", + "**/*.spec.ts", + "**/*.spec.tsx", + "**/vitest.config.ts", + "**/__mocks__/**", + ], + }, }, resolve: { alias: { diff --git a/packages/core/package.json b/packages/core/package.json index 25e6224e8ca..f98a50b8fb4 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -12,6 +12,7 @@ "lint": "eslint src --ext=ts --max-warnings=0", "check-types": "tsc --noEmit", "test": "vitest run", + "test:coverage": "vitest run --coverage", "clean": "rimraf .turbo" }, "dependencies": { @@ -26,6 +27,7 @@ "@roo-code/config-eslint": "workspace:^", "@roo-code/config-typescript": "workspace:^", "@types/node": "^24.1.0", + "@vitest/coverage-v8": "^3.2.3", "vitest": "^3.2.3" } } diff --git a/packages/telemetry/package.json b/packages/telemetry/package.json index 1d434ad290a..cf97b72ded3 100644 --- a/packages/telemetry/package.json +++ b/packages/telemetry/package.json @@ -8,6 +8,7 @@ "lint": "eslint src --ext=ts --max-warnings=0", "check-types": "tsc --noEmit", "test": "vitest run", + "test:coverage": "vitest run --coverage", "clean": "rimraf .turbo" }, "dependencies": { @@ -20,6 +21,7 @@ "@roo-code/config-typescript": "workspace:^", "@types/node": "20.x", "@types/vscode": "^1.84.0", + "@vitest/coverage-v8": "^3.2.3", "vitest": "^3.2.3" } } diff --git a/packages/telemetry/vitest.config.ts b/packages/telemetry/vitest.config.ts index b6d6dbb880f..fd7c24b19cb 100644 --- a/packages/telemetry/vitest.config.ts +++ b/packages/telemetry/vitest.config.ts @@ -5,5 +5,10 @@ export default defineConfig({ globals: true, environment: "node", watch: false, + coverage: { + provider: "v8", + reporter: ["text", "lcov"], + exclude: ["**/*.test.ts", "**/*.test.tsx", "**/*.spec.ts", "**/*.spec.tsx", "**/vitest.config.ts"], + }, }, }) diff --git a/src/package.json b/src/package.json index 7c1ed56cd86..4f1c58f10e6 100644 --- a/src/package.json +++ b/src/package.json @@ -438,6 +438,7 @@ "check-types": "tsc --noEmit", "pretest": "turbo run bundle --cwd ..", "test": "vitest run", + "test:coverage": "vitest run --coverage", "format": "prettier --write .", "bundle": "node esbuild.mjs", "vscode:prepublish": "pnpm bundle --production", @@ -577,6 +578,7 @@ "rimraf": "^6.0.1", "tsup": "^8.4.0", "tsx": "^4.19.3", + "@vitest/coverage-v8": "^3.2.3", "vitest": "^3.2.3", "zod-to-ts": "^1.2.0" } diff --git a/src/vitest.config.ts b/src/vitest.config.ts index 3a6854d5328..6429e14e8f7 100644 --- a/src/vitest.config.ts +++ b/src/vitest.config.ts @@ -14,6 +14,19 @@ export default defineConfig({ testTimeout: 20_000, hookTimeout: 20_000, onConsoleLog, + coverage: { + provider: "v8", + reporter: ["text", "lcov"], + exclude: [ + "**/*.test.ts", + "**/*.test.tsx", + "**/*.spec.ts", + "**/*.spec.tsx", + "**/vitest.setup.ts", + "**/vitest.config.ts", + "**/__mocks__/**", + ], + }, }, resolve: { alias: { diff --git a/turbo.json b/turbo.json index 70794554648..2d8f8a2f98e 100644 --- a/turbo.json +++ b/turbo.json @@ -6,6 +6,10 @@ "test": { "dependsOn": ["@roo-code/types#build"] }, + "test:coverage": { + "dependsOn": ["@roo-code/types#build"], + "outputs": ["coverage/**"] + }, "format": {}, "clean": { "cache": false diff --git a/webview-ui/package.json b/webview-ui/package.json index 6da253ea333..87a699125ce 100644 --- a/webview-ui/package.json +++ b/webview-ui/package.json @@ -7,6 +7,7 @@ "check-types": "tsc", "pretest": "turbo run bundle --cwd ..", "test": "vitest run", + "test:coverage": "vitest run --coverage", "format": "prettier --write src", "dev": "vite", "build": "tsc -b && vite build", @@ -100,6 +101,7 @@ "@types/stacktrace-js": "^2.0.3", "@types/vscode-webview": "^1.57.5", "@vitejs/plugin-react": "^4.3.4", + "@vitest/coverage-v8": "^3.2.3", "@vitest/ui": "^3.2.3", "babel-plugin-react-compiler": "^1.0.0", "identity-obj-proxy": "^3.0.0", diff --git a/webview-ui/vitest.config.ts b/webview-ui/vitest.config.ts index 595c51348d5..0135048d4ea 100644 --- a/webview-ui/vitest.config.ts +++ b/webview-ui/vitest.config.ts @@ -14,6 +14,19 @@ export default defineConfig({ environment: "jsdom", include: ["src/**/*.spec.ts", "src/**/*.spec.tsx"], onConsoleLog, + coverage: { + provider: "v8", + reporter: ["text", "lcov"], + exclude: [ + "**/*.test.ts", + "**/*.test.tsx", + "**/*.spec.ts", + "**/*.spec.tsx", + "**/vitest.setup.ts", + "**/vitest.config.ts", + "**/__mocks__/**", + ], + }, }, resolve: { alias: { From 9c6c5b482c445322fae53ec3e35f67edcbb51049 Mon Sep 17 00:00:00 2001 From: Elliott de Launay Date: Sun, 26 Apr 2026 20:45:23 -0400 Subject: [PATCH 2/6] feat(ci): enabling e2e tests, and mocking in ci to make sure nothing breaks --- .github/workflows/e2e.yml | 26 ++ apps/vscode-e2e/AGENTS.md | 91 +++++++ apps/vscode-e2e/fixtures/.gitignore | 3 + apps/vscode-e2e/fixtures/.gitkeep | 0 apps/vscode-e2e/fixtures/markdown-lists.json | 60 +++++ apps/vscode-e2e/fixtures/modes.json | 18 ++ .../vscode-e2e/fixtures/task-hello-world.json | 18 ++ apps/vscode-e2e/package.json | 3 + apps/vscode-e2e/src/runTest.ts | 76 +++++- apps/vscode-e2e/src/suite/index.ts | 18 +- packages/core/vitest.config.ts | 5 + packages/types/src/api.ts | 6 + pnpm-lock.yaml | 224 +++++++++++++++++- src/extension/api.ts | 4 + 14 files changed, 535 insertions(+), 17 deletions(-) create mode 100644 .github/workflows/e2e.yml create mode 100644 apps/vscode-e2e/AGENTS.md create mode 100644 apps/vscode-e2e/fixtures/.gitignore create mode 100644 apps/vscode-e2e/fixtures/.gitkeep create mode 100644 apps/vscode-e2e/fixtures/markdown-lists.json create mode 100644 apps/vscode-e2e/fixtures/modes.json create mode 100644 apps/vscode-e2e/fixtures/task-hello-world.json diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml new file mode 100644 index 00000000000..4c8192ee664 --- /dev/null +++ b/.github/workflows/e2e.yml @@ -0,0 +1,26 @@ +name: E2E Tests (Mocked) + +on: + workflow_dispatch: + pull_request: + types: [opened, reopened, ready_for_review, synchronize] + branches: [main] + paths: + - "src/**" + - "webview-ui/**" + - "apps/vscode-e2e/**" + - "packages/core/**" + +jobs: + e2e-mock: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Setup Node.js and pnpm + uses: ./.github/actions/setup-node-pnpm + - name: Install xvfb + run: sudo apt-get install -y xvfb + - name: Run mocked E2E tests + run: xvfb-run -a pnpm --filter @roo-code/vscode-e2e test:ci:mock diff --git a/apps/vscode-e2e/AGENTS.md b/apps/vscode-e2e/AGENTS.md new file mode 100644 index 00000000000..847a010931a --- /dev/null +++ b/apps/vscode-e2e/AGENTS.md @@ -0,0 +1,91 @@ +# E2E Test Fixture Workflow + +E2E tests run against `@copilotkit/aimock` (`LLMock`) — a local HTTP server that replays recorded LLM responses. This makes tests free, deterministic, and CI-friendly. + +## How aimock matching works + +Fixtures are matched by **substring**: `incoming_last_user_message.includes(fixture.match.userMessage)`. A fixture fires if its match string appears _anywhere_ in the last user message of the API request. + +**Critical**: the last user message always contains `` with the current time. Never use a match string that includes a timestamp — it will stop matching on the next run. + +Record mode uses **record-on-miss**: if an existing fixture already matches a request, aimock serves it and does **not** re-record. Only unmatched requests are proxied to the real API and saved as `openai-*.json` files. + +## Adding a fixture for a new test + +1. Write the test in `src/suite/`. Use short, stable, unique text in the task prompt. + +2. Clear any stale auto-recorded files first (they accumulate across record runs): + + ```sh + git clean -fx apps/vscode-e2e/fixtures/ + ``` + + The `-x` flag is required because `openai-*.json` files are gitignored — `git clean -f` alone silently skips them. + +3. Record fixtures (requires an OpenRouter API key with credits): + + ```sh + OPENROUTER_API_KEY= pnpm --filter @roo-code/vscode-e2e test:record + ``` + + This proxies unmatched requests to OpenRouter and writes `fixtures/openai-*.json`. Background + calls from the extension will also be recorded here — that's expected, ignore them. + +4. Find the auto-recorded file for your test: + + ```sh + grep -l "your unique prompt text" fixtures/openai-*.json + ``` + +5. Inspect it to find the `response` block (tool calls the LLM made). + +6. Create a named fixture file, e.g. `fixtures/my-feature.json`, with a **short stable match string**: + + ```json + { + "fixtures": [ + { + "match": { "userMessage": "your unique prompt text" }, + "response": { + "toolCalls": [ + { "name": "attempt_completion", "arguments": "{\"result\":\"...\"}", "id": "call_001" } + ] + } + } + ] + } + ``` + + The match string should be unique enough to identify this request but contain **no timestamps, file paths, or environment details**. + +7. Delete the `openai-*.json` files — they're gitignored and can't be replayed. + +8. Verify in mock mode (no API key needed): + ```sh + pnpm --filter @roo-code/vscode-e2e test:ci:mock + ``` + +## Multi-turn tests + +If the LLM calls a tool first (e.g. `read_file`) and then calls `attempt_completion` after seeing the result, you need two fixtures: + +- **Turn 1**: match on the task prompt → respond with the tool call +- **Turn 2**: match on a stable part of the tool _result_ → respond with `attempt_completion` + +The tool result is provided by the extension (not the mock), so its content is deterministic if test files have stable names. Use a stable substring from the tool result as the turn-2 match string. + +## 404 errors in logs are expected + +Background API calls from the extension (usage collection, initialization) hit aimock with no matching fixture and return 404. These do **not** affect test results — the tests still pass. You'll see `[OpenRouter] API error: { message: '404 No fixture matched' }` in the output; this is normal. + +## Running tests + +| Command | Purpose | +| ------------------------------------------------------------------------- | ------------------------------------------------------------------ | +| `pnpm --filter @roo-code/vscode-e2e test:ci:mock` | Replay mode — no API key needed, uses fixtures | +| `OPENROUTER_API_KEY= pnpm --filter @roo-code/vscode-e2e test:record` | Record mode — proxies to real API, writes `openai-*.json` | +| `OPENROUTER_API_KEY= pnpm --filter @roo-code/vscode-e2e test:ci` | Real-API mode — runs against live OpenRouter (for drift detection) | + +## Programmatic fixtures (regex matching) + +For requests that can't be matched by a stable substring (e.g. "starts with `` but not preceded by a user message"), add a programmatic fixture in `src/runTest.ts` using `mock.addFixture()` with a `RegExp` match. These are only available in replay mode and are not recorded. diff --git a/apps/vscode-e2e/fixtures/.gitignore b/apps/vscode-e2e/fixtures/.gitignore new file mode 100644 index 00000000000..8558c4cf5fd --- /dev/null +++ b/apps/vscode-e2e/fixtures/.gitignore @@ -0,0 +1,3 @@ +# Auto-recorded fixtures have timestamp-based match strings and never replay correctly. +# Contributors should extract stable fixtures manually from these files, then delete them. +openai-*.json diff --git a/apps/vscode-e2e/fixtures/.gitkeep b/apps/vscode-e2e/fixtures/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/apps/vscode-e2e/fixtures/markdown-lists.json b/apps/vscode-e2e/fixtures/markdown-lists.json new file mode 100644 index 00000000000..2f4302ff286 --- /dev/null +++ b/apps/vscode-e2e/fixtures/markdown-lists.json @@ -0,0 +1,60 @@ +{ + "fixtures": [ + { + "match": { + "userMessage": "Please show me an example of an unordered list with the following items: Apple, Banana, Orange" + }, + "response": { + "toolCalls": [ + { + "name": "attempt_completion", + "arguments": "{\"result\":\"Here is an unordered list:\\n- Apple\\n- Banana\\n- Orange\"}", + "id": "call_markdown_unordered_001" + } + ] + } + }, + { + "match": { + "userMessage": "Please show me a numbered list with three steps: First step, Second step, Third step" + }, + "response": { + "toolCalls": [ + { + "name": "attempt_completion", + "arguments": "{\"result\":\"Here is a numbered list:\\n1. First step\\n2. Second step\\n3. Third step\"}", + "id": "call_markdown_ordered_001" + } + ] + } + }, + { + "match": { + "userMessage": "Please create a nested list with 'Main item' having two sub-items: 'Sub-item A' and 'Sub-item B'" + }, + "response": { + "toolCalls": [ + { + "name": "attempt_completion", + "arguments": "{\"result\":\"Here is a nested list:\\n- Main item\\n - Sub-item A\\n - Sub-item B\"}", + "id": "call_markdown_nested_001" + } + ] + } + }, + { + "match": { + "userMessage": "Please create a list that has both numbered items and bullet points, mixing ordered and unordered lists" + }, + "response": { + "toolCalls": [ + { + "name": "attempt_completion", + "arguments": "{\"result\":\"Here is a mixed list:\\n1. First numbered item\\n- Bullet point A\\n- Bullet point B\\n2. Second numbered item\"}", + "id": "call_markdown_mixed_001" + } + ] + } + } + ] +} diff --git a/apps/vscode-e2e/fixtures/modes.json b/apps/vscode-e2e/fixtures/modes.json new file mode 100644 index 00000000000..39f4c62f355 --- /dev/null +++ b/apps/vscode-e2e/fixtures/modes.json @@ -0,0 +1,18 @@ +{ + "fixtures": [ + { + "match": { + "userMessage": "Use the `switch_mode` tool to switch to ask mode." + }, + "response": { + "toolCalls": [ + { + "name": "switch_mode", + "arguments": "{\"mode_slug\":\"ask\",\"reason\":\"User requested to switch to ask mode.\"}", + "id": "call_modes_switch_001" + } + ] + } + } + ] +} diff --git a/apps/vscode-e2e/fixtures/task-hello-world.json b/apps/vscode-e2e/fixtures/task-hello-world.json new file mode 100644 index 00000000000..19b3da57fc1 --- /dev/null +++ b/apps/vscode-e2e/fixtures/task-hello-world.json @@ -0,0 +1,18 @@ +{ + "fixtures": [ + { + "match": { + "userMessage": "Hello world, what is your name? Respond with 'My name is ...'" + }, + "response": { + "toolCalls": [ + { + "name": "attempt_completion", + "arguments": "{\"result\":\"My name is Roo! I'm your AI coding assistant, here to help you with development tasks.\"}", + "id": "call_task_hello_world_001" + } + ] + } + } + ] +} diff --git a/apps/vscode-e2e/package.json b/apps/vscode-e2e/package.json index 900ac6d753c..4a1af856d32 100644 --- a/apps/vscode-e2e/package.json +++ b/apps/vscode-e2e/package.json @@ -6,6 +6,8 @@ "check-types": "tsc -p tsconfig.esm.json --noEmit", "format": "prettier --write src", "test:ci": "pnpm -w bundle && pnpm --filter @roo-code/vscode-webview build && pnpm test:run", + "test:ci:mock": "pnpm -w bundle && pnpm --filter @roo-code/vscode-webview build && USE_MOCK=true pnpm test:run", + "test:record": "AIMOCK_RECORD=true pnpm test:ci", "test:run": "rimraf out && tsc -p tsconfig.json && npx dotenvx run -f .env.local -- node ./out/runTest.js", "clean": "rimraf out .turbo" }, @@ -13,6 +15,7 @@ "@roo-code/config-eslint": "workspace:^", "@roo-code/config-typescript": "workspace:^", "@roo-code/types": "workspace:^", + "@copilotkit/aimock": "^1.15.1", "@types/mocha": "^10.0.10", "@types/node": "20.x", "@types/vscode": "^1.95.0", diff --git a/apps/vscode-e2e/src/runTest.ts b/apps/vscode-e2e/src/runTest.ts index 2bec946b4ab..5c249b06465 100644 --- a/apps/vscode-e2e/src/runTest.ts +++ b/apps/vscode-e2e/src/runTest.ts @@ -3,20 +3,69 @@ import * as os from "os" import * as fs from "fs/promises" import { runTests } from "@vscode/test-electron" +import { LLMock } from "@copilotkit/aimock" async function main() { - try { - // The folder containing the Extension Manifest package.json - // Passed to `--extensionDevelopmentPath` - const extensionDevelopmentPath = path.resolve(__dirname, "../../../src") + const isRecord = process.env.AIMOCK_RECORD === "true" + // Record mode always needs aimock running (to capture traffic). + // Replay mode starts aimock when no real API key is present or USE_MOCK is forced. + const useMock = isRecord || !process.env.OPENROUTER_API_KEY || process.env.USE_MOCK === "true" + + let mock: InstanceType | undefined + + if (useMock) { + const fixturesDir = path.resolve(__dirname, "../fixtures") + + mock = new LLMock({ + port: 0, // random free port + ...(isRecord && { + record: { + // OpenRouter is OpenAI-compatible; aimock proxies using the openai provider key. + // Use /api (not /api/v1) — aimock appends the request path (/v1/chat/completions) + // so including /v1 here would produce a doubled /v1/v1 upstream URL. + providers: { openai: "https://openrouter.ai/api" }, + fixturePath: fixturesDir, + }, + }), + }) + + mock.loadFixtureDir(fixturesDir) - // The path to the extension test script - // Passed to --extensionTestsPath - const extensionTestsPath = path.resolve(__dirname, "./suite/index") + if (!isRecord) { + // The modes test (switch_mode → ask) triggers a second API call whose last + // user message starts with directly — no + // wrapper. JSON fixtures use substring matching so a bare "" + // match would collide with all other requests. A regex anchored to the start + // uniquely identifies this post-switch turn. + mock.addFixture({ + match: { userMessage: /^/ }, + response: { + toolCalls: [ + { + name: "attempt_completion", + arguments: JSON.stringify({ result: "Switched to ❓ Ask mode as requested." }), + id: "call_modes_post_switch_001", + }, + ], + }, + }) + } - // Create a temporary workspace folder for tests - const testWorkspace = await fs.mkdtemp(path.join(os.tmpdir(), "roo-test-workspace-")) + await mock.start() + } + // The folder containing the Extension Manifest package.json + // Passed to `--extensionDevelopmentPath` + const extensionDevelopmentPath = path.resolve(__dirname, "../../../src") + + // The path to the extension test script + // Passed to --extensionTestsPath + const extensionTestsPath = path.resolve(__dirname, "./suite/index") + + // Create a temporary workspace folder for tests + const testWorkspace = await fs.mkdtemp(path.join(os.tmpdir(), "roo-test-workspace-")) + + try { // Get test filter from command line arguments or environment variable // Usage examples: // - npm run test:e2e -- --grep "write-to-file" @@ -25,11 +74,12 @@ async function main() { const testGrep = process.argv.find((arg, i) => process.argv[i - 1] === "--grep") || process.env.TEST_GREP const testFile = process.argv.find((arg, i) => process.argv[i - 1] === "--file") || process.env.TEST_FILE - // Pass test filters as environment variables to the test runner + // Pass test filters and mock URL as environment variables to the test runner const extensionTestsEnv = { ...process.env, ...(testGrep && { TEST_GREP: testGrep }), ...(testFile && { TEST_FILE: testFile }), + ...(mock && { AIMOCK_URL: mock.url }), } // Download VS Code, unzip it and run the integration test @@ -40,12 +90,12 @@ async function main() { extensionTestsEnv, version: process.env.VSCODE_VERSION || "1.101.2", }) - - // Clean up the temporary workspace - await fs.rm(testWorkspace, { recursive: true, force: true }) } catch (error) { console.error("Failed to run tests", error) process.exit(1) + } finally { + await fs.rm(testWorkspace, { recursive: true, force: true }) + await mock?.stop() } } diff --git a/apps/vscode-e2e/src/suite/index.ts b/apps/vscode-e2e/src/suite/index.ts index ab0be6e5dff..5c23fb9e71d 100644 --- a/apps/vscode-e2e/src/suite/index.ts +++ b/apps/vscode-e2e/src/suite/index.ts @@ -3,7 +3,7 @@ import Mocha from "mocha" import { glob } from "glob" import * as vscode from "vscode" -import type { RooCodeAPI } from "@roo-code/types" +import { RooCodeEventName, type RooCodeAPI } from "@roo-code/types" import { waitFor } from "./utils" @@ -16,15 +16,29 @@ export async function run() { const api = extension.isActive ? extension.exports : await extension.activate() + const aimockUrl = process.env.AIMOCK_URL + const isRecord = process.env.AIMOCK_RECORD === "true" + await api.setConfiguration({ apiProvider: "openrouter" as const, - openRouterApiKey: process.env.OPENROUTER_API_KEY!, + // In record mode, forward the real key so aimock can proxy it to OpenRouter. + // In replay mode, "mock-key" is sufficient — aimock never contacts the real API. + openRouterApiKey: aimockUrl && !isRecord ? "mock-key" : process.env.OPENROUTER_API_KEY!, openRouterModelId: "openai/gpt-4.1", + ...(aimockUrl && { openRouterBaseUrl: `${aimockUrl}/v1` }), }) await vscode.commands.executeCommand("roo-cline.SidebarProvider.focus") await waitFor(() => api.isReady()) + // Automatically approve completion_result asks so tests don't stall waiting + // for a button that the webview routes to "start new task" rather than "yes". + api.on(RooCodeEventName.Message, ({ message }) => { + if (message.type === "ask" && message.ask === "completion_result") { + api.approveCurrentAsk() + } + }) + globalThis.api = api const mochaOptions: Mocha.MochaOptions = { diff --git a/packages/core/vitest.config.ts b/packages/core/vitest.config.ts index b6d6dbb880f..fd7c24b19cb 100644 --- a/packages/core/vitest.config.ts +++ b/packages/core/vitest.config.ts @@ -5,5 +5,10 @@ export default defineConfig({ globals: true, environment: "node", watch: false, + coverage: { + provider: "v8", + reporter: ["text", "lcov"], + exclude: ["**/*.test.ts", "**/*.test.tsx", "**/*.spec.ts", "**/*.spec.tsx", "**/vitest.config.ts"], + }, }, }) diff --git a/packages/types/src/api.ts b/packages/types/src/api.ts index e61e1e61067..ad2130fb322 100644 --- a/packages/types/src/api.ts +++ b/packages/types/src/api.ts @@ -65,6 +65,12 @@ export interface RooCodeAPI extends EventEmitter { * Simulates pressing the secondary button in the chat interface. */ pressSecondaryButton(): Promise + /** + * Programmatically approves the current pending ask on the active task. + * Equivalent to the user clicking "Yes" for tool/command approvals, or + * confirming a completion result. No-ops if no task is active. + */ + approveCurrentAsk(): Promise /** * Returns true if the API is ready to use. */ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d95c2f02346..186d4ca11c5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,7 +8,6 @@ overrides: tar-fs: '>=3.1.1' esbuild: '>=0.25.0' undici: '>=5.29.0' - brace-expansion: ^2.0.2 form-data: '>=4.0.4' bluebird: '>=3.7.2' glob: '>=11.1.0' @@ -141,6 +140,9 @@ importers: '@types/react': specifier: ^18.3.23 version: 18.3.23 + '@vitest/coverage-v8': + specifier: ^3.2.3 + version: 3.2.4(vitest@3.2.4) ink-testing-library: specifier: ^4.0.0 version: 4.0.0(@types/react@18.3.23) @@ -156,6 +158,9 @@ importers: apps/vscode-e2e: devDependencies: + '@copilotkit/aimock': + specifier: ^1.15.1 + version: 1.15.1(vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@20.17.57)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0)) '@roo-code/config-eslint': specifier: workspace:^ version: link:../../packages/config-eslint @@ -511,6 +516,9 @@ importers: '@types/vscode': specifier: ^1.102.0 version: 1.103.0 + '@vitest/coverage-v8': + specifier: ^3.2.4 + version: 3.2.4(vitest@3.2.4) globals: specifier: ^16.3.0 version: 16.3.0 @@ -583,6 +591,9 @@ importers: '@types/node': specifier: ^24.1.0 version: 24.2.1 + '@vitest/coverage-v8': + specifier: ^3.2.3 + version: 3.2.4(vitest@3.2.4) vitest: specifier: ^3.2.3 version: 3.2.4(@types/debug@4.1.12)(@types/node@24.2.1)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0) @@ -703,6 +714,9 @@ importers: '@types/vscode': specifier: ^1.84.0 version: 1.100.0 + '@vitest/coverage-v8': + specifier: ^3.2.3 + version: 3.2.4(vitest@3.2.4) vitest: specifier: ^3.2.3 version: 3.2.4(@types/debug@4.1.12)(@types/node@20.17.57)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0) @@ -1092,6 +1106,9 @@ importers: '@types/vscode': specifier: ^1.84.0 version: 1.100.0 + '@vitest/coverage-v8': + specifier: ^3.2.3 + version: 3.2.4(vitest@3.2.4) '@vscode/test-electron': specifier: ^2.5.2 version: 2.5.2 @@ -1390,6 +1407,9 @@ importers: '@vitejs/plugin-react': specifier: ^4.3.4 version: 4.4.1(vite@6.3.6(@types/node@20.17.57)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0)) + '@vitest/coverage-v8': + specifier: ^3.2.3 + version: 3.2.4(vitest@3.2.4) '@vitest/ui': specifier: ^3.2.3 version: 3.2.4(vitest@3.2.4) @@ -1863,24 +1883,28 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@basetenlabs/performance-client-linux-riscv64-gnu@0.0.10': resolution: {integrity: sha512-2KUvdK4wuoZdIqNnJhx7cu6ybXCwtiwGAtlrEvhai3FOkUQ3wE2Xa+TQ33mNGSyFbw6wAvLawYtKVFmmw27gJw==} engines: {node: '>= 10'} cpu: [riscv64] os: [linux] + libc: [glibc] '@basetenlabs/performance-client-linux-x64-gnu@0.0.10': resolution: {integrity: sha512-9jjQPjHLiVOGwUPlmhnBl7OmmO7hQ8WMt+v3mJuxkS5JTNDmVOngfmgGlbN9NjBhQMENjdcMUVOquVo7HeybGQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@basetenlabs/performance-client-linux-x64-musl@0.0.10': resolution: {integrity: sha512-bjYB8FKcPvEa251Ep2Gm3tvywADL9eavVjZsikdf0AvJ1K5pT+vLLvJBU9ihBsTPWnbF4pJgxVjwS6UjVObsQA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@basetenlabs/performance-client-win32-arm64-msvc@0.0.10': resolution: {integrity: sha512-Vxq5UXEmfh3C3hpwXdp3Daaf0dnLR9zFH2x8MJ1Hf/TcilmOP1clneewNpIv0e7MrnT56Z4pM6P3d8VFMZqBKg==} @@ -1907,6 +1931,10 @@ packages: '@bcoe/v8-coverage@0.2.3': resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + '@bcoe/v8-coverage@1.0.2': + resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} + engines: {node: '>=18'} + '@braintree/sanitize-url@7.1.1': resolution: {integrity: sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw==} @@ -1980,6 +2008,19 @@ packages: '@chevrotain/utils@11.0.3': resolution: {integrity: sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==} + '@copilotkit/aimock@1.15.1': + resolution: {integrity: sha512-DG9p6fKdYmuTW0zaUe9iDbgB/CM3SWhpdhVBrszQ6+L2UW4+DZB0gvICFQXRWhVXMpqxEkI9Pqhm/MtMb8li9A==} + engines: {node: '>=24.0.0'} + hasBin: true + peerDependencies: + jest: '>=29' + vitest: '>=3' + peerDependenciesMeta: + jest: + optional: true + vitest: + optional: true + '@corex/deepmerge@4.0.43': resolution: {integrity: sha512-N8uEMrMPL0cu/bdboEWpQYb/0i2K5Qn8eCsxzOmxSggJbbQte7ljMRoXm917AbntqTGOzdTu+vP3KOOzoC70HQ==} @@ -2342,89 +2383,105 @@ packages: resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-arm@1.2.4': resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-ppc64@1.2.4': resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-riscv64@1.2.4': resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} cpu: [riscv64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-s390x@1.2.4': resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-x64@1.2.4': resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linuxmusl-arm64@1.2.4': resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-libvips-linuxmusl-x64@1.2.4': resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-linux-arm64@0.34.5': resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-linux-arm@0.34.5': resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-linux-ppc64@0.34.5': resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-linux-riscv64@0.34.5': resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [riscv64] os: [linux] + libc: [glibc] '@img/sharp-linux-s390x@0.34.5': resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-linux-x64@0.34.5': resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-linuxmusl-arm64@0.34.5': resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-linuxmusl-x64@0.34.5': resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-wasm32@0.34.5': resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} @@ -2520,6 +2577,9 @@ packages: '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@kwsites/file-exists@1.1.1': resolution: {integrity: sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==} @@ -2671,24 +2731,28 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@next/swc-linux-arm64-musl@16.1.6': resolution: {integrity: sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@next/swc-linux-x64-gnu@16.1.6': resolution: {integrity: sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@next/swc-linux-x64-musl@16.1.6': resolution: {integrity: sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@next/swc-win32-arm64-msvc@16.1.6': resolution: {integrity: sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw==} @@ -2755,24 +2819,28 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@node-rs/crc32-linux-arm64-musl@1.10.6': resolution: {integrity: sha512-k8ra/bmg0hwRrIEE8JL1p32WfaN9gDlUUpQRWsbxd1WhjqvXea7kKO6K4DwVxyxlPhBS9Gkb5Urq7Y4mXANzaw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@node-rs/crc32-linux-x64-gnu@1.10.6': resolution: {integrity: sha512-IfjtqcuFK7JrSZ9mlAFhb83xgium30PguvRjIMI45C3FJwu18bnLk1oR619IYb/zetQT82MObgmqfKOtgemEKw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@node-rs/crc32-linux-x64-musl@1.10.6': resolution: {integrity: sha512-LbFYsA5M9pNunOweSt6uhxenYQF94v3bHDAQRPTQ3rnjn+mK6IC7YTAYoBjvoJP8lVzcvk9hRj8wp4Jyh6Y80g==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@node-rs/crc32-wasm32-wasi@1.10.6': resolution: {integrity: sha512-KaejdLgHMPsRaxnM+OG9L9XdWL2TabNx80HLdsCOoX9BVhEkfh39OeahBo8lBmidylKbLGMQoGfIKDjq0YMStw==} @@ -2925,31 +2993,37 @@ packages: resolution: {integrity: sha512-BhEzNLjn4HjP8+Q18D3/jeIDBxW7OgoJYIjw2CaaysnYneoTlij8hPTKxHfyqq4IGM3fFs9TLR/k338M3zkQ7g==} cpu: [arm64] os: [linux] + libc: [glibc] '@oxc-resolver/binding-linux-arm64-musl@11.2.0': resolution: {integrity: sha512-yxbMYUgRmN2V8x8XoxmD/Qq6aG7YIW3ToMDILfmcfeeRRVieEJ3DOWBT0JSE+YgrOy79OyFDH/1lO8VnqLmDQQ==} cpu: [arm64] os: [linux] + libc: [musl] '@oxc-resolver/binding-linux-riscv64-gnu@11.2.0': resolution: {integrity: sha512-QG1UfgC2N2qhW1tOnDCgB/26vn1RCshR5sYPhMeaxO1gMQ3kEKbZ3QyBXxrG1IX5qsXYj5hPDJLDYNYUjRcOpg==} cpu: [riscv64] os: [linux] + libc: [glibc] '@oxc-resolver/binding-linux-s390x-gnu@11.2.0': resolution: {integrity: sha512-uqTDsQdi6mrkSV1gvwbuT8jf/WFl6qVDVjNlx7IPSaAByrNiJfPrhTmH8b+Do58Dylz7QIRZgxQ8CHIZSyBUdg==} cpu: [s390x] os: [linux] + libc: [glibc] '@oxc-resolver/binding-linux-x64-gnu@11.2.0': resolution: {integrity: sha512-GZdHXhJ7p6GaQg9MjRqLebwBf8BLvGIagccI6z5yMj4fV3LU4QuDfwSEERG+R6oQ/Su9672MBqWwncvKcKT68w==} cpu: [x64] os: [linux] + libc: [glibc] '@oxc-resolver/binding-linux-x64-musl@11.2.0': resolution: {integrity: sha512-YBAC3GOicYznReG2twE7oFPSeK9Z1f507z1EYWKg6HpGYRYRlJyszViu7PrhMT85r/MumDTs429zm+CNqpFWOA==} cpu: [x64] os: [linux] + libc: [musl] '@oxc-resolver/binding-wasm32-wasi@11.2.0': resolution: {integrity: sha512-+qlIg45CPVPy+Jn3vqU1zkxA/AAv6e/2Ax/ImX8usZa8Tr2JmQn/93bmSOOOnr9fXRV9d0n4JyqYzSWxWPYDEw==} @@ -3784,56 +3858,67 @@ packages: resolution: {integrity: sha512-de6TFZYIvJwRNjmW3+gaXiZ2DaWL5D5yGmSYzkdzjBDS3W+B9JQ48oZEsmMvemqjtAFzE16DIBLqd6IQQRuG9Q==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.40.2': resolution: {integrity: sha512-urjaEZubdIkacKc930hUDOfQPysezKla/O9qV+O89enqsqUmQm8Xj8O/vh0gHg4LYfv7Y7UsE3QjzLQzDYN1qg==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.40.2': resolution: {integrity: sha512-KlE8IC0HFOC33taNt1zR8qNlBYHj31qGT1UqWqtvR/+NuCVhfufAq9fxO8BMFC22Wu0rxOwGVWxtCMvZVLmhQg==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.40.2': resolution: {integrity: sha512-j8CgxvfM0kbnhu4XgjnCWJQyyBOeBI1Zq91Z850aUddUmPeQvuAy6OiMdPS46gNFgy8gN1xkYyLgwLYZG3rBOg==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loongarch64-gnu@4.40.2': resolution: {integrity: sha512-Ybc/1qUampKuRF4tQXc7G7QY9YRyeVSykfK36Y5Qc5dmrIxwFhrOzqaVTNoZygqZ1ZieSWTibfFhQ5qK8jpWxw==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-powerpc64le-gnu@4.40.2': resolution: {integrity: sha512-3FCIrnrt03CCsZqSYAOW/k9n625pjpuMzVfeI+ZBUSDT3MVIFDSPfSUgIl9FqUftxcUXInvFah79hE1c9abD+Q==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.40.2': resolution: {integrity: sha512-QNU7BFHEvHMp2ESSY3SozIkBPaPBDTsfVNGx3Xhv+TdvWXFGOSH2NJvhD1zKAT6AyuuErJgbdvaJhYVhVqrWTg==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.40.2': resolution: {integrity: sha512-5W6vNYkhgfh7URiXTO1E9a0cy4fSgfE4+Hl5agb/U1sa0kjOLMLC1wObxwKxecE17j0URxuTrYZZME4/VH57Hg==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.40.2': resolution: {integrity: sha512-B7LKIz+0+p348JoAL4X/YxGx9zOx3sR+o6Hj15Y3aaApNfAshK8+mWZEf759DXfRLeL2vg5LYJBB7DdcleYCoQ==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.40.2': resolution: {integrity: sha512-lG7Xa+BmBNwpjmVUbmyKxdQJ3Q6whHjMjzQplOs5Z+Gj7mxPtWakGHqzMqNER68G67kmCX9qX57aRsW5V0VOng==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.40.2': resolution: {integrity: sha512-tD46wKHd+KJvsmije4bUskNuvWKFcTOIM9tZ/RrmIvcXnbi0YK/cKS9FzFtAm7Oxi2EhV5N2OpfFB348vSQRXA==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-win32-arm64-msvc@4.40.2': resolution: {integrity: sha512-Bjv/HG8RRWLNkXwQQemdsWw4Mg+IJ29LK+bJPW2SCzPKOUaMmPEppQlu/Fqk1d7+DX3V7JbFdbkh/NMmurT6Pg==} @@ -4164,48 +4249,56 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-arm64-gnu@4.1.8': resolution: {integrity: sha512-qq7jXtO1+UEtCmCeBBIRDrPFIVI4ilEQ97qgBGdwXAARrUqSn/L9fUrkb1XP/mvVtoVeR2bt/0L77xx53bPZ/Q==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.1.6': resolution: {integrity: sha512-8kjivE5xW0qAQ9HX9reVFmZj3t+VmljDLVRJpVBEoTR+3bKMnvC7iLcoSGNIUJGOZy1mLVq7x/gerVg0T+IsYw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@tailwindcss/oxide-linux-arm64-musl@4.1.8': resolution: {integrity: sha512-O6b8QesPbJCRshsNApsOIpzKt3ztG35gfX9tEf4arD7mwNinsoCKxkj8TgEE0YRjmjtO3r9FlJnT/ENd9EVefQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.1.6': resolution: {integrity: sha512-A4spQhwnWVpjWDLXnOW9PSinO2PTKJQNRmL/aIl2U/O+RARls8doDfs6R41+DAXK0ccacvRyDpR46aVQJJCoCg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-x64-gnu@4.1.8': resolution: {integrity: sha512-32iEXX/pXwikshNOGnERAFwFSfiltmijMIAbUhnNyjFr3tmWmMJWQKU2vNcFX0DACSXJ3ZWcSkzNbaKTdngH6g==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.1.6': resolution: {integrity: sha512-YRee+6ZqdzgiQAHVSLfl3RYmqeeaWVCk796MhXhLQu2kJu2COHBkqlqsqKYx3p8Hmk5pGCQd2jTAoMWWFeyG2A==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@tailwindcss/oxide-linux-x64-musl@4.1.8': resolution: {integrity: sha512-s+VSSD+TfZeMEsCaFaHTaY5YNj3Dri8rST09gMvYQKwPphacRG7wbuQ5ZJMIJXN/puxPcg/nU+ucvWguPpvBDg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.1.6': resolution: {integrity: sha512-qAp4ooTYrBQ5pk5jgg54/U1rCJ/9FLYOkkQ/nTE+bVMseMfB6O7J8zb19YTpWuu4UdfRf5zzOrNKfl6T64MNrQ==} @@ -4711,6 +4804,15 @@ packages: peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 + '@vitest/coverage-v8@3.2.4': + resolution: {integrity: sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==} + peerDependencies: + '@vitest/browser': 3.2.4 + vitest: 3.2.4 + peerDependenciesMeta: + '@vitest/browser': + optional: true + '@vitest/expect@3.2.4': resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} @@ -5034,6 +5136,9 @@ packages: resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==} engines: {node: '>=4'} + ast-v8-to-istanbul@0.3.12: + resolution: {integrity: sha512-BRRC8VRZY2R4Z4lFIL35MwNXmwVqBityvOIwETtsCSwvjl0IdgFsy9NhdaA6j74nUdtJJlIypeRhpDam19Wq3g==} + async-function@1.0.0: resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} engines: {node: '>= 0.4'} @@ -5086,6 +5191,10 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + bare-events@2.5.4: resolution: {integrity: sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==} @@ -5175,9 +5284,16 @@ packages: bowser@2.11.0: resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==} + brace-expansion@1.1.14: + resolution: {integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==} + brace-expansion@2.0.2: resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + brace-expansion@5.0.5: + resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} + engines: {node: 18 || 20 || >=22} + braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -5521,6 +5637,9 @@ packages: resolution: {integrity: sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==} engines: {node: '>= 14'} + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + confbox@0.1.8: resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} @@ -7503,6 +7622,10 @@ packages: resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} engines: {node: '>=10'} + istanbul-lib-source-maps@5.0.6: + resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} + engines: {node: '>=10'} + istanbul-reports@3.1.7: resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} engines: {node: '>=8'} @@ -7561,6 +7684,9 @@ packages: resolution: {integrity: sha512-pbKLsbCfi7kriM3s1J4DDCo7jQkI58zPLHi0heXPzPlj0hjUsm+FesPUbE0DSbIVIK503A36aUBoCN7eMFedkA==} engines: {node: '>=1.0.0'} + js-tokens@10.0.0: + resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -7773,48 +7899,56 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-gnu@1.30.1: resolution: {integrity: sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.29.2: resolution: {integrity: sha512-Q64eM1bPlOOUgxFmoPUefqzY1yV3ctFPE6d/Vt7WzLW4rKTv7MyYNky+FWxRpLkNASTnKQUaiMJ87zNODIrrKQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-arm64-musl@1.30.1: resolution: {integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.29.2: resolution: {integrity: sha512-0v6idDCPG6epLXtBH/RPkHvYx74CVziHo6TMYga8O2EiQApnUPZsbR9nFNrg2cgBzk1AYqEd95TlrsL7nYABQg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-gnu@1.30.1: resolution: {integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.29.2: resolution: {integrity: sha512-rMpz2yawkgGT8RULc5S4WiZopVMOFWjiItBT7aSfDX4NQav6M44rhn5hjtkKzB+wMTRlLLqxkeYEtQ3dd9696w==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-linux-x64-musl@1.30.1: resolution: {integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.29.2: resolution: {integrity: sha512-nL7zRW6evGQqYVu/bKGK+zShyz8OVzsCotFgc7judbt6wnB2KbiKKJwBE4SGoDBQ1O94RjW4asrCjQL4i8Fhbw==} @@ -8045,6 +8179,9 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + magicast@0.3.5: + resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} + make-dir@4.0.0: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} @@ -8311,6 +8448,10 @@ packages: resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==} engines: {node: 20 || >=22} + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -10052,6 +10193,10 @@ packages: resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} engines: {node: '>=8'} + test-exclude@7.0.2: + resolution: {integrity: sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==} + engines: {node: '>=18'} + text-decoder@1.2.3: resolution: {integrity: sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==} @@ -11940,6 +12085,8 @@ snapshots: '@bcoe/v8-coverage@0.2.3': {} + '@bcoe/v8-coverage@1.0.2': {} + '@braintree/sanitize-url@7.1.1': {} '@changesets/apply-release-plan@7.0.13': @@ -12103,6 +12250,10 @@ snapshots: '@chevrotain/utils@11.0.3': {} + '@copilotkit/aimock@1.15.1(vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@20.17.57)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0))': + optionalDependencies: + vitest: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@20.17.57)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0) + '@corex/deepmerge@4.0.43': {} '@csstools/color-helpers@5.0.2': {} @@ -12562,6 +12713,11 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + '@kwsites/file-exists@1.1.1': dependencies: debug: 4.4.1(supports-color@8.1.1) @@ -14879,6 +15035,25 @@ snapshots: transitivePeerDependencies: - supports-color + '@vitest/coverage-v8@3.2.4(vitest@3.2.4)': + dependencies: + '@ampproject/remapping': 2.3.0 + '@bcoe/v8-coverage': 1.0.2 + ast-v8-to-istanbul: 0.3.12 + debug: 4.4.3 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.1.7 + magic-string: 0.30.21 + magicast: 0.3.5 + std-env: 3.10.0 + test-exclude: 7.0.2 + tinyrainbow: 2.0.0 + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.2.1)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0) + transitivePeerDependencies: + - supports-color + '@vitest/expect@3.2.4': dependencies: '@types/chai': 5.2.2 @@ -15324,6 +15499,12 @@ snapshots: dependencies: tslib: 2.8.1 + ast-v8-to-istanbul@0.3.12: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + estree-walker: 3.0.3 + js-tokens: 10.0.0 + async-function@1.0.0: {} async-mutex@0.5.0: @@ -15376,6 +15557,8 @@ snapshots: balanced-match@1.0.2: {} + balanced-match@4.0.4: {} + bare-events@2.5.4: optional: true @@ -15465,10 +15648,19 @@ snapshots: bowser@2.11.0: {} + brace-expansion@1.1.14: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + brace-expansion@2.0.2: dependencies: balanced-match: 1.0.2 + brace-expansion@5.0.5: + dependencies: + balanced-match: 4.0.4 + braces@3.0.3: dependencies: fill-range: 7.1.1 @@ -15825,6 +16017,8 @@ snapshots: normalize-path: 3.0.0 readable-stream: 4.7.0 + concat-map@0.0.1: {} + confbox@0.1.8: {} confbox@0.2.2: {} @@ -17989,6 +18183,14 @@ snapshots: make-dir: 4.0.0 supports-color: 7.2.0 + istanbul-lib-source-maps@5.0.6: + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + debug: 4.4.3 + istanbul-lib-coverage: 3.2.2 + transitivePeerDependencies: + - supports-color + istanbul-reports@3.1.7: dependencies: html-escaper: 2.0.2 @@ -18061,6 +18263,8 @@ snapshots: dependencies: easy-stack: 1.0.1 + js-tokens@10.0.0: {} + js-tokens@4.0.0: {} js-tokens@9.0.1: {} @@ -18544,6 +18748,12 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + magicast@0.3.5: + dependencies: + '@babel/parser': 7.27.2 + '@babel/types': 7.27.1 + source-map-js: 1.2.1 + make-dir@4.0.0: dependencies: semver: 7.7.3 @@ -19061,9 +19271,13 @@ snapshots: dependencies: '@isaacs/brace-expansion': 5.0.0 + minimatch@10.2.5: + dependencies: + brace-expansion: 5.0.5 + minimatch@3.1.2: dependencies: - brace-expansion: 2.0.2 + brace-expansion: 1.1.14 minimatch@5.1.6: dependencies: @@ -21132,6 +21346,12 @@ snapshots: glob: 11.1.0 minimatch: 3.1.2 + test-exclude@7.0.2: + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 11.1.0 + minimatch: 10.2.5 + text-decoder@1.2.3: dependencies: b4a: 1.6.7 diff --git a/src/extension/api.ts b/src/extension/api.ts index 4a66b40078d..ad6bef4fa3a 100644 --- a/src/extension/api.ts +++ b/src/extension/api.ts @@ -294,6 +294,10 @@ export class API extends EventEmitter implements RooCodeAPI { await this.sidebarProvider.postMessageToWebview({ type: "invoke", invoke: "secondaryButtonClick" }) } + public async approveCurrentAsk() { + this.sidebarProvider.getCurrentTask()?.approveAsk() + } + public isReady() { return this.sidebarProvider.viewLaunched } From dea23780916232c7fdcd45eb3e7de4f089abc9f7 Mon Sep 17 00:00:00 2001 From: Elliott de Launay Date: Sun, 26 Apr 2026 22:02:06 -0400 Subject: [PATCH 3/6] fix(ci) --- apps/vscode-e2e/src/suite/index.ts | 4 ++-- packages/types/tsup.config.ts | 3 +++ src/package.json | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/vscode-e2e/src/suite/index.ts b/apps/vscode-e2e/src/suite/index.ts index 5c23fb9e71d..065872f8886 100644 --- a/apps/vscode-e2e/src/suite/index.ts +++ b/apps/vscode-e2e/src/suite/index.ts @@ -3,7 +3,7 @@ import Mocha from "mocha" import { glob } from "glob" import * as vscode from "vscode" -import { RooCodeEventName, type RooCodeAPI } from "@roo-code/types" +import type { RooCodeAPI, RooCodeEventName } from "@roo-code/types" import { waitFor } from "./utils" @@ -33,7 +33,7 @@ export async function run() { // Automatically approve completion_result asks so tests don't stall waiting // for a button that the webview routes to "start new task" rather than "yes". - api.on(RooCodeEventName.Message, ({ message }) => { + api.on("message" as RooCodeEventName.Message, ({ message }) => { if (message.type === "ask" && message.ask === "completion_result") { api.approveCurrentAsk() } diff --git a/packages/types/tsup.config.ts b/packages/types/tsup.config.ts index 38b458806ae..0a1482d6ff6 100644 --- a/packages/types/tsup.config.ts +++ b/packages/types/tsup.config.ts @@ -8,4 +8,7 @@ export default defineConfig({ sourcemap: true, clean: true, outDir: "dist", + // ai-sdk-provider-poe is ESM-only (./code subpath has no "require" condition) + // so tsup must bundle it inline rather than emit a runtime require() call. + noExternal: ["ai-sdk-provider-poe"], }) diff --git a/src/package.json b/src/package.json index 4f1c58f10e6..43810195dc1 100644 --- a/src/package.json +++ b/src/package.json @@ -437,6 +437,7 @@ "lint": "eslint . --ext=ts --max-warnings=0", "check-types": "tsc --noEmit", "pretest": "turbo run bundle --cwd ..", + "pretest:coverage": "turbo run bundle --cwd ..", "test": "vitest run", "test:coverage": "vitest run --coverage", "format": "prettier --write .", From f8d0666dc7d96f03774b56812174298e438f1934 Mon Sep 17 00:00:00 2001 From: Elliott de Launay Date: Sun, 26 Apr 2026 22:45:51 -0400 Subject: [PATCH 4/6] nit: coderabbit --- apps/vscode-e2e/AGENTS.md | 2 +- apps/vscode-e2e/src/runTest.ts | 7 ++++++- apps/vscode-e2e/src/suite/index.ts | 4 ++++ .../custom-tools/__tests__/custom-tool-registry.spec.ts | 4 ++-- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/apps/vscode-e2e/AGENTS.md b/apps/vscode-e2e/AGENTS.md index 847a010931a..6a3a347b629 100644 --- a/apps/vscode-e2e/AGENTS.md +++ b/apps/vscode-e2e/AGENTS.md @@ -34,7 +34,7 @@ Record mode uses **record-on-miss**: if an existing fixture already matches a re 4. Find the auto-recorded file for your test: ```sh - grep -l "your unique prompt text" fixtures/openai-*.json + grep -l "your unique prompt text" apps/vscode-e2e/fixtures/openai-*.json ``` 5. Inspect it to find the `response` block (tool calls the LLM made). diff --git a/apps/vscode-e2e/src/runTest.ts b/apps/vscode-e2e/src/runTest.ts index 5c249b06465..38b1839fd38 100644 --- a/apps/vscode-e2e/src/runTest.ts +++ b/apps/vscode-e2e/src/runTest.ts @@ -7,6 +7,11 @@ import { LLMock } from "@copilotkit/aimock" async function main() { const isRecord = process.env.AIMOCK_RECORD === "true" + + if (isRecord && !process.env.OPENROUTER_API_KEY) { + throw new Error("AIMOCK_RECORD=true requires OPENROUTER_API_KEY to record fixtures") + } + // Record mode always needs aimock running (to capture traffic). // Replay mode starts aimock when no real API key is present or USE_MOCK is forced. const useMock = isRecord || !process.env.OPENROUTER_API_KEY || process.env.USE_MOCK === "true" @@ -92,7 +97,7 @@ async function main() { }) } catch (error) { console.error("Failed to run tests", error) - process.exit(1) + process.exitCode = 1 } finally { await fs.rm(testWorkspace, { recursive: true, force: true }) await mock?.stop() diff --git a/apps/vscode-e2e/src/suite/index.ts b/apps/vscode-e2e/src/suite/index.ts index 065872f8886..c8073f9ce78 100644 --- a/apps/vscode-e2e/src/suite/index.ts +++ b/apps/vscode-e2e/src/suite/index.ts @@ -19,6 +19,10 @@ export async function run() { const aimockUrl = process.env.AIMOCK_URL const isRecord = process.env.AIMOCK_RECORD === "true" + if (isRecord && !process.env.OPENROUTER_API_KEY) { + throw new Error("AIMOCK_RECORD=true requires OPENROUTER_API_KEY") + } + await api.setConfiguration({ apiProvider: "openrouter" as const, // In record mode, forward the real key so aimock can proxy it to OpenRouter. diff --git a/packages/core/src/custom-tools/__tests__/custom-tool-registry.spec.ts b/packages/core/src/custom-tools/__tests__/custom-tool-registry.spec.ts index d1694f2effa..dcd92ae9644 100644 --- a/packages/core/src/custom-tools/__tests__/custom-tool-registry.spec.ts +++ b/packages/core/src/custom-tools/__tests__/custom-tool-registry.spec.ts @@ -227,7 +227,7 @@ describe("CustomToolRegistry", () => { expect(result.loaded).toContain("simple") expect(registry.has("simple")).toBe(true) - }, 120_000) + }, 300_000) it("should handle named exports", async () => { const result = await registry.loadFromDirectory(TEST_FIXTURES_DIR) @@ -281,7 +281,7 @@ describe("CustomToolRegistry", () => { const result = await registry.loadFromDirectory(TEST_FIXTURES_DIR) expect(result.loaded).toContain("cached") - }, 120_000) + }, 300_000) }) describe.sequential("loadFromDirectories", () => { From 84e16e9da1fa6a328f034da9dedcc3eb8af2ae87 Mon Sep 17 00:00:00 2001 From: Elliott de Launay Date: Sun, 26 Apr 2026 23:05:40 -0400 Subject: [PATCH 5/6] refactor(runTest): moving workspace create into try block --- apps/vscode-e2e/src/runTest.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/vscode-e2e/src/runTest.ts b/apps/vscode-e2e/src/runTest.ts index 38b1839fd38..4ea13f81584 100644 --- a/apps/vscode-e2e/src/runTest.ts +++ b/apps/vscode-e2e/src/runTest.ts @@ -67,10 +67,11 @@ async function main() { // Passed to --extensionTestsPath const extensionTestsPath = path.resolve(__dirname, "./suite/index") - // Create a temporary workspace folder for tests - const testWorkspace = await fs.mkdtemp(path.join(os.tmpdir(), "roo-test-workspace-")) + let testWorkspace: string | undefined try { + // Create a temporary workspace folder for tests + testWorkspace = await fs.mkdtemp(path.join(os.tmpdir(), "roo-test-workspace-")) // Get test filter from command line arguments or environment variable // Usage examples: // - npm run test:e2e -- --grep "write-to-file" @@ -99,7 +100,9 @@ async function main() { console.error("Failed to run tests", error) process.exitCode = 1 } finally { - await fs.rm(testWorkspace, { recursive: true, force: true }) + if (testWorkspace) { + await fs.rm(testWorkspace, { recursive: true, force: true }) + } await mock?.stop() } } From 791ae33c10559f929a4d87cd7e023c231ec636e3 Mon Sep 17 00:00:00 2001 From: Elliott de Launay Date: Sun, 26 Apr 2026 23:14:07 -0400 Subject: [PATCH 6/6] feat(turbo): adding caching --- .github/workflows/code-qa.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/code-qa.yml b/.github/workflows/code-qa.yml index b2671bccbbc..58eb242eb9a 100644 --- a/.github/workflows/code-qa.yml +++ b/.github/workflows/code-qa.yml @@ -58,6 +58,14 @@ jobs: uses: actions/checkout@v4 - name: Setup Node.js and pnpm uses: ./.github/actions/setup-node-pnpm + - name: Cache Turbo + uses: actions/cache@v4 + with: + path: .turbo/cache + key: ${{ runner.os }}-turbo-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-turbo-${{ hashFiles('**/pnpm-lock.yaml') }}- + ${{ runner.os }}-turbo- - name: Run unit tests with coverage run: pnpm test:coverage - name: Upload coverage to Codecov