diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 990cf0a925..2f8bb58a56 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,6 +19,7 @@ jobs: runs-on: ubuntu-latest outputs: extension: ${{ steps.filter.outputs.extension }} + is_internal_head: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }} sdk-ts: ${{ steps.filter.outputs.sdk-ts }} sdk-python: ${{ steps.filter.outputs.sdk-python }} sdk-go: ${{ steps.filter.outputs.sdk-go }} @@ -39,6 +40,7 @@ jobs: - 'packages/sdk-ts/**' - 'packages/extension/**' - 'packages/protocol/**' + - 'packages/integrations/**' - 'pnpm-lock.yaml' - 'turbo.json' - '.github/**' @@ -167,6 +169,7 @@ jobs: packages/extension/dist/** packages/extension/artifacts/** packages/sdk-ts/dist/** + packages/integrations/dist/** packages/evals/dist/** retention-days: 1 @@ -211,6 +214,7 @@ jobs: env: CHROME_PATH: ${{ steps.setup-chrome.outputs.chrome-path }} - name: Browserbase smoke test + if: needs.determine-changes.outputs.is_internal_head == 'true' run: >- pnpm exec vitest run --root . packages/sdk-ts/tests/browser-runtime/stagehandBrowserbaseSmoke.test.ts diff --git a/packages/integrations/README.md b/packages/integrations/README.md new file mode 100644 index 0000000000..86cbf8043b --- /dev/null +++ b/packages/integrations/README.md @@ -0,0 +1,5 @@ +# Stagehand integrations + +Private workspace package for Stagehand integration adapters. + +The code-mode stdio entrypoint currently provides the MCP host and process lifecycle used by later code-mode capabilities. It intentionally advertises no tools yet. diff --git a/packages/integrations/package.json b/packages/integrations/package.json new file mode 100644 index 0000000000..264cfa56b9 --- /dev/null +++ b/packages/integrations/package.json @@ -0,0 +1,33 @@ +{ + "name": "@browserbasehq/stagehand-integrations", + "version": "4.0.0", + "private": true, + "description": "Shared integration surfaces for Stagehand V4", + "files": [ + "dist" + ], + "type": "module", + "exports": { + "./codemode/stdio-server": { + "import": "./dist/codemode/stdio-server.mjs" + } + }, + "scripts": { + "build": "tsdown", + "test": "pnpm run build && vitest run --root ../.. packages/integrations/tests", + "test:unit": "vitest run --root ../.. packages/integrations/tests", + "typecheck": "tsc --noEmit -p tsconfig.json" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "catalog:" + }, + "devDependencies": { + "@types/node": "catalog:", + "tsdown": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:" + }, + "engines": { + "node": ">=22.18.0" + } +} diff --git a/packages/integrations/src/codemode/mcp-runtime.ts b/packages/integrations/src/codemode/mcp-runtime.ts new file mode 100644 index 0000000000..89fa58ab89 --- /dev/null +++ b/packages/integrations/src/codemode/mcp-runtime.ts @@ -0,0 +1,13 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; + +export function createCodeModeMcpHost(): McpServer { + return new McpServer({ + name: "stagehand-codemode", + version: "4.0.0", + }); +} + +export async function connectCodeModeStdio(server: McpServer): Promise { + await server.connect(new StdioServerTransport()); +} diff --git a/packages/integrations/src/codemode/stdio-lifecycle.ts b/packages/integrations/src/codemode/stdio-lifecycle.ts new file mode 100644 index 0000000000..b12736a647 --- /dev/null +++ b/packages/integrations/src/codemode/stdio-lifecycle.ts @@ -0,0 +1,25 @@ +export type AsyncCloser = { + close(): Promise; +}; + +export const STDIO_SHUTDOWN_GRACE_MS = 5_000; + +export async function closeCodeModeStdio( + resources: readonly AsyncCloser[], + timeoutMs = STDIO_SHUTDOWN_GRACE_MS, +): Promise { + let timeout: NodeJS.Timeout | undefined; + const cleanup = Promise.allSettled( + resources.map((resource) => Promise.resolve().then(() => resource.close())), + ).then((results) => results.every((result) => result.status === "fulfilled")); + const deadline = new Promise((resolve) => { + timeout = setTimeout(() => resolve(false), timeoutMs); + timeout.unref(); + }); + + try { + return await Promise.race([cleanup, deadline]); + } finally { + if (timeout) clearTimeout(timeout); + } +} diff --git a/packages/integrations/src/codemode/stdio-server.ts b/packages/integrations/src/codemode/stdio-server.ts new file mode 100644 index 0000000000..4d93cb4050 --- /dev/null +++ b/packages/integrations/src/codemode/stdio-server.ts @@ -0,0 +1,23 @@ +import { connectCodeModeStdio, createCodeModeMcpHost } from "./mcp-runtime.js"; +import { closeCodeModeStdio } from "./stdio-lifecycle.js"; + +const server = createCodeModeMcpHost(); +let closing = false; + +async function shutdown(code: number): Promise { + if (closing) return; + closing = true; + const clean = await closeCodeModeStdio([server]); + if (!clean) { + process.stderr.write("Failed to close Stagehand code mode cleanly.\n"); + } + process.exit(code === 0 && !clean ? 1 : code); +} + +process.once("SIGINT", () => void shutdown(130)); +process.once("SIGTERM", () => void shutdown(143)); +process.stdin.once("end", () => void shutdown(0)); +process.stdin.once("close", () => void shutdown(0)); + +await connectCodeModeStdio(server); +process.stderr.write("Stagehand code-mode MCP host listening on stdio\n"); diff --git a/packages/integrations/tests/mcp-runtime.test.ts b/packages/integrations/tests/mcp-runtime.test.ts new file mode 100644 index 0000000000..dcfa098c3f --- /dev/null +++ b/packages/integrations/tests/mcp-runtime.test.ts @@ -0,0 +1,26 @@ +import { Client } from "@modelcontextprotocol/sdk/client/index.js"; +import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { createCodeModeMcpHost } from "../src/codemode/mcp-runtime.js"; + +describe("code-mode MCP host", () => { + let client: Client; + let server: ReturnType; + + beforeEach(async () => { + server = createCodeModeMcpHost(); + client = new Client({ name: "stagehand-codemode-host-test", version: "1.0.0" }); + const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); + await server.connect(serverTransport); + await client.connect(clientTransport); + }); + + afterEach(async () => { + await client.close(); + await server.close(); + }); + + it("initializes without advertising the tools capability", () => { + expect(client.getServerCapabilities()).not.toHaveProperty("tools"); + }); +}); diff --git a/packages/integrations/tests/stdio-lifecycle.test.ts b/packages/integrations/tests/stdio-lifecycle.test.ts new file mode 100644 index 0000000000..5eac6dfe5e --- /dev/null +++ b/packages/integrations/tests/stdio-lifecycle.test.ts @@ -0,0 +1,42 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; +import { closeCodeModeStdio } from "../src/codemode/stdio-lifecycle.js"; + +describe("closeCodeModeStdio", () => { + afterEach(() => vi.useRealTimers()); + + it("closes every resource concurrently", async () => { + const first = { close: vi.fn(async () => undefined) }; + const second = { close: vi.fn(async () => undefined) }; + + await expect(closeCodeModeStdio([first, second], 50)).resolves.toBe(true); + expect(first.close).toHaveBeenCalledOnce(); + expect(second.close).toHaveBeenCalledOnce(); + }); + + it("reports cleanup failures without exposing their messages", async () => { + const healthy = { close: vi.fn(async () => undefined) }; + const failing = { close: vi.fn(async () => Promise.reject(new Error("secret detail"))) }; + + await expect(closeCodeModeStdio([healthy, failing], 50)).resolves.toBe(false); + }); + + it("contains synchronous cleanup failures", async () => { + const failing = { + close: vi.fn(() => { + throw new Error("secret detail"); + }), + }; + + await expect(closeCodeModeStdio([failing], 50)).resolves.toBe(false); + }); + + it("bounds cleanup when a resource never settles", async () => { + vi.useFakeTimers(); + const stuck = { close: vi.fn(() => new Promise(() => undefined)) }; + const result = closeCodeModeStdio([stuck], 5_000); + + await vi.advanceTimersByTimeAsync(5_000); + + await expect(result).resolves.toBe(false); + }); +}); diff --git a/packages/integrations/tests/stdio-server.test.ts b/packages/integrations/tests/stdio-server.test.ts new file mode 100644 index 0000000000..25d8bb3018 --- /dev/null +++ b/packages/integrations/tests/stdio-server.test.ts @@ -0,0 +1,160 @@ +import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process"; +import { PassThrough, type Stream } from "node:stream"; +import { fileURLToPath } from "node:url"; +import { Client } from "@modelcontextprotocol/sdk/client/index.js"; +import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; +import { describe, expect, it } from "vitest"; + +const entrypoint = fileURLToPath(new URL("../dist/codemode/stdio-server.mjs", import.meta.url)); +const baseEnv = { PATH: process.env.PATH ?? "" }; +const readyMessage = "Stagehand code-mode MCP host listening on stdio"; + +function startServer(): ChildProcessWithoutNullStreams { + return spawn(process.execPath, [entrypoint], { + env: baseEnv, + stdio: ["pipe", "pipe", "pipe"], + }); +} + +async function waitForReady(child: ChildProcessWithoutNullStreams): Promise { + let stderr = ""; + return await new Promise((resolve, reject) => { + const timeout = setTimeout( + () => reject(new Error(`stdio host did not start: ${stderr}`)), + 10_000, + ); + const onData = (chunk: Buffer) => { + stderr += chunk.toString(); + if (!stderr.includes(readyMessage)) return; + clearTimeout(timeout); + child.stderr.off("data", onData); + resolve(stderr); + }; + child.stderr.on("data", onData); + child.once("exit", (code, signal) => { + clearTimeout(timeout); + reject( + new Error(`stdio host exited before ready (code=${code}, signal=${signal}): ${stderr}`), + ); + }); + }); +} + +function waitForOutput(stream: Stream, expected: string): Promise { + let output = ""; + return new Promise((resolve, reject) => { + const cleanup = () => { + clearTimeout(timeout); + stream.off("data", onData); + stream.off("error", onError); + stream.off("end", onEnd); + stream.off("close", onClose); + }; + const succeed = () => { + cleanup(); + resolve(output); + }; + const fail = (message: string) => { + cleanup(); + reject(new Error(message)); + }; + const onData = (chunk: Buffer) => { + output += chunk.toString(); + if (output.includes(expected)) succeed(); + }; + const onError = () => fail(`stdio output stream failed before ${JSON.stringify(expected)}`); + const onEnd = () => fail(`stdio output stream ended before ${JSON.stringify(expected)}`); + const onClose = () => fail(`stdio output stream closed before ${JSON.stringify(expected)}`); + const timeout = setTimeout( + () => fail(`stdio host did not emit ${JSON.stringify(expected)}: ${output}`), + 10_000, + ); + stream.on("data", onData); + stream.once("error", onError); + stream.once("end", onEnd); + stream.once("close", onClose); + }); +} + +function waitForExit( + child: ChildProcessWithoutNullStreams, +): Promise<{ code: number | null; signal: NodeJS.Signals | null }> { + return new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + child.kill("SIGKILL"); + reject(new Error("stdio host did not exit within 10 seconds")); + }, 10_000); + child.once("error", reject); + child.once("exit", (code, signal) => { + clearTimeout(timeout); + resolve({ code, signal }); + }); + }); +} + +describe("built code-mode stdio host", () => { + it("cleans up output waiters when the stream closes before the expected output", async () => { + const stream = new PassThrough(); + const output = waitForOutput(stream, readyMessage); + + stream.destroy(); + + await expect(output).rejects.toThrow(`closed before ${JSON.stringify(readyMessage)}`); + expect(stream.listenerCount("data")).toBe(0); + expect(stream.listenerCount("error")).toBe(0); + expect(stream.listenerCount("end")).toBe(0); + expect(stream.listenerCount("close")).toBe(0); + }); + + it("starts and exits successfully on stdin EOF", async () => { + const child = startServer(); + try { + await waitForReady(child); + const exit = waitForExit(child); + child.stdin.end(); + await expect(exit).resolves.toStrictEqual({ code: 0, signal: null }); + } finally { + if (child.exitCode === null && child.signalCode === null) child.kill("SIGKILL"); + } + }); + + it.skipIf(process.platform === "win32")( + "preserves SIGINT and SIGTERM exit semantics", + async () => { + for (const [signal, expectedCode] of [ + ["SIGINT", 130], + ["SIGTERM", 143], + ] as const) { + const child = startServer(); + try { + await waitForReady(child); + const exit = waitForExit(child); + child.kill(signal); + await expect(exit).resolves.toStrictEqual({ code: expectedCode, signal: null }); + } finally { + if (child.exitCode === null && child.signalCode === null) child.kill("SIGKILL"); + } + } + }, + 30_000, + ); + + it("initializes without advertising tools through the compiled child", async () => { + const transport = new StdioClientTransport({ + command: process.execPath, + args: [entrypoint], + env: baseEnv, + stderr: "pipe", + }); + if (!transport.stderr) throw new Error("stdio transport did not expose stderr"); + const ready = waitForOutput(transport.stderr, readyMessage); + const client = new Client({ name: "stagehand-codemode-stdio-test", version: "1.0.0" }); + + try { + await Promise.all([client.connect(transport), ready]); + expect(client.getServerCapabilities()).not.toHaveProperty("tools"); + } finally { + await client.close(); + } + }); +}); diff --git a/packages/integrations/tsconfig.json b/packages/integrations/tsconfig.json new file mode 100644 index 0000000000..36876ff6db --- /dev/null +++ b/packages/integrations/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "module": "NodeNext", + "moduleResolution": "NodeNext", + "target": "ES2022", + "types": ["node"], + "rootDir": ".", + "noEmit": true + }, + "include": ["src/**/*.ts", "tests/**/*.ts"], + "exclude": ["dist", "node_modules"] +} diff --git a/packages/integrations/tsdown.config.ts b/packages/integrations/tsdown.config.ts new file mode 100644 index 0000000000..587cfd7b80 --- /dev/null +++ b/packages/integrations/tsdown.config.ts @@ -0,0 +1,15 @@ +import { defineConfig } from "tsdown"; + +export default defineConfig({ + entry: { + "codemode/stdio-server": "src/codemode/stdio-server.ts", + }, + format: ["esm"], + platform: "node", + target: "node22", + dts: { + sourcemap: true, + }, + sourcemap: true, + outDir: "dist", +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b461760f35..7882eaeae3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -244,6 +244,9 @@ catalogs: '@mdx-js/mdx': specifier: 3.1.1 version: 3.1.1 + '@modelcontextprotocol/sdk': + specifier: 1.29.0 + version: 1.29.0 '@opentelemetry/api': specifier: 1.9.1 version: 1.9.1 @@ -491,72 +494,6 @@ importers: specifier: 'catalog:' version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@24.13.2)(vite@8.1.3(@types/node@24.13.2)(esbuild@0.28.1)(jiti@1.21.7)(tsx@4.23.1)(yaml@2.9.0)) - packages/protocol: - dependencies: - camelcase-keys: - specifier: 'catalog:' - version: 10.0.2 - snakecase-keys: - specifier: 'catalog:' - version: 9.0.2 - zod: - specifier: 'catalog:' - version: 4.4.3 - devDependencies: - '@types/chrome': - specifier: 'catalog:' - version: 0.2.2 - '@types/node': - specifier: 'catalog:' - version: 24.13.2 - chrome-launcher: - specifier: 'catalog:' - version: 1.2.1 - oxfmt: - specifier: 'catalog:' - version: 0.57.0 - typescript: - specifier: 'catalog:' - version: 5.9.3 - vite: - specifier: 8.1.3 - version: 8.1.3(@types/node@24.13.2)(esbuild@0.28.1)(jiti@1.21.7)(tsx@4.23.1)(yaml@2.9.0) - vitest: - specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@24.13.2)(vite@8.1.3(@types/node@24.13.2)(esbuild@0.28.1)(jiti@1.21.7)(tsx@4.23.1)(yaml@2.9.0)) - - packages/sdk-go: {} - - packages/sdk-python: {} - - packages/sdk-ts: - dependencies: - '@browserbasehq/sdk': - specifier: 'catalog:' - version: 2.16.0 - '@opentelemetry/api': - specifier: 'catalog:' - version: 1.9.1 - '@opentelemetry/core': - specifier: 'catalog:' - version: 2.9.0(@opentelemetry/api@1.9.1) - chrome-launcher: - specifier: 'catalog:' - version: 1.2.1 - zod: - specifier: 'catalog:' - version: 4.4.3 - devDependencies: - publint: - specifier: 'catalog:' - version: 0.3.21 - tsdown: - specifier: 'catalog:' - version: 0.22.3(publint@0.3.21)(tsx@4.23.1)(typescript@5.9.3) - vitest: - specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@25.9.4)(vite@8.1.3(@types/node@25.9.4)(esbuild@0.28.1)(jiti@1.21.7)(tsx@4.23.1)(yaml@2.9.0)) - packages/extension: dependencies: '@ai-sdk/anthropic': @@ -627,6 +564,91 @@ importers: specifier: 'catalog:' version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@24.13.2)(vite@8.1.3(@types/node@24.13.2)(esbuild@0.28.1)(jiti@1.21.7)(tsx@4.23.1)(yaml@2.9.0)) + packages/integrations: + dependencies: + '@modelcontextprotocol/sdk': + specifier: 'catalog:' + version: 1.29.0(zod@4.4.3) + devDependencies: + '@types/node': + specifier: 'catalog:' + version: 24.13.2 + tsdown: + specifier: 'catalog:' + version: 0.22.3(publint@0.3.21)(tsx@4.23.1)(typescript@5.9.3) + typescript: + specifier: 'catalog:' + version: 5.9.3 + vitest: + specifier: 'catalog:' + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@24.13.2)(vite@8.1.3(@types/node@24.13.2)(esbuild@0.28.1)(jiti@1.21.7)(tsx@4.23.1)(yaml@2.9.0)) + + packages/protocol: + dependencies: + camelcase-keys: + specifier: 'catalog:' + version: 10.0.2 + snakecase-keys: + specifier: 'catalog:' + version: 9.0.2 + zod: + specifier: 'catalog:' + version: 4.4.3 + devDependencies: + '@types/chrome': + specifier: 'catalog:' + version: 0.2.2 + '@types/node': + specifier: 'catalog:' + version: 24.13.2 + chrome-launcher: + specifier: 'catalog:' + version: 1.2.1 + oxfmt: + specifier: 'catalog:' + version: 0.57.0 + typescript: + specifier: 'catalog:' + version: 5.9.3 + vite: + specifier: 8.1.3 + version: 8.1.3(@types/node@24.13.2)(esbuild@0.28.1)(jiti@1.21.7)(tsx@4.23.1)(yaml@2.9.0) + vitest: + specifier: 'catalog:' + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@24.13.2)(vite@8.1.3(@types/node@24.13.2)(esbuild@0.28.1)(jiti@1.21.7)(tsx@4.23.1)(yaml@2.9.0)) + + packages/sdk-go: {} + + packages/sdk-python: {} + + packages/sdk-ts: + dependencies: + '@browserbasehq/sdk': + specifier: 'catalog:' + version: 2.16.0 + '@opentelemetry/api': + specifier: 'catalog:' + version: 1.9.1 + '@opentelemetry/core': + specifier: 'catalog:' + version: 2.9.0(@opentelemetry/api@1.9.1) + chrome-launcher: + specifier: 'catalog:' + version: 1.2.1 + zod: + specifier: 'catalog:' + version: 4.4.3 + devDependencies: + publint: + specifier: 'catalog:' + version: 0.3.21 + tsdown: + specifier: 'catalog:' + version: 0.22.3(publint@0.3.21)(tsx@4.23.1)(typescript@5.9.3) + vitest: + specifier: 'catalog:' + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@25.9.4)(vite@8.1.3(@types/node@25.9.4)(esbuild@0.28.1)(jiti@1.21.7)(tsx@4.23.1)(yaml@2.9.0)) + packages: '@ai-sdk/amazon-bedrock@3.0.111': @@ -8976,8 +8998,8 @@ snapshots: cross-spawn: 7.0.6 eventsource: 3.0.7 eventsource-parser: 3.1.0 - express: 5.2.1(supports-color@8.1.1) - express-rate-limit: 8.6.0(express@5.2.1(supports-color@8.1.1)) + express: 5.2.1 + express-rate-limit: 8.6.0(express@5.2.1) hono: 4.12.32 jose: 6.2.4 json-schema-typed: 8.0.2 @@ -10315,7 +10337,7 @@ snapshots: transitivePeerDependencies: - supports-color - body-parser@2.3.0(supports-color@8.1.1): + body-parser@2.3.0: dependencies: bytes: 3.1.2 content-type: 2.0.0 @@ -11181,10 +11203,10 @@ snapshots: expr-eval-fork@3.0.3: {} - express-rate-limit@8.6.0(express@5.2.1(supports-color@8.1.1)): + express-rate-limit@8.6.0(express@5.2.1): dependencies: debug: 4.4.3(supports-color@8.1.1) - express: 5.2.1(supports-color@8.1.1) + express: 5.2.1 ip-address: 10.2.0 transitivePeerDependencies: - supports-color @@ -11225,10 +11247,10 @@ snapshots: transitivePeerDependencies: - supports-color - express@5.2.1(supports-color@8.1.1): + express@5.2.1: dependencies: accepts: 2.0.0 - body-parser: 2.3.0(supports-color@8.1.1) + body-parser: 2.3.0 content-disposition: 1.1.0 content-type: 1.0.5 cookie: 0.7.2 @@ -11238,7 +11260,7 @@ snapshots: encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 - finalhandler: 2.1.1(supports-color@8.1.1) + finalhandler: 2.1.1 fresh: 2.0.0 http-errors: 2.0.1 merge-descriptors: 2.0.0 @@ -11249,8 +11271,8 @@ snapshots: proxy-addr: 2.0.7 qs: 6.15.3 range-parser: 1.2.1 - router: 2.2.0(supports-color@8.1.1) - send: 1.2.1(supports-color@8.1.1) + router: 2.2.0 + send: 1.2.1 serve-static: 2.2.1 statuses: 2.0.2 type-is: 2.1.0 @@ -11364,7 +11386,7 @@ snapshots: transitivePeerDependencies: - supports-color - finalhandler@2.1.1(supports-color@8.1.1): + finalhandler@2.1.1: dependencies: debug: 4.4.3(supports-color@8.1.1) encodeurl: 2.0.0 @@ -14041,7 +14063,7 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.1.5 '@rolldown/binding-win32-x64-msvc': 1.1.5 - router@2.2.0(supports-color@8.1.1): + router@2.2.0: dependencies: debug: 4.4.3(supports-color@8.1.1) depd: 2.0.0 @@ -14131,7 +14153,7 @@ snapshots: transitivePeerDependencies: - supports-color - send@1.2.1(supports-color@8.1.1): + send@1.2.1: dependencies: debug: 4.4.3(supports-color@8.1.1) encodeurl: 2.0.0 @@ -14166,7 +14188,7 @@ snapshots: encodeurl: 2.0.0 escape-html: 1.0.3 parseurl: 1.3.3 - send: 1.2.1(supports-color@8.1.1) + send: 1.2.1 transitivePeerDependencies: - supports-color diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 90e5e925a0..1ffc7f1bd4 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -4,6 +4,7 @@ packages: catalogMode: prefer catalog: + "@modelcontextprotocol/sdk": 1.29.0 "@ast-grep/lang-go": 0.0.6 "@ast-grep/lang-python": 0.0.6 "@ast-grep/napi": 0.44.1 diff --git a/turbo.json b/turbo.json index 21eef1e19d..fcecb93d4b 100644 --- a/turbo.json +++ b/turbo.json @@ -26,6 +26,11 @@ "inputs": ["$TURBO_DEFAULT$", "!dist/**"], "outputs": ["dist/**"] }, + "@browserbasehq/stagehand-integrations#build": { + "dependsOn": ["^build"], + "inputs": ["$TURBO_DEFAULT$", "!dist/**"], + "outputs": ["dist/**"] + }, "@browserbasehq/stagehand-evals#build": { "dependsOn": ["^build"], "inputs": ["$TURBO_DEFAULT$", "!dist/**"], @@ -66,6 +71,19 @@ "@browserbasehq/stagehand-evals#typecheck": { "dependsOn": ["^build"] }, + "@browserbasehq/stagehand-integrations#typecheck": { + "dependsOn": ["^build"] + }, + "@browserbasehq/stagehand-integrations#test:unit": { + "dependsOn": ["^build", "@browserbasehq/stagehand-integrations#build"], + "inputs": [ + "$TURBO_DEFAULT$", + "tests/**", + "src/**", + "**/*.test.ts", + "$TURBO_ROOT$/vitest.config.ts" + ] + }, "@browserbasehq/stagehand-docs#typecheck": {}, "test:unit": { "dependsOn": ["^build"], diff --git a/vitest.config.ts b/vitest.config.ts index 826ac255c9..0c3f2127e5 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -9,6 +9,7 @@ export default defineConfig({ "packages/protocol/json-rpc/tests/**/*.test.ts", "packages/docs/tests/**/*.test.ts", "packages/evals/tests/**/*.test.ts", + "packages/integrations/tests/**/*.test.ts", "packages/extension/tests/**/*.test.ts", "packages/sdk-ts/tests/**/*.test.ts", "packages/extension/understudy/**/*.test.ts",