From a82573467a6403e4ced03ec85f1cee191001bae3 Mon Sep 17 00:00:00 2001 From: Richard Solomou Date: Sun, 19 Jul 2026 12:22:20 +0300 Subject: [PATCH 1/3] feat(agent): add local browser use Add an opt-in isolated Playwright MCP server for every local agent adapter. Generated-By: PostHog Code Task-Id: 18a0ed17-2c63-4288-98b3-65563803ac72 --- AGENTS.md | 3 + docs/BROWSER-USE.md | 30 ++++++++ packages/core/src/sessions/sessionService.ts | 12 +++- .../features/sessions/sessionServiceHost.ts | 1 + .../settings/sections/AdvancedSettings.tsx | 8 +++ .../features/settings/settingsStore.test.ts | 2 + .../ui/src/features/settings/settingsStore.ts | 5 ++ packages/workspace-server/package.json | 1 + .../src/services/agent/agent.test.ts | 68 +++++++++++++++++++ .../src/services/agent/agent.ts | 18 ++++- .../services/agent/browser-use-mcp.test.ts | 29 ++++++++ .../src/services/agent/browser-use-mcp.ts | 43 ++++++++++++ .../src/services/agent/schemas.ts | 4 ++ pnpm-lock.yaml | 31 +++++++++ 14 files changed, 252 insertions(+), 3 deletions(-) create mode 100644 docs/BROWSER-USE.md create mode 100644 packages/workspace-server/src/services/agent/browser-use-mcp.test.ts create mode 100644 packages/workspace-server/src/services/agent/browser-use-mcp.ts diff --git a/AGENTS.md b/AGENTS.md index b9e3e21ac7..d5318135d1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,3 +1,5 @@ + + # PostHog Code Development Guide `AGENTS.md` is the source of truth for architecture and development rules. `CLAUDE.md` is a symlink to this file. Edit this file only. @@ -229,6 +231,7 @@ See [docs/conventions.md](./docs/conventions.md). - Use SDK types from `@anthropic-ai/claude-agent-sdk` and `@agentclientprotocol/sdk`. - Do not use Claude Code SDK `rawInput`. Use Zod-validated metadata. - User approvals are tool calls with permissions. Do not model approvals as custom methods plus notifications. +- Keep browser and computer-use capabilities adapter-neutral unless an adapter has a documented technical constraint. ## Key Libraries diff --git a/docs/BROWSER-USE.md b/docs/BROWSER-USE.md new file mode 100644 index 0000000000..c9ed66cd21 --- /dev/null +++ b/docs/BROWSER-USE.md @@ -0,0 +1,30 @@ + + +# Browser use + +PostHog Code can give local agent sessions browser automation tools through an isolated Playwright MCP server. + +## Enable it + +1. Install Google Chrome. +2. Open **Settings → Advanced**. +3. Enable **Browser use**. +4. Start a new local session. + +The setting applies when a session starts. Existing sessions are unchanged. + +## Behavior + +- Browser use is opt-in and disabled by default. +- It is available only to local sessions. +- Each session launches an isolated Chrome profile, so it does not inherit cookies or logins from the user's normal browser profile. +- Tool calls and screenshots use the existing MCP tool-call pipeline. +- Cloud sessions do not receive the local browser server. + +## Scope + +This feature automates websites in a dedicated Chrome window. It does not control arbitrary desktop applications or the user's existing browser windows. + +## Implementation + +The workspace session layer injects a pinned `@playwright/mcp` stdio server for every supported local agent adapter instead of maintaining a bespoke browser-control protocol. This keeps browser lifecycle, accessibility snapshots, input actions, and image responses on Playwright's supported MCP implementation while reusing PostHog Code's existing MCP tool-call and approval surfaces. diff --git a/packages/core/src/sessions/sessionService.ts b/packages/core/src/sessions/sessionService.ts index 096826e4fb..c40fcc9622 100644 --- a/packages/core/src/sessions/sessionService.ts +++ b/packages/core/src/sessions/sessionService.ts @@ -337,6 +337,7 @@ export interface SessionServiceDeps { rtkEnabledCloud?: boolean; spokenNotifications?: boolean; spokenNarrationEnabled?: boolean; + browserUse?: boolean; }; usageLimit: { show: (...args: any[]) => any }; readonly addDirectoryDialog: { open: boolean }; @@ -1088,14 +1089,19 @@ export class SessionService { this.d.log.warn("Failed to verify workspace", { taskId, err }); }); - const { customInstructions, rtkEnabledLocal, spokenNarrationEnabled } = - this.d.settings; + const { + customInstructions, + rtkEnabledLocal, + spokenNarrationEnabled, + browserUse, + } = this.d.settings; const result = await this.d.trpc.agent.reconnect.mutate({ taskId, taskRunId, repoPath, rtkEnabled: rtkEnabledLocal, spokenNarration: spokenNarrationEnabled === true, + browserUse: browserUse === true, apiHost: auth.apiHost, projectId: auth.projectId, logUrl, @@ -1419,6 +1425,7 @@ export class SessionService { customInstructions: startCustomInstructions, rtkEnabledLocal, spokenNarrationEnabled, + browserUse, } = this.d.settings; const preferredModel = model ?? this.d.DEFAULT_GATEWAY_MODEL; const result = await this.d.trpc.agent.start.mutate({ @@ -1432,6 +1439,7 @@ export class SessionService { customInstructions: startCustomInstructions || undefined, rtkEnabled: rtkEnabledLocal, spokenNarration: spokenNarrationEnabled === true, + browserUse: browserUse === true, effort: effortLevelSchema.safeParse(reasoningLevel).success ? (reasoningLevel as EffortLevel) : undefined, diff --git a/packages/ui/src/features/sessions/sessionServiceHost.ts b/packages/ui/src/features/sessions/sessionServiceHost.ts index 9811a82430..edb71ffa10 100644 --- a/packages/ui/src/features/sessions/sessionServiceHost.ts +++ b/packages/ui/src/features/sessions/sessionServiceHost.ts @@ -137,6 +137,7 @@ function buildSessionServiceDeps(): SessionServiceDeps { ), import.meta.env.DEV, ), + browserUse: state.browserUse, }; }, usageLimit: { diff --git a/packages/ui/src/features/settings/sections/AdvancedSettings.tsx b/packages/ui/src/features/settings/sections/AdvancedSettings.tsx index 12843aba89..b2ca90c572 100644 --- a/packages/ui/src/features/settings/sections/AdvancedSettings.tsx +++ b/packages/ui/src/features/settings/sections/AdvancedSettings.tsx @@ -33,6 +33,8 @@ export function AdvancedSettings() { const setRtkEnabledLocal = useSettingsStore((s) => s.setRtkEnabledLocal); const rtkEnabledCloud = useSettingsStore((s) => s.rtkEnabledCloud); const setRtkEnabledCloud = useSettingsStore((s) => s.setRtkEnabledCloud); + const browserUse = useSettingsStore((s) => s.browserUse); + const setBrowserUse = useSettingsStore((s) => s.setBrowserUse); const hostTRPC = useHostTRPC(); const { data: rtkStatus } = useQuery(hostTRPC.agent.rtkStatus.queryOptions()); const devModeClient = useServiceOptional(DEV_MODE_CLIENT); @@ -88,6 +90,12 @@ export function AdvancedSettings() { )} + + + { expect(useSettingsStore.getState().lastUsedLocalWorkspaceMode).toBe( "local", ); + expect(useSettingsStore.getState().browserUse).toBe(false); }); }); @@ -220,6 +221,7 @@ describe("feature settingsStore cloud selections", () => { ["slotMachineMode", false, true], ["dismissibleUpdateBanners", false, true], ["showSidebarWorktrees", false, true], + ["browserUse", false, true], ] as const)("rehydrates %s", async (field, initial, persisted) => { getItem.mockResolvedValue( JSON.stringify({ state: { [field]: persisted }, version: 0 }), diff --git a/packages/ui/src/features/settings/settingsStore.ts b/packages/ui/src/features/settings/settingsStore.ts index 161ed7a11a..1dcea3148e 100644 --- a/packages/ui/src/features/settings/settingsStore.ts +++ b/packages/ui/src/features/settings/settingsStore.ts @@ -211,12 +211,14 @@ interface SettingsStore { // sessions, cloud covers cloud runs. rtkEnabledLocal: boolean; rtkEnabledCloud: boolean; + browserUse: boolean; setAllowBypassPermissions: (enabled: boolean) => void; setPreventSleepWhileRunning: (enabled: boolean) => void; setDebugLogsCloudRuns: (enabled: boolean) => void; setAutoPublishCloudRuns: (enabled: boolean) => void; setRtkEnabledLocal: (enabled: boolean) => void; setRtkEnabledCloud: (enabled: boolean) => void; + setBrowserUse: (enabled: boolean) => void; // Terminal terminalFont: TerminalFont; @@ -428,6 +430,7 @@ export const useSettingsStore = create()( autoPublishCloudRuns: true, rtkEnabledLocal: true, rtkEnabledCloud: true, + browserUse: false, setAllowBypassPermissions: (enabled) => set({ allowBypassPermissions: enabled }), setPreventSleepWhileRunning: (enabled) => @@ -437,6 +440,7 @@ export const useSettingsStore = create()( set({ autoPublishCloudRuns: enabled }), setRtkEnabledLocal: (enabled) => set({ rtkEnabledLocal: enabled }), setRtkEnabledCloud: (enabled) => set({ rtkEnabledCloud: enabled }), + setBrowserUse: (enabled) => set({ browserUse: enabled }), // Terminal terminalFont: "berkeley-mono", @@ -566,6 +570,7 @@ export const useSettingsStore = create()( autoPublishCloudRuns: state.autoPublishCloudRuns, rtkEnabledLocal: state.rtkEnabledLocal, rtkEnabledCloud: state.rtkEnabledCloud, + browserUse: state.browserUse, // Terminal terminalFont: state.terminalFont, diff --git a/packages/workspace-server/package.json b/packages/workspace-server/package.json index 5f89350d09..a96c9e6801 100644 --- a/packages/workspace-server/package.json +++ b/packages/workspace-server/package.json @@ -25,6 +25,7 @@ "@hono/trpc-server": "catalog:", "@modelcontextprotocol/sdk": "^1.29.0", "@parcel/watcher": "catalog:", + "@playwright/mcp": "0.0.78", "@posthog/agent": "workspace:*", "@posthog/di": "workspace:*", "@posthog/enricher": "workspace:*", diff --git a/packages/workspace-server/src/services/agent/agent.test.ts b/packages/workspace-server/src/services/agent/agent.test.ts index 7d479b71df..305586aa9c 100644 --- a/packages/workspace-server/src/services/agent/agent.test.ts +++ b/packages/workspace-server/src/services/agent/agent.test.ts @@ -116,6 +116,19 @@ vi.mock("@posthog/agent/adapters/claude/session/jsonl-hydration", () => ({ hydrateSessionJsonl: vi.fn().mockResolvedValue(undefined), })); +vi.mock("./browser-use-mcp", () => ({ + buildBrowserUseServer: vi.fn((enabled: boolean | undefined) => + enabled + ? { + name: "playwright", + command: process.execPath, + args: ["/mock/playwright-mcp/cli.js", "--isolated"], + env: [{ name: "ELECTRON_RUN_AS_NODE", value: "1" }], + } + : null, + ), +})); + vi.mock("node:fs", async (importOriginal) => { const original = await importOriginal(); return { @@ -345,6 +358,48 @@ describe("AgentService", () => { expect(codexMcp).toEqual(claudeMcp); }); + it("passes the same browser MCP server to Claude and Codex", async () => { + await service.startSession({ + ...baseSessionParams, + taskRunId: "run-claude", + adapter: "claude", + browserUse: true, + }); + await service.startSession({ + ...baseSessionParams, + taskRunId: "run-codex", + adapter: "codex", + browserUse: true, + }); + + const claudeBrowserServer = + mockNewSession.mock.calls[0][0].mcpServers.at(-1); + const codexBrowserServer = + mockNewSession.mock.calls[1][0].mcpServers.at(-1); + expect(claudeBrowserServer).toMatchObject({ + name: "playwright", + args: expect.arrayContaining(["--isolated"]), + }); + expect(codexBrowserServer).toEqual(claudeBrowserServer); + }); + + it.each([undefined, false])( + "does not pass browser MCP when browserUse is %s", + async (browserUse) => { + await service.startSession({ + ...baseSessionParams, + adapter: "claude", + browserUse, + }); + + expect(mockNewSession.mock.calls[0][0].mcpServers).not.toEqual( + expect.arrayContaining([ + expect.objectContaining({ name: "playwright" }), + ]), + ); + }, + ); + it("drops unreachable MCP servers for codex but keeps them for claude", async () => { vi.stubGlobal( "fetch", @@ -414,6 +469,19 @@ describe("AgentService", () => { "spokenNarration", ); }); + + it("keeps browser configuration out of adapter-specific session meta", async () => { + await service.startSession({ + ...baseSessionParams, + adapter: "claude", + browserUse: true, + }); + + expect(mockNewSession).toHaveBeenCalledTimes(1); + expect(mockNewSession.mock.calls[0][0]._meta).not.toHaveProperty( + "browserUse", + ); + }); }); describe("permission requests", () => { diff --git a/packages/workspace-server/src/services/agent/agent.ts b/packages/workspace-server/src/services/agent/agent.ts index 9af21e716d..014c16442b 100644 --- a/packages/workspace-server/src/services/agent/agent.ts +++ b/packages/workspace-server/src/services/agent/agent.ts @@ -88,6 +88,7 @@ import type { ProcessTrackingService } from "../process-tracking/process-trackin import { loadSessionEnvOverrides } from "../session-env/loader"; import { isScratchPath } from "../workspace/scratch"; import type { AgentAuthAdapter, McpToolInstallations } from "./auth-adapter"; +import { buildBrowserUseServer } from "./browser-use-mcp"; import { cleanupCodexHome, prepareCodexHome } from "./codex-home"; import { discoverExternalPlugins } from "./discover-plugins"; import { @@ -290,6 +291,8 @@ interface SessionConfig { rtkEnabled?: boolean; /** The user's spoken-narration setting at session start. */ spokenNarration?: boolean; + /** Whether local sessions may launch the isolated browser-use tools. */ + browserUse?: boolean; } /** Pull the adapter's `agentCapabilities._meta.posthog.steering` from initialize. */ @@ -935,10 +938,22 @@ If a repository IS genuinely required, attach one in this priority order: // ("ACP connection closed") and makes the host silently fall back to a // Claude/Opus session. Claude connects lazily and is unaffected, so only // the Codex server list is pruned to the reachable ones. - const sessionMcpServers = + const reachableMcpServers = adapter === "codex" ? await this.filterReachableMcpServers(mcpServers, taskRunId) : mcpServers; + let browserUseServer: ReturnType = null; + try { + browserUseServer = buildBrowserUseServer(config.browserUse, "local"); + } catch (err) { + this.log.warn("Browser-use server unavailable; continuing without it", { + error: err instanceof Error ? err.message : String(err), + }); + } + const sessionMcpServers = [ + ...reachableMcpServers, + ...(browserUseServer ? [browserUseServer] : []), + ]; let externalPlugins: Awaited> = []; @@ -2028,6 +2043,7 @@ For git operations while detached: rtkEnabled: "rtkEnabled" in params ? params.rtkEnabled : undefined, spokenNarration: "spokenNarration" in params ? params.spokenNarration : undefined, + browserUse: "browserUse" in params ? params.browserUse : undefined, }; } diff --git a/packages/workspace-server/src/services/agent/browser-use-mcp.test.ts b/packages/workspace-server/src/services/agent/browser-use-mcp.test.ts new file mode 100644 index 0000000000..bb9e78735d --- /dev/null +++ b/packages/workspace-server/src/services/agent/browser-use-mcp.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, it } from "vitest"; +import { BROWSER_USE_MCP_NAME, buildBrowserUseServer } from "./browser-use-mcp"; + +describe("buildBrowserUseServer", () => { + it.each([ + { enabled: false, environment: "local" as const }, + { enabled: true, environment: "cloud" as const }, + { enabled: undefined, environment: "local" as const }, + ])("returns null for $enabled in $environment", (input) => { + expect(buildBrowserUseServer(input.enabled, input.environment)).toBeNull(); + }); + + it("builds an isolated Chrome MCP server for local sessions", () => { + const server = buildBrowserUseServer(true, "local"); + + expect(server).toMatchObject({ + name: BROWSER_USE_MCP_NAME, + command: process.execPath, + args: expect.arrayContaining([ + "--browser", + "chrome", + "--isolated", + "vision", + ]), + env: [{ name: "ELECTRON_RUN_AS_NODE", value: "1" }], + }); + expect(server?.args[0]).toMatch(/@playwright[\\/]mcp[\\/]cli\.js$/); + }); +}); diff --git a/packages/workspace-server/src/services/agent/browser-use-mcp.ts b/packages/workspace-server/src/services/agent/browser-use-mcp.ts new file mode 100644 index 0000000000..40236b6228 --- /dev/null +++ b/packages/workspace-server/src/services/agent/browser-use-mcp.ts @@ -0,0 +1,43 @@ +import { existsSync } from "node:fs"; +import { createRequire } from "node:module"; +import { dirname, join } from "node:path"; +import type { McpServerStdio } from "@agentclientprotocol/sdk"; + +export const BROWSER_USE_MCP_NAME = "playwright"; +const require = createRequire(import.meta.url); + +function resolvePlaywrightMcpCli(): string { + const packageJson = require.resolve("@playwright/mcp/package.json"); + const cli = join(dirname(packageJson), "cli.js"); + if (!existsSync(cli)) { + throw new Error(`Playwright MCP CLI not found at ${cli}`); + } + return cli; +} + +export function buildBrowserUseServer( + enabled: boolean | undefined, + environment: "local" | "cloud", +): McpServerStdio | null { + if (!enabled || environment !== "local") return null; + + return { + name: BROWSER_USE_MCP_NAME, + command: process.execPath, + args: [ + resolvePlaywrightMcpCli(), + "--browser", + "chrome", + "--isolated", + "--caps", + "vision", + "--codegen", + "none", + "--image-responses", + "allow", + "--output-mode", + "stdout", + ], + env: [{ name: "ELECTRON_RUN_AS_NODE", value: "1" }], + }; +} diff --git a/packages/workspace-server/src/services/agent/schemas.ts b/packages/workspace-server/src/services/agent/schemas.ts index 5d8eeab508..5670e9419f 100644 --- a/packages/workspace-server/src/services/agent/schemas.ts +++ b/packages/workspace-server/src/services/agent/schemas.ts @@ -98,6 +98,8 @@ export const startSessionInput = z.object({ * narration off, so headless runs never load the tool. */ spokenNarration: z.boolean().optional(), + /** Enables the isolated Playwright browser tools for local sessions. */ + browserUse: z.boolean().optional(), }); export type StartSessionInput = z.infer; @@ -233,6 +235,8 @@ export const reconnectSessionInput = z.object({ rtkEnabled: z.boolean().optional(), /** See startSessionInput.spokenNarration. */ spokenNarration: z.boolean().optional(), + /** See startSessionInput.browserUse. */ + browserUse: z.boolean().optional(), }); export type ReconnectSessionInput = z.infer; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 68e8fe7e82..2ecc30db16 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1510,6 +1510,9 @@ importers: '@parcel/watcher': specifier: 'catalog:' version: 2.5.6 + '@playwright/mcp': + specifier: 0.0.78 + version: 0.0.78 '@posthog/agent': specifier: workspace:* version: link:../agent @@ -5595,6 +5598,11 @@ packages: resolution: {integrity: sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==} engines: {node: ^14.18.0 || >=16.0.0} + '@playwright/mcp@0.0.78': + resolution: {integrity: sha512-XLTUeA6mEN9sQ+hJ4dfG8EIkDbxS0K3Trc2RBkUJuf02TgE2FQRNTMtq/aJfhyRMINsRl/Ybc4sxcWLtFn4/TQ==} + engines: {node: '>=18'} + hasBin: true + '@playwright/test@1.60.0': resolution: {integrity: sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==} engines: {node: '>=18'} @@ -13114,11 +13122,21 @@ packages: engines: {node: '>=18'} hasBin: true + playwright-core@1.62.0-alpha-1783623505000: + resolution: {integrity: sha512-CPJZdsA/KGT2QQlekiV6Wt+QlQrZHVSZ6oiNtOI/bYYOIVLM8jfKGWTM4zQiyd4UN+40Cq4cA6lxmZHZbtPvJQ==} + engines: {node: '>=20'} + hasBin: true + playwright@1.60.0: resolution: {integrity: sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==} engines: {node: '>=18'} hasBin: true + playwright@1.62.0-alpha-1783623505000: + resolution: {integrity: sha512-6KV9h4PP3hqu4NaGdxxcijWfYh9LJcFI/R2sP4TTC4I5cFo3oRawN0ETlW5MkE3cQEgKhhoj0KUNz4sfpCT0Tg==} + engines: {node: '>=20'} + hasBin: true + plist@3.1.0: resolution: {integrity: sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==} engines: {node: '>=10.4.0'} @@ -19915,6 +19933,11 @@ snapshots: '@pkgr/core@0.3.6': {} + '@playwright/mcp@0.0.78': + dependencies: + playwright: 1.62.0-alpha-1783623505000 + playwright-core: 1.62.0-alpha-1783623505000 + '@playwright/test@1.60.0': dependencies: playwright: 1.60.0 @@ -28712,12 +28735,20 @@ snapshots: playwright-core@1.60.0: {} + playwright-core@1.62.0-alpha-1783623505000: {} + playwright@1.60.0: dependencies: playwright-core: 1.60.0 optionalDependencies: fsevents: 2.3.2 + playwright@1.62.0-alpha-1783623505000: + dependencies: + playwright-core: 1.62.0-alpha-1783623505000 + optionalDependencies: + fsevents: 2.3.2 + plist@3.1.0: dependencies: '@xmldom/xmldom': 0.8.11 From dd0b708f2900a9aad9a46c53f1f53b3e1a124096 Mon Sep 17 00:00:00 2001 From: Richard Solomou Date: Sun, 19 Jul 2026 13:22:10 +0300 Subject: [PATCH 2/3] fix(code): package browser automation runtime Generated-By: PostHog Code Task-Id: 18a0ed17-2c63-4288-98b3-65563803ac72 --- apps/code/runtime-dependencies.test.ts | 16 ++++++++++++++++ apps/code/runtime-dependencies.ts | 7 +++++++ apps/code/scripts/before-pack.ts | 5 +++++ 3 files changed, 28 insertions(+) diff --git a/apps/code/runtime-dependencies.test.ts b/apps/code/runtime-dependencies.test.ts index 2aaa3c0ec0..869fde9469 100644 --- a/apps/code/runtime-dependencies.test.ts +++ b/apps/code/runtime-dependencies.test.ts @@ -5,6 +5,7 @@ import { macOnlyNativeModules, packagedFileGlobs, requiredNativeModules, + runtimeJavaScriptModules, runtimeNativeModules, watcherPackageFor, } from "./runtime-dependencies"; @@ -42,6 +43,21 @@ describe("native module globs", () => { expect(packagedFileGlobs).toContain("node_modules/node-pty/**/*"); expect(packagedFileGlobs).toContain("node_modules/better-sqlite3/**/*"); }); + + it("packages browser automation runtime modules", () => { + expect(runtimeJavaScriptModules).toEqual([ + "@playwright/mcp", + "playwright", + "playwright-core", + ]); + expect(packagedFileGlobs).toEqual( + expect.arrayContaining([ + "node_modules/@playwright/mcp/**/*", + "node_modules/playwright/**/*", + "node_modules/playwright-core/**/*", + ]), + ); + }); }); describe("native module list invariants", () => { diff --git a/apps/code/runtime-dependencies.ts b/apps/code/runtime-dependencies.ts index 0d2f45bc70..a1c8e685f7 100644 --- a/apps/code/runtime-dependencies.ts +++ b/apps/code/runtime-dependencies.ts @@ -29,6 +29,12 @@ export const runtimeNativeModules = [ "is-number", ]; +export const runtimeJavaScriptModules = [ + "@playwright/mcp", + "playwright", + "playwright-core", +]; + // The base native modules that must exist when packaging; a missing one is a // broken build, not a warning. before-pack stages these with copyRequiredDep. export const requiredNativeModules = [ @@ -65,6 +71,7 @@ const scopeOf = (name: string) => export const packagedFileGlobs = [ ...runtimeNativeModules, + ...runtimeJavaScriptModules, ...macOnlyNativeModules, ].map((name) => `node_modules/${scopeOf(name)}/**/*`); diff --git a/apps/code/scripts/before-pack.ts b/apps/code/scripts/before-pack.ts index f5529560b9..6bd8dce53b 100644 --- a/apps/code/scripts/before-pack.ts +++ b/apps/code/scripts/before-pack.ts @@ -3,6 +3,7 @@ import path from "node:path"; import { macOnlyNativeModules, requiredNativeModules, + runtimeJavaScriptModules, runtimeNativeModules, watcherPackageFor, } from "../runtime-dependencies"; @@ -72,6 +73,10 @@ export default async function beforePack(context: BeforePackContext) { } } + for (const dep of runtimeJavaScriptModules) { + copyRequiredDep(dep, rootNodeModules, localNodeModules); + } + const watcherPkg = watcherPackageFor(platformName, arch); if (watcherPkg) { copyRequiredDep(watcherPkg, rootNodeModules, localNodeModules); From ea99a7d10a1a7ce2529e45b973da5185918a9b8f Mon Sep 17 00:00:00 2001 From: Richard Solomou Date: Sun, 19 Jul 2026 14:02:21 +0300 Subject: [PATCH 3/3] fix(agent): omit browser image responses Keep browser automation on accessibility snapshots so repeated screenshots cannot inflate model requests past the gateway limit. Generated-By: PostHog Code Task-Id: 64d16b4c-2c25-4e05-ba9d-824311186124 --- .../workspace-server/src/services/agent/browser-use-mcp.test.ts | 1 + packages/workspace-server/src/services/agent/browser-use-mcp.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/workspace-server/src/services/agent/browser-use-mcp.test.ts b/packages/workspace-server/src/services/agent/browser-use-mcp.test.ts index bb9e78735d..2dc6cef119 100644 --- a/packages/workspace-server/src/services/agent/browser-use-mcp.test.ts +++ b/packages/workspace-server/src/services/agent/browser-use-mcp.test.ts @@ -25,5 +25,6 @@ describe("buildBrowserUseServer", () => { env: [{ name: "ELECTRON_RUN_AS_NODE", value: "1" }], }); expect(server?.args[0]).toMatch(/@playwright[\\/]mcp[\\/]cli\.js$/); + expect(server?.args).toContain("omit"); }); }); diff --git a/packages/workspace-server/src/services/agent/browser-use-mcp.ts b/packages/workspace-server/src/services/agent/browser-use-mcp.ts index 40236b6228..904cbb690d 100644 --- a/packages/workspace-server/src/services/agent/browser-use-mcp.ts +++ b/packages/workspace-server/src/services/agent/browser-use-mcp.ts @@ -34,7 +34,7 @@ export function buildBrowserUseServer( "--codegen", "none", "--image-responses", - "allow", + "omit", "--output-mode", "stdout", ],