Skip to content

feat(browser): add @mastra/browser-viewer package with Playwright-based CLI provider#15415

Merged
NikAiyer merged 53 commits into
mainfrom
feat/browser-viewer-cli
Apr 22, 2026
Merged

feat(browser): add @mastra/browser-viewer package with Playwright-based CLI provider#15415
NikAiyer merged 53 commits into
mainfrom
feat/browser-viewer-cli

Conversation

@NikAiyer

@NikAiyer NikAiyer commented Apr 15, 2026

Copy link
Copy Markdown
Member

Summary

Adds package for Playwright-based browser management with CLI providers (agent-browser, browser-use, browse-cli), enabling screencast visualization in Studio.

What's New

@mastra/browser-viewer (new package)

  • BrowserViewer class that launches Chrome via Playwright with --remote-debugging-port
  • Thread-isolated browser sessions with automatic lifecycle management
  • CDP URL exposure for CLI tools to connect as secondary clients
  • Screencast support via direct page-level CDP sessions

@mastra/core changes

  • Workspace.browser configuration for CLI provider integration
  • BrowserCliHandler for automatic CDP URL injection into browser CLI commands
  • MastraBrowser.providerType property ('sdk' | 'cli') and connectToExternalCdp() method
  • Support for external CDP connections when agents provide their own browser endpoints
  • Fixed CWD path duplication in LocalProcessManager

How It Works

import { BrowserViewer } from '@mastra/browser-viewer';

const workspace = new Workspace({
  sandbox: new LocalSandbox({ cwd: './workspace' }),
  browser: new BrowserViewer({ headless: false }),
});

When an agent uses a browser CLI command:

  1. BrowserViewer lazily launches Chrome with remote debugging enabled
  2. BrowserCliHandler automatically injects the CDP URL into CLI commands
  3. CLI tools connect to the existing browser as secondary CDP clients
  4. Screencast streams from the browser to Studio

Supported CLIs:

  • agent-browser — via --cdp <port> and warmup connect command
  • browser-use — via --cdp-url <url>
  • browse — via --ws <url>

External CDP support: If an agent provides their own CDP URL (e.g., browser-use cloud), the system detects it, skips injection, and connects BrowserViewer to the external browser for screencast.

Testing Notes

  • Local sandbox only — Remote sandbox support not yet implemented (would need different CDP discovery)
  • Browser CLIs must be installed separately (npm i -g agent-browser, pipx install browser-use, etc.)

Related

ELI5: This PR adds a small tool that can launch or connect to Chrome and share a WebSocket CDP URL so CLI browser tools can talk to it—letting Studio show live browser screencasts and interact with those CLIs. It also wires core workspace/agent logic so CLI browser commands get auto-configured, warmed up, and lifecycle-managed.

  • New package: @mastra/browser-viewer

    • Playwright-based BrowserViewer that can launch Chrome (with --remote-debugging-port) or connect to an existing CDP endpoint.
    • Thread-isolated or shared browser sessions via BrowserViewerThreadManager; exposes getCdpUrl(threadId?) for external CLI clients.
    • Screencast support using per-page CDP sessions with reconnection handling, URL update events, and input injection (mouse/keyboard) through CDP.
    • Public API exports: BrowserViewer, BrowserViewerThreadManager, BrowserViewerConfig, CLIProvider, and thread-manager config type.
    • README, build/test/lint configs, package.json, ESLint config and changeset for initial release.
  • Core integrations (packages/core)

    • Workspace.browser: optional MastraBrowser provider (must be providerType: 'cli'); browser closed on workspace.destroy; browser not auto-launched on init.
    • MastraBrowser additions:
      • providerType: 'sdk' | 'cli' (default 'sdk').
      • getCdpUrl(threadId?): string | null.
      • launch(threadId?) and isBrowserRunning(threadId?) support thread-aware lifecycles.
      • connectToExternalCdp(cdpUrl, threadId?) hook (base throws by default; BrowserViewer implements).
      • Screencast gating now checks thread-aware running state and thread-session presence.
    • Browser CLI handling:
      • New BrowserCliHandler (singleton browserCliHandler) with CLI detection, external-CDP extraction, CDP injection into chained shell commands, warmup command generation, and warmup lifecycle cleanup tied to browser close.
      • Shell utilities: splitShellCommand and reassembleShellCommand for quote-aware command splitting/rewriting, plus tests.
    • execute-command flow:
      • Commands are analyzed for browser CLIs before sandbox execution.
      • If workspace.browser exists and the CLI is internal (no external CDP), launch browser per-thread as needed, obtain cdpUrl, run warmup commands (best-effort), inject CDP URL and session flags into the command, and register warmup cleanup.
      • If an external CDP URL is detected, attempt browser.connectToExternalCdp(externalCdpUrl, threadId) (errors ignored), and skip injection.
    • Agent/browser interactions:
      • Agent construction now validates explicit config.browser must be providerType === 'sdk' (otherwise AGENT_INVALID_BROWSER_PROVIDER).
      • Agent resolution favors explicitly configured browser; workspace-derived browser is applied unless explicit.
      • Per-request browser context injection now includes providerType and, for CLI providers, cdpUrl when the thread is running.
      • Agents without browsers can use a workspace-provided browser when appropriate.
    • ThreadManager visibility tweak: onSessionCreated/onSessionDestroyed changed to protected for subclass use.
  • Browser-viewer internals

    • Thread manager supports owned browserServer launches (chromium.launchServer), discovers DevToolsActivePort to build CDP URL, connects via Playwright (connectOverCDP) for external endpoints, creates contexts/pages, caches per-page CDP sessions, wires disconnect handlers, and supports clean closeAll.
    • BrowserViewer implements startScreencast to provide a stream backed by per-page CDP sessions and handles reconnects on navigation/new page/close.
    • Input injection routes Input.dispatchMouseEvent/Input.dispatchKeyEvent via per-thread CDP sessions.
  • Commits / runtime fixes and refinements

    • Fixes to active-tab logic and caching of CDP sessions for input injection to reduce overhead.
    • Dedicated screencast CDP session creation and improved external CDP detection (including --cdp patterns).
    • Explicit browser unset/ disconnect detection (Target.targetDestroyed) and propagation of browser close events from ThreadManager to ViewerRegistry.
    • ViewerRegistry enhanced to explicitly stop streams and broadcast browser_closed on screencast errors.
  • Bug fixes

    • LocalProcessManager cwd duplication fixed: relative cwd resolution avoids duplicating workspace path when already workspace-relative.
    • ViewerRegistry now broadcasts browser_closed on screencast stream errors so UI won't remain 'Live' after errors.
  • Tests

    • New and extended tests:
      • BrowserCliHandler end-to-end suite (detection, extraction, injection, warmup lifecycle).
      • Utilities tests for shell splitting and reassembly.
      • execute-command tests validating browser launch, CDP injection, warmup behavior, and external CDP connect handling.
      • Adjusted mocks/serialization to include providerType where needed.
  • Docs & release

    • README for @mastra/browser-viewer and changesets added for @mastra/browser-viewer and @mastra/core; release notes document initial package and core CLI-browser integration.
    • Operational notes: local sandbox only; browser CLIs must be installed separately (e.g., agent-browser, browser-use, browse). Remote sandbox/browser-hosting not included in this PR.
  • Scope & reviewer notes

    • Large new feature surface: major review focus on browser-viewer thread manager/browser-viewer files and core/browser CLI-handling integration. Backwards compatibility preserved: MastraBrowser defaults remain SDK-type and workspace.browser validation prevents misconfiguration.

…ed CLI provider

- Add PlaywrightViewer class that launches Chrome via Playwright
- Add providerType ('sdk' | 'cli') to MastraBrowser for runtime enforcement
- Add runtime check in Agent constructor to ensure SDK providers only
- SDK providers (AgentBrowser, StagehandBrowser) set providerType='sdk'
- PlaywrightViewer sets providerType='cli' for workspace usage

This enables reliable screencast and input injection for CLI tools
(agent-browser, browser-use, browse-cli) by using direct page-level
CDP sessions instead of browser-level flattened protocol.

Co-Authored-By: Mastra Code (anthropic/claude-opus-4-5) <noreply@mastra.ai>
@changeset-bot

changeset-bot Bot commented Apr 15, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: b75af5e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 22 packages
Name Type
@mastra/browser-viewer Minor
@mastra/core Minor
@mastra/server Minor
mastracode Patch
@mastra/mcp-docs-server Patch
@internal/playground Patch
@mastra/client-js Patch
@mastra/opencode Patch
@mastra/longmemeval Patch
@mastra/deployer Minor
@mastra/express Patch
@mastra/fastify Patch
@mastra/hono Patch
@mastra/koa Patch
mastra Patch
@mastra/deployer-cloud Minor
@mastra/deployer-vercel Patch
@mastra/playground-ui Patch
@mastra/react Patch
@mastra/deployer-cloudflare Patch
@mastra/deployer-netlify Patch
create-mastra Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercel Bot commented Apr 15, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
mastra-docs-1.x Skipped Skipped Apr 22, 2026 5:04pm

Request Review

@coderabbitai

coderabbitai Bot commented Apr 15, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

Adds a new @mastra/browser-viewer package and integrates CLI-driven browser/CDP support across core: Playwright-backed per-thread/shared session management, CDP discovery/injection/connection, CLI warmup/injection utilities, workspace wiring, tests, docs, and build/config files.

Changes

Cohort / File(s) Summary
Changesets
\.changeset/bright-windows-glow.md, \.changeset/old-guests-hammer.md, \.changeset/warm-dots-flow.md
Add release notes for new packages/patches: browser-viewer, core browser CLI support, and server screencast fix.
New package: browser-viewer (pkg + config + docs)
browser/browser-viewer/package.json, browser/browser-viewer/tsconfig.json, browser/browser-viewer/tsup.config.ts, browser/browser-viewer/eslint.config.js, browser/browser-viewer/README.md
Add package manifest, build/lint/TS configs and README for @mastra/browser-viewer.
BrowserViewer public API
browser/browser-viewer/src/index.ts, browser/browser-viewer/src/types.ts
Export BrowserViewer, BrowserViewerThreadManager and types (BrowserViewerConfig, CLIProvider, thread-manager config).
BrowserViewer implementation
browser/browser-viewer/src/browser-viewer.ts, browser/browser-viewer/src/thread-manager.ts
Implement BrowserViewer and thread manager: Playwright launch/connect, CDP discovery, per-thread/shared session lifecycle, CDP sessions, screencast streaming, input injection, and teardown.
Core browser surface & threading
packages/core/src/browser/browser.ts, packages/core/src/browser/thread-manager.ts, packages/core/src/browser/processor.ts
Introduce providerType on MastraBrowser, thread-aware launch/isRunning signatures, getCdpUrl/connectToExternalCdp hooks, make ThreadManager callbacks protected, and include cdpUrl/providerType in context injection.
Browser CLI handling
packages/core/src/browser/cli-handler.ts, packages/core/src/browser/index.ts
Add BrowserCliHandler and singleton browserCliHandler for CLI detection, external-CDP extraction, CDP injection and warmup; re-export handler and types.
Workspace & execution integration
packages/core/src/workspace/workspace.ts, packages/core/src/workspace/tools/execute-command.ts, packages/core/src/workspace/sandbox/local-process-manager.ts
Workspace gains optional browser provider (validated/closed on destroy). execute-command pre-processes commands to warm up/inject/connect CDP for detected browser CLIs. Fix cwd resolution to avoid duplicated workspace-relative paths.
Shell utilities & tests
packages/core/src/workspace/sandbox/utils.ts, packages/core/src/workspace/sandbox/utils.test.ts
Add quote-aware splitShellCommand/reassembleShellCommand utilities and comprehensive tests.
Agent & runtime tests
packages/core/src/agent/agent.ts, packages/core/src/agent/__tests__/browser.test.ts, packages/server/src/server/handlers/agent.test.ts
Agent: validate provided browser providerType === 'sdk', add explicit-browser tracking and workspace-derived browser handling; update tests/mocks to include providerType.
Execute-command tests & helpers
packages/core/src/workspace/tools/__tests__/execute-command.test.ts
Add tests and helpers covering browser CLI handling, warmup, injection, external CDP connect behavior and state isolation.
Browser CLI handler tests
packages/core/src/browser/__tests__/cli-handler.test.ts
Comprehensive tests for CLI detection, external CDP extraction, injection, warmup, and cleanup/dedup logic.
Viewer/streaming fix
packages/server/src/server/browser-stream/viewer-registry.ts
On screencast stream error, ensure stream stopped/removed and broadcast { status: 'browser_closed' }.
Misc TS config tweaks
browser/agent-browser/tsconfig.json, browser/stagehand/tsconfig.json
Remove compilerOptions.rootDir overrides.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: introducing a new @mastra/browser-viewer package with Playwright-based CLI provider support, which aligns directly with the changeset contents.
Docstring Coverage ✅ Passed Docstring coverage is 85.71% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/browser-viewer-cli

Comment @coderabbitai help to get the list of available commands and usage tips.

- Add example showing 5 different browser automation approaches
- CLI agents use PlaywrightViewer (integration pending)
- SDK agents use AgentBrowser and StagehandBrowser directly
- Include setup instructions for all CLI tools and skills

Co-Authored-By: Mastra Code (anthropic/claude-opus-4-5) <noreply@mastra.ai>
…nt variables

- Add addEnv() to WorkspaceSandbox interface (optional)
- Implement addEnv() in LocalSandbox
- Export commandExists utility from detect.ts

This enables workspace integrations (like browser providers) to inject
environment variables into the sandbox after creation.

Co-Authored-By: Mastra Code (anthropic/claude-opus-4-5) <noreply@mastra.ai>
@vercel vercel Bot temporarily deployed to Preview – mastra-docs-1.x April 15, 2026 22:30 Inactive
- Add browser config to WorkspaceConfig that accepts MastraBrowser instances
- Add browser getter and initialization to Workspace class
- Inject CDP URL into execute-command for CLI browser commands
- Wire BrowserContextProcessor to get context from workspace browser
- Add getCdpUrl() method to MastraBrowser base class
- Update browser-workspace example agents to use workspace browser config

Co-Authored-By: Mastra Code (anthropic/claude-opus-4-5) <noreply@mastra.ai>
Cleaner name since there's no collision with any existing BrowserViewer class.

Co-Authored-By: Mastra Code (anthropic/claude-opus-4-5) <noreply@mastra.ai>
When getWorkspace() is called, if the agent doesn't have its own browser
configured but the workspace does (CLI approach with BrowserViewer),
copy the workspace's browser to the agent's #browser field.

This allows server-side code that uses agent.browser to work for both:
- SDK providers (AgentBrowser, StagehandBrowser) set directly on agent
- CLI providers (BrowserViewer) set on workspace

Co-Authored-By: Mastra Code (anthropic/claude-opus-4-5) <noreply@mastra.ai>
@vercel vercel Bot temporarily deployed to Preview – mastra-docs-1.x April 15, 2026 22:50 Inactive
- Add BrowserViewerThreadManager for per-thread Chrome instances
- Update BrowserViewer.launch() and getCdpUrl() to accept threadId
- Add thread-aware browser launch in execute-command tool
- Inject CDP URL into CLI commands for workspace browsers
- MastraBrowser base class now supports threadId in launch/getCdpUrl/isBrowserRunning

Co-Authored-By: Mastra Code (anthropic/claude-opus-4-5) <noreply@mastra.ai>
@vercel vercel Bot temporarily deployed to Preview – mastra-docs-1.x April 15, 2026 23:06 Inactive
@vercel vercel Bot temporarily deployed to Preview – mastra-docs-1.x April 16, 2026 01:13 Inactive
@vercel vercel Bot temporarily deployed to Preview – mastra-docs-1.x April 16, 2026 01:13 Inactive
chromium.launch() doesn't expose wsEndpoint(), only launchServer() does.
Updated to use launchServer() and connect() pattern for both thread
and shared browser sessions.
@vercel vercel Bot temporarily deployed to Preview – mastra-docs-1.x April 16, 2026 01:33 Inactive
Playwright's wsEndpoint() returns a Playwright-protocol WebSocket URL,
not a raw Chrome CDP URL. External tools like agent-browser, browser-use,
and browse-cli need the real CDP WebSocket URL to connect.

Chrome writes the debugging port and WebSocket path to a DevToolsActivePort
file in its user data directory. We now read this file to construct the
correct CDP URL (ws://127.0.0.1:PORT/devtools/browser/GUID).

Also unified CDP injection in execute-command.ts:
- All CLIs accept either port or full WebSocket URL
- Updated browse flag from --cdp-url to --ws (per its help output)
- Use full URL for all CLIs for consistency
@vercel vercel Bot temporarily deployed to Preview – mastra-docs-1.x April 16, 2026 16:12 Inactive
@vercel vercel Bot temporarily deployed to Preview – mastra-docs-1.x April 16, 2026 16:13 Inactive
…ctToCdp helper

Extract shared CDP connection logic from createSharedSessionFromCdp and
connectToExternalCdp into a single connectToCdp private method.

Both methods were doing the same work:
- Connect via chromium.connectOverCDP
- Get or create context and page
- Set up CDP session
- Register disconnection handler
- Build session and store it
- Notify callbacks

Now createSharedSessionFromCdp is a one-liner that delegates to connectToCdp,
and connectToExternalCdp handles pre-close logic then delegates.

Reduces thread-manager.ts from 641 to 592 lines (~8% reduction).
@vercel vercel Bot temporarily deployed to Preview – mastra-docs-1.x April 21, 2026 19:31 Inactive
…r logic

BrowserCliHandler tests (50 tests):
- CLI detection (agent-browser, browser-use, browse)
- External CDP flag detection and URL extraction
- CDP injection into single and chained commands
- Shell escaping for URLs and thread IDs
- Warmup state management (isolation by browser/CLI/thread)
- Warmup command generation and deduplication

execute-command browser tests (10 tests):
- Browser launch when CLI detected and not running
- CDP injection into commands
- External CDP detection and connection
- Graceful handling of external CDP connection failure
- Warmup command execution and skip when already warmed

Co-Authored-By: Mastra Code (anthropic/claude-opus-4-5) <noreply@mastra.ai>
@vercel vercel Bot temporarily deployed to Preview – mastra-docs-1.x April 21, 2026 19:45 Inactive
The #setBrowserFromWorkspace method was incorrectly removed while addressing
CodeRabbit feedback. This method is needed for agents to pick up BrowserViewer
from workspace, which is required for screencast to work with CLI browser providers.

The method only sets this.#browser if:
1. Agent doesn't already have a browser (SDK takes precedence)
2. Workspace has a browser configured

Co-Authored-By: Mastra Code (anthropic/claude-opus-4-5) <noreply@mastra.ai>
@vercel vercel Bot temporarily deployed to Preview – mastra-docs-1.x April 21, 2026 21:35 Inactive
@NikAiyer NikAiyer marked this pull request as ready for review April 21, 2026 21:42

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
browser/browser-viewer/src/thread-manager.ts (1)

356-359: Avoid one-shot DevToolsActivePort discovery to reduce startup flakiness.

At Line 356, returning null immediately if the file is not present can intermittently miss a valid CDP URL during Chrome startup. A short bounded retry window would make discovery more reliable.

🔧 Proposed fix
-    if (!existsSync(portFilePath)) {
-      this.logger?.warn?.('DevToolsActivePort file not found');
-      return null;
-    }
+    const deadline = Date.now() + 1500;
+    while (!existsSync(portFilePath) && Date.now() < deadline) {
+      Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 50);
+    }
+    if (!existsSync(portFilePath)) {
+      this.logger?.warn?.('DevToolsActivePort file not found');
+      return null;
+    }

Also applies to: 361-369

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@browser/browser-viewer/src/thread-manager.ts` around lines 356 - 359, The
current DevToolsActivePort discovery in thread-manager.ts returns null
immediately when existsSync(portFilePath) is false, causing flakiness; change
this to a short bounded retry loop: repeatedly check existsSync(portFilePath)
(and then the subsequent read/parse logic) with a small delay (e.g., 50–200ms)
until either the file appears or a total timeout (e.g., 2–5s) elapses, then
proceed to read the file or return null; update both the initial existsSync
check and the follow-up block around lines 361–369 that parses the file so they
use the same retry-with-timeout helper (or inline loop) and preserve logging via
this.logger for each failure/timeout.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@browser/browser-viewer/src/thread-manager.ts`:
- Around line 491-493: The 'disconnected' listener currently closes over raw
threadId which can be wrong for shared-slot sessions; change the callback to use
the computed effectiveThreadId (the same value used when storing sessions, e.g.,
the variable/logic that derives effectiveThreadId from
threadId/DEFAULT_THREAD_ID) before calling this.handleBrowserDisconnected so
lifecycle callbacks always reference the shared/session key; update the
browser.on('disconnected', ...) handler to capture and pass effectiveThreadId
into handleBrowserDisconnected instead of the original threadId.

In `@packages/core/src/agent/agent.ts`:
- Around line 1175-1185: The bug is that `#setBrowserFromWorkspace` assigns
workspace.browser into the agent field this.#browser once and that cached
browser is then preferred in `#execute`, causing stale/incorrect browsers for
dynamic/factory workspaces; fix by removing the persistent assignment and
instead read the browser from the incoming workspace at execution time (use
earlyWorkspace?.browser) or, if you must cache, key the cache by workspace
identity and clear/update this.#browser whenever the current workspace
differs—change `#setBrowserFromWorkspace` to not unconditionally set this.#browser
(or add workspace-aware cache invalidation) and update `#execute` to prefer the
current earlyWorkspace?.browser over any cached this.#browser.

---

Nitpick comments:
In `@browser/browser-viewer/src/thread-manager.ts`:
- Around line 356-359: The current DevToolsActivePort discovery in
thread-manager.ts returns null immediately when existsSync(portFilePath) is
false, causing flakiness; change this to a short bounded retry loop: repeatedly
check existsSync(portFilePath) (and then the subsequent read/parse logic) with a
small delay (e.g., 50–200ms) until either the file appears or a total timeout
(e.g., 2–5s) elapses, then proceed to read the file or return null; update both
the initial existsSync check and the follow-up block around lines 361–369 that
parses the file so they use the same retry-with-timeout helper (or inline loop)
and preserve logging via this.logger for each failure/timeout.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 918a6e4b-e4c3-47b0-acce-94af60d5c8fc

📥 Commits

Reviewing files that changed from the base of the PR and between 02b4833 and ff0f28d.

📒 Files selected for processing (4)
  • browser/browser-viewer/src/thread-manager.ts
  • packages/core/src/agent/agent.ts
  • packages/core/src/browser/__tests__/cli-handler.test.ts
  • packages/core/src/workspace/tools/__tests__/execute-command.test.ts

Comment thread browser/browser-viewer/src/thread-manager.ts Outdated
Comment thread packages/core/src/agent/agent.ts Outdated
thread-manager.ts:
- Add retry loop for DevToolsActivePort file discovery during Chrome startup
- Use effectiveThreadId in connectToCdp disconnect handler for consistent lifecycle

agent.ts:
- Track explicit vs workspace-derived browser with #hasExplicitBrowser flag
- Keep workspace browser in sync per-request instead of caching stale reference
- hasOwnBrowser() now returns true only for explicitly configured SDK browsers

Co-Authored-By: Mastra Code (anthropic/claude-opus-4-5) <noreply@mastra.ai>
@vercel vercel Bot temporarily deployed to Preview – mastra-docs-1.x April 21, 2026 23:04 Inactive

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
packages/core/src/agent/agent.ts (2)

1142-1168: ⚠️ Potential issue | 🟠 Major

Clear the derived browser when no workspace resolves.

This still leaves this.#browser sticky when the current request has no workspace at all. If a factory returns undefined here, or there is no global workspace on a later request, the previously-synced workspace browser survives and #execute() can keep injecting that stale session into browser tools / requestContext.browser.

Suggested fix
       const resolvedWorkspace = await Promise.resolve(result);

       if (!resolvedWorkspace) {
+        if (!this.#hasExplicitBrowser) {
+          this.#browser = undefined;
+        }
         return undefined;
       }
@@
     const globalWorkspace = this.#mastra?.getWorkspace();
+    if (!globalWorkspace && !this.#hasExplicitBrowser) {
+      this.#browser = undefined;
+    }
     if (globalWorkspace) {
       this.#setBrowserFromWorkspace(globalWorkspace);
     }
     return globalWorkspace;

Also applies to: 1177-1185

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/agent/agent.ts` around lines 1142 - 1168, When no workspace
resolves (resolvedWorkspace is undefined and Mastra's getWorkspace() returns
undefined), clear the agent's derived browser to avoid carrying a stale session:
update the branch after the factory-resolution block and the fallback block
(where `#setBrowserFromWorkspace` is called) to explicitly clear this.#browser (or
call a `#clearBrowser` or similar helper) when there is no workspace to set;
ensure the change is applied in both places referenced around
resolvedWorkspace/#setBrowserFromWorkspace and the later fallback (also at the
region noted around 1177-1185) so that requestContext.browser cannot retain a
previous session.

197-197: ⚠️ Potential issue | 🟠 Major

Keep setBrowser() aligned with the new browser invariant.

The SDK-only check now exists only in the constructor, while the public setter still accepts any MastraBrowser and never updates #hasExplicitBrowser. That means agent.setBrowser(browserViewer) bypasses AGENT_INVALID_BROWSER_PROVIDER, and agent.setBrowser(sdkBrowser) can still be treated as non-explicit and later overwritten by workspace.browser.

Also applies to: 330-348, 488-497

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/agent/agent.ts` at line 197, Update setBrowser to mirror
the constructor's browser invariant: validate the incoming MastraBrowser the
same way the constructor does (reject SDK-only providers by throwing
AGENT_INVALID_BROWSER_PROVIDER), assign the browser to this.#browser, and set
the private flag this.#hasExplicitBrowser to true for explicit (non-SDK)
browsers (and false for SDK/browser-from-workspace cases) so calling
agent.setBrowser(...) cannot bypass the invariant or leave `#hasExplicitBrowser`
stale; apply the same change to the other setBrowser-like paths referenced
around the other occurrences so all places (including where workspace.browser
may overwrite) maintain the same validation and flag update.
🧹 Nitpick comments (1)
packages/core/src/browser/__tests__/cli-handler.test.ts (1)

238-244: Consider strengthening the URL escaping assertion.

The current assertion only verifies the beginning of the escaped string. Since the URL contains embedded single quotes ('test'), the test should verify the complete escaped output to ensure these are properly handled.

Suggested improvement
   it('escapes special characters in URLs', () => {
     const specialUrl = "ws://localhost:9222/devtools?foo=bar&baz='test'";
     const result = handler.injectCdpUrl('agent-browser open https://google.com', specialUrl, threadId);

-    // Should be shell-escaped
-    expect(result).toContain("'ws://localhost:9222/devtools?foo=bar&baz=");
+    // Verify the URL is properly shell-escaped
+    // The exact escaping format depends on the implementation (e.g., single quotes with escaped inner quotes)
+    expect(result).toContain('--cdp');
+    expect(result).toMatch(/ws:\/\/localhost:9222\/devtools\?foo=bar&baz=/);
+    // Verify the result doesn't allow shell interpretation of special chars
+    expect(result).not.toMatch(/[^'"]&[^'"]/); // Unquoted ampersand would be interpreted by shell
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/browser/__tests__/cli-handler.test.ts` around lines 238 -
244, Update the test "escapes special characters in URLs" to assert the full
escaped output from handler.injectCdpUrl rather than only the prefix: call
handler.injectCdpUrl with the specialUrl and verify the returned string exactly
contains the correctly shell-escaped form of the entire URL (including the
embedded single-quoted segment "'test'") so that the escaping logic in
injectCdpUrl is fully validated; locate the test and the injectCdpUrl invocation
to replace the partial expect(result).toContain(...) with an assertion that
compares or matches the complete expected escaped string.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/core/src/workspace/tools/__tests__/execute-command.test.ts`:
- Around line 551-554: The tests call vi.clearAllMocks() in the beforeEach but
do not clear the singleton warmup state held by browserCliHandler, causing test
leakage; update the beforeEach to also call browserCliHandler.resetWarmup() (or
the module's explicit clear/reset method) to reset the CLI warmup cache between
tests so warmup assertions remain isolated and order-independent.

---

Duplicate comments:
In `@packages/core/src/agent/agent.ts`:
- Around line 1142-1168: When no workspace resolves (resolvedWorkspace is
undefined and Mastra's getWorkspace() returns undefined), clear the agent's
derived browser to avoid carrying a stale session: update the branch after the
factory-resolution block and the fallback block (where `#setBrowserFromWorkspace`
is called) to explicitly clear this.#browser (or call a `#clearBrowser` or similar
helper) when there is no workspace to set; ensure the change is applied in both
places referenced around resolvedWorkspace/#setBrowserFromWorkspace and the
later fallback (also at the region noted around 1177-1185) so that
requestContext.browser cannot retain a previous session.
- Line 197: Update setBrowser to mirror the constructor's browser invariant:
validate the incoming MastraBrowser the same way the constructor does (reject
SDK-only providers by throwing AGENT_INVALID_BROWSER_PROVIDER), assign the
browser to this.#browser, and set the private flag this.#hasExplicitBrowser to
true for explicit (non-SDK) browsers (and false for SDK/browser-from-workspace
cases) so calling agent.setBrowser(...) cannot bypass the invariant or leave
`#hasExplicitBrowser` stale; apply the same change to the other setBrowser-like
paths referenced around the other occurrences so all places (including where
workspace.browser may overwrite) maintain the same validation and flag update.

---

Nitpick comments:
In `@packages/core/src/browser/__tests__/cli-handler.test.ts`:
- Around line 238-244: Update the test "escapes special characters in URLs" to
assert the full escaped output from handler.injectCdpUrl rather than only the
prefix: call handler.injectCdpUrl with the specialUrl and verify the returned
string exactly contains the correctly shell-escaped form of the entire URL
(including the embedded single-quoted segment "'test'") so that the escaping
logic in injectCdpUrl is fully validated; locate the test and the injectCdpUrl
invocation to replace the partial expect(result).toContain(...) with an
assertion that compares or matches the complete expected escaped string.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 3a0cb793-260d-45a8-890a-d0c3ab27ce6f

📥 Commits

Reviewing files that changed from the base of the PR and between 02b4833 and 13fcef7.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (4)
  • browser/browser-viewer/src/thread-manager.ts
  • packages/core/src/agent/agent.ts
  • packages/core/src/browser/__tests__/cli-handler.test.ts
  • packages/core/src/workspace/tools/__tests__/execute-command.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • browser/browser-viewer/src/thread-manager.ts

Comment thread packages/core/src/workspace/tools/__tests__/execute-command.test.ts
@vercel vercel Bot temporarily deployed to Preview – mastra-docs-1.x April 22, 2026 16:39 Inactive
Previously the ViewerRegistry only logged screencast errors but didn't
notify the UI, leaving the screencast panel showing 'Live' after
browser close.

Co-Authored-By: Mastra Code (anthropic/claude-opus-4-5) <noreply@mastra.ai>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@browser/browser-viewer/src/browser-viewer.ts`:
- Around line 241-250: When constructing BrowserState in browser-viewer.ts,
don't hard-code the active tab to index 0; use
BrowserViewerThreadManager.getActivePageForThread() to find the active Page and
compute activeTabIndex and each tab's isActive accordingly: call
getActivePageForThread() to obtain the active Page (or its GUID/identity),
iterate context.pages() to build tabs using page.url() and set isActive = (page
=== activePage) (or compare identities), and set activeTabIndex to the index of
that active page (fallback to 0 only if no active page found). Update the tabs
creation code and the returned activeTabIndex to reflect that computed index.
- Around line 264-293: The provider.getCdpSession implementation re-selects
pages and calls context.newCDPSession directly, bypassing the thread manager's
existing active-page resolution and race handling; replace the manual selection
with the thread-manager helper (e.g., call
this.threadManager.getCdpSession(threadId) or the thread-manager method that
returns an existing/ready CDP session for a thread) and return a wrapper around
that session implementing send/on/off, throwing if the thread-manager helper
returns null/throws so the same disconnect/page-close cleanup logic is used as
in thread-manager.ts.
- Around line 377-392: The injectMouseEvent and injectKeyboardEvent
implementations call this.threadManager.getCdpSessionForThread(...) which opens
a new CDP session per call; change these methods to reuse a thread-scoped
persistent session instead of creating a session per event (or, if the thread
manager provides a persistent accessor, call that), e.g. obtain and cache a
single CDP session per thread (or request a long-lived session from
BrowserViewerThreadManager) and reuse it for subsequent dispatches in
injectMouseEvent and injectKeyboardEvent, or ensure any transient session
returned by getCdpSessionForThread is explicitly closed/retired after dispatch
to avoid accumulating sessions.

In `@packages/core/src/agent/agent.ts`:
- Around line 488-493: The setBrowser method currently only sets this.#browser
and flips this.#hasExplicitBrowser when a truthy browser is passed, so calling
setBrowser(undefined) fails to persist an explicit "disabled" override and
getWorkspace() can repopulate `#browser`; change setBrowser(browser: MastraBrowser
| undefined) so that whenever it is called with undefined it still sets
this.#hasExplicitBrowser = true (i.e., treat an explicit undefined as an
explicit override), while preserving the existing behavior for non-undefined
browsers; update any comments/tests relying on explicit-ness of
hasOwnBrowser()/#hasExplicitBrowser accordingly to reflect that an explicit
disable is recorded.

In `@packages/core/src/browser/cli-handler.ts`:
- Around line 44-46: The external CDP detection only matches --cdp values that
start with ws:// or wss://; update the regexes externalCdpPattern and
externalCdpExtractor to also recognize numeric ports and host:port forms (e.g.,
9222, localhost:9222, 127.0.0.1:9222) so analyzeCommand treats these as external
CDP targets; change externalCdpPattern to allow --cdp followed by either wss?://
or an IPv4/hostname with optional :port or a bare port number, and change
externalCdpExtractor to capture the full host[:port] or bare port into the same
capture group (preserving existing wss?:// capture), ensuring downstream code
that uses the extractor (analyzeCommand and any code paths around the
launch/warmup logic) can handle the extracted host/port string.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3e02d3f8-cdd7-4fde-99dc-73f63542f334

📥 Commits

Reviewing files that changed from the base of the PR and between 13fcef7 and bfb2d11.

📒 Files selected for processing (6)
  • browser/browser-viewer/src/browser-viewer.ts
  • browser/browser-viewer/src/thread-manager.ts
  • packages/core/src/agent/agent.ts
  • packages/core/src/browser/__tests__/cli-handler.test.ts
  • packages/core/src/browser/cli-handler.ts
  • packages/core/src/workspace/tools/__tests__/execute-command.test.ts
✅ Files skipped from review due to trivial changes (1)
  • browser/browser-viewer/src/thread-manager.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/core/src/browser/tests/cli-handler.test.ts
  • packages/core/src/workspace/tools/tests/execute-command.test.ts

Comment on lines +241 to +250
const pages = context.pages();
const tabs: BrowserTabState[] = pages.map((page, index) => ({
url: page.url(),
title: '', // Would need async call to get title
isActive: index === 0,
}));

return {
tabs,
activeTabIndex: 0,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use the actual active page when building BrowserState.

isActive and activeTabIndex are hard-coded to the first page, so multi-tab sessions will report the wrong active tab after a tab switch. BrowserViewerThreadManager.getActivePageForThread() is already available and should drive both values here.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@browser/browser-viewer/src/browser-viewer.ts` around lines 241 - 250, When
constructing BrowserState in browser-viewer.ts, don't hard-code the active tab
to index 0; use BrowserViewerThreadManager.getActivePageForThread() to find the
active Page and compute activeTabIndex and each tab's isActive accordingly: call
getActivePageForThread() to obtain the active Page (or its GUID/identity),
iterate context.pages() to build tabs using page.url() and set isActive = (page
=== activePage) (or compare identities), and set activeTabIndex to the index of
that active page (fallback to 0 only if no active page found). Update the tabs
creation code and the returned activeTabIndex to reflect that computed index.

Comment thread browser/browser-viewer/src/browser-viewer.ts
Comment on lines +377 to +392
override async injectMouseEvent(params: MouseEventParams, threadId?: string): Promise<void> {
const cdpSession = await this.threadManager.getCdpSessionForThread(threadId ?? this.getCurrentThread());
if (!cdpSession) {
throw new Error('CDP session not available for mouse injection');
}

await cdpSession.send('Input.dispatchMouseEvent', params);
}

override async injectKeyboardEvent(params: KeyboardEventParams, threadId?: string): Promise<void> {
const cdpSession = await this.threadManager.getCdpSessionForThread(threadId ?? this.getCurrentThread());
if (!cdpSession) {
throw new Error('CDP session not available for keyboard injection');
}

await cdpSession.send('Input.dispatchKeyEvent', params);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don’t open a fresh CDP session for every input event.

BrowserViewerThreadManager.getCdpSessionForThread() creates a new CDP session on each call, so mouse moves and keyboard input turn into a session-per-event hot path here. That is going to accumulate avoidable CDP sessions and add a lot of overhead under live control; these methods should reuse a thread-scoped session or explicitly retire the transient session after dispatch.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@browser/browser-viewer/src/browser-viewer.ts` around lines 377 - 392, The
injectMouseEvent and injectKeyboardEvent implementations call
this.threadManager.getCdpSessionForThread(...) which opens a new CDP session per
call; change these methods to reuse a thread-scoped persistent session instead
of creating a session per event (or, if the thread manager provides a persistent
accessor, call that), e.g. obtain and cache a single CDP session per thread (or
request a long-lived session from BrowserViewerThreadManager) and reuse it for
subsequent dispatches in injectMouseEvent and injectKeyboardEvent, or ensure any
transient session returned by getCdpSessionForThread is explicitly
closed/retired after dispatch to avoid accumulating sessions.

Comment thread packages/core/src/agent/agent.ts Outdated
Comment on lines +44 to +46
externalCdpPattern: /(?:\bconnect\s+["']?wss?:\/\/|--cdp\s+["']?wss?:\/\/)/,
// Extract URL from: connect "wss://..." or --cdp "wss://..."
externalCdpExtractor: /(?:\bconnect|--cdp)\s+["']?(wss?:\/\/[^\s"']+)["']?/,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

agent-browser --cdp <port> is not recognized as external CDP and can trigger the wrong execution path.

Line 44 only treats --cdp as external when the value starts with ws:///wss://. That misses valid port-style usage (e.g., --cdp 9222), so analyzeCommand may classify the command as non-external and unnecessarily run local launch/warmup logic.

💡 Proposed fix
   'agent-browser': {
@@
-    externalCdpPattern: /(?:\bconnect\s+["']?wss?:\/\/|--cdp\s+["']?wss?:\/\/)/,
+    externalCdpPattern: /(?:\bconnect\s+["']?wss?:\/\/|--cdp\s+["']?\S+)/,

Also applies to: 127-145, 293-295

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/browser/cli-handler.ts` around lines 44 - 46, The external
CDP detection only matches --cdp values that start with ws:// or wss://; update
the regexes externalCdpPattern and externalCdpExtractor to also recognize
numeric ports and host:port forms (e.g., 9222, localhost:9222, 127.0.0.1:9222)
so analyzeCommand treats these as external CDP targets; change
externalCdpPattern to allow --cdp followed by either wss?:// or an IPv4/hostname
with optional :port or a bare port number, and change externalCdpExtractor to
capture the full host[:port] or bare port into the same capture group
(preserving existing wss?:// capture), ensuring downstream code that uses the
extractor (analyzeCommand and any code paths around the launch/warmup logic) can
handle the extracted host/port string.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/server/src/server/browser-stream/viewer-registry.ts`:
- Around line 402-409: The error handler on stream doesn't explicitly stop the
errored stream, risking leaked CDP/session resources; inside the
stream.on('error', ...) block (where you check this.screencasts.get(viewerKey)
!== currentStream, log the error, then this.screencasts.delete(viewerKey) and
this.broadcastStatus(viewerKey, { status: 'browser_closed' })), explicitly tear
down the currentStream before deleting it: invoke the stream's cleanup API (e.g.
call a stop/close/dispose method if available on currentStream), remove any
remaining listeners (e.g. removeAllListeners or equivalent), then proceed to
delete from this.screencasts and call this.broadcastStatus to ensure no orphaned
resources or handlers remain.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 752c31b6-c7be-468c-af46-894016d05e29

📥 Commits

Reviewing files that changed from the base of the PR and between bfb2d11 and e624839.

📒 Files selected for processing (2)
  • .changeset/warm-dots-flow.md
  • packages/server/src/server/browser-stream/viewer-registry.ts
✅ Files skipped from review due to trivial changes (1)
  • .changeset/warm-dots-flow.md

Comment thread packages/server/src/server/browser-stream/viewer-registry.ts
- Fix BrowserState active tab logic to use actual page count
- Cache CDP sessions for input injection to reduce overhead
- Use dedicated method for screencast CDP session creation
- Update setBrowser(undefined) to mark browser as explicitly set
- Expand external CDP detection to recognize --cdp <port> pattern
- Fix viewer-registry error handler to explicitly stop stream
- Add browser disconnect detection via Target.targetDestroyed
- Propagate browser close events from ThreadManager to ViewerRegistry

Co-Authored-By: Mastra Code (anthropic/claude-opus-4-5) <noreply@mastra.ai>
@vercel vercel Bot temporarily deployed to Preview – mastra-docs-1.x April 22, 2026 17:04 Inactive

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
browser/browser-viewer/src/thread-manager.ts (1)

326-367: CDP session caching is a reasonable approach but has an edge case.

The caching keyed by pageUrl (line 349) addresses the previous concern about stale sessions. However, if multiple tabs have the same URL, the cache may return a session for the wrong tab. This is a minor edge case since the session will still target a valid page with the expected URL.

Consider using a more robust page identity (like Playwright's internal page ID) if this becomes an issue in practice.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@browser/browser-viewer/src/thread-manager.ts` around lines 326 - 367, The
cache currently keys CDP sessions by pageUrl in getCdpSessionForThread, which
can return the wrong tab when multiple pages share the same URL; update
inputCdpSessions to store and compare a stable unique page identifier from the
Playwright Page (e.g., the page's internal GUID/id accessible on the Page object
or main frame) instead of pageUrl, fall back to URL if no id is available, and
update all uses in getCdpSessionForThread (where activePage, cached, pageUrl,
and newCDPSession are referenced) to check cached.pageId === currentPageId
before reusing the session and to cache { session, pageId } when creating a new
CDP session.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@browser/browser-viewer/src/thread-manager.ts`:
- Around line 326-367: The cache currently keys CDP sessions by pageUrl in
getCdpSessionForThread, which can return the wrong tab when multiple pages share
the same URL; update inputCdpSessions to store and compare a stable unique page
identifier from the Playwright Page (e.g., the page's internal GUID/id
accessible on the Page object or main frame) instead of pageUrl, fall back to
URL if no id is available, and update all uses in getCdpSessionForThread (where
activePage, cached, pageUrl, and newCDPSession are referenced) to check
cached.pageId === currentPageId before reusing the session and to cache {
session, pageId } when creating a new CDP session.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c69d64c1-a5ec-4201-9e48-9b798051e4a4

📥 Commits

Reviewing files that changed from the base of the PR and between e624839 and b75af5e.

📒 Files selected for processing (6)
  • browser/browser-viewer/src/browser-viewer.ts
  • browser/browser-viewer/src/thread-manager.ts
  • packages/core/src/agent/agent.ts
  • packages/core/src/browser/__tests__/cli-handler.test.ts
  • packages/core/src/browser/cli-handler.ts
  • packages/server/src/server/browser-stream/viewer-registry.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/server/src/server/browser-stream/viewer-registry.ts
  • packages/core/src/agent/agent.ts

@NikAiyer NikAiyer merged commit 0a0aa94 into main Apr 22, 2026
48 checks passed
@NikAiyer NikAiyer deleted the feat/browser-viewer-cli branch April 22, 2026 17:28
abhiaiyer91 pushed a commit that referenced this pull request Apr 22, 2026
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and publish to npm
yourself or [setup this action to publish
automatically](https://github.com/changesets/action#with-publishing). If
you're not ready to do a release yet, that's fine, whenever you add more
changesets to main, this PR will be updated.

⚠️⚠️⚠️⚠️⚠️⚠️

`main` is currently in **pre mode** so this branch has prereleases
rather than normal releases. If you want to exit prereleases, run
`changeset pre exit` on `main`.

⚠️⚠️⚠️⚠️⚠️⚠️

# Releases
## @mastra/browser-viewer@0.1.0-alpha.0

### Minor Changes

- Initial release of @mastra/browser-viewer
([#15415](#15415))

Playwright-based browser viewer for CLI providers that enables
screencast visualization in Studio. Supports thread-isolated browser
sessions and automatic CDP connection management.

    ```typescript
    import { BrowserViewer } from '@mastra/browser-viewer';

    const workspace = new Workspace({
      sandbox: new LocalSandbox({ cwd: './workspace' }),
      browser: new BrowserViewer({
        cli: 'agent-browser',
        headless: false,
      }),
    });
    ```

### Patch Changes

- Updated dependencies
\[[`0a0aa94`](0a0aa94),
[`01a7d51`](01a7d51)]:
    -   @mastra/core@1.27.0-alpha.1

## @mastra/core@1.27.0-alpha.1

### Minor Changes

- Added support for CLI-driven browser automation with screencast
support in `@mastra/core`, including automatic CDP injection for browser
CLIs. ([#15415](#15415))

Fixed local process spawning so workspace-relative `cwd` values no
longer get duplicated.

### Patch Changes

- Added opt-in temporal-gap markers for observational memory. When
enabled via `observationalMemory.temporalMarkers: true`, the agent
receives a `<system-reminder type="temporal-gap">` before any user
message that arrives more than 10 minutes after the previous one, so it
can anchor responses in real elapsed time. Markers are persisted,
surfaced to the observer, and rendered by the MastraCode TUI on reload.
([#15605](#15605))

## @mastra/memory@1.17.0-alpha.0

### Minor Changes

- Added opt-in temporal-gap markers for observational memory. When
enabled via `observationalMemory.temporalMarkers: true`, the agent
receives a `<system-reminder type="temporal-gap">` before any user
message that arrives more than 10 minutes after the previous one, so it
can anchor responses in real elapsed time. Markers are persisted,
surfaced to the observer, and rendered by the MastraCode TUI on reload.
([#15605](#15605))

### Patch Changes

- Fixed observer agent truncation that could cut UTF-16 surrogate pairs
in half when formatting messages, tool results, or observation lines
with emoji or other astral-plane characters. This produced lone
surrogates that strict JSON parsers (including Anthropic's) reject with
errors like `no low surrogate in string`, causing observer runs to fail.
([#15634](#15634))

- Updated dependencies
\[[`0a0aa94`](0a0aa94),
[`01a7d51`](01a7d51)]:
    -   @mastra/core@1.27.0-alpha.1

## @mastra/client-js@1.14.1-alpha.1

### Patch Changes

- Updated dependencies
\[[`0a0aa94`](0a0aa94),
[`01a7d51`](01a7d51)]:
    -   @mastra/core@1.27.0-alpha.1

## @mastra/react@0.2.28-alpha.1

### Patch Changes

- Updated dependencies
\[[`0a0aa94`](0a0aa94),
[`01a7d51`](01a7d51)]:
    -   @mastra/core@1.27.0-alpha.1
    -   @mastra/client-js@1.14.1-alpha.1

## @mastra/deployer-cloud@1.27.0-alpha.1

### Patch Changes

- Updated dependencies
\[[`0a0aa94`](0a0aa94),
[`01a7d51`](01a7d51)]:
    -   @mastra/core@1.27.0-alpha.1
    -   @mastra/deployer@1.27.0-alpha.1

## @mastra/deployer-cloudflare@1.1.25-alpha.1

### Patch Changes

- Updated dependencies
\[[`0a0aa94`](0a0aa94),
[`01a7d51`](01a7d51)]:
    -   @mastra/core@1.27.0-alpha.1
    -   @mastra/deployer@1.27.0-alpha.1

## @mastra/deployer-netlify@1.1.1-alpha.1

### Patch Changes

- Updated dependencies
\[[`0a0aa94`](0a0aa94),
[`01a7d51`](01a7d51)]:
    -   @mastra/core@1.27.0-alpha.1
    -   @mastra/deployer@1.27.0-alpha.1

## @mastra/deployer-vercel@1.1.19-alpha.1

### Patch Changes

- Updated dependencies
\[[`0a0aa94`](0a0aa94),
[`01a7d51`](01a7d51)]:
    -   @mastra/core@1.27.0-alpha.1
    -   @mastra/deployer@1.27.0-alpha.1

## @mastra/longmemeval@1.0.30-alpha.1

### Patch Changes

- Updated dependencies
\[[`0a0aa94`](0a0aa94),
[`01a7d51`](01a7d51),
[`6e9ab07`](6e9ab07)]:
    -   @mastra/core@1.27.0-alpha.1
    -   @mastra/memory@1.17.0-alpha.0

## @mastra/opencode@0.0.27-alpha.1

### Patch Changes

- Updated dependencies
\[[`0a0aa94`](0a0aa94),
[`01a7d51`](01a7d51),
[`6e9ab07`](6e9ab07)]:
    -   @mastra/core@1.27.0-alpha.1
    -   @mastra/memory@1.17.0-alpha.0

## @mastra/tavily@1.0.1-alpha.0

### Patch Changes

- Fixed runtime `ERR_MODULE_NOT_FOUND` for `@tavily/core` by making it a
direct dependency. Consumers no longer need to install `@tavily/core`
manually. ([#15628](#15628))

- Updated dependencies
\[[`0a0aa94`](0a0aa94),
[`01a7d51`](01a7d51)]:
    -   @mastra/core@1.27.0-alpha.1

## mastracode@0.15.1-alpha.1

### Patch Changes

- Added opt-in temporal-gap markers for observational memory. When
enabled via `observationalMemory.temporalMarkers: true`, the agent
receives a `<system-reminder type="temporal-gap">` before any user
message that arrives more than 10 minutes after the previous one, so it
can anchor responses in real elapsed time. Markers are persisted,
surfaced to the observer, and rendered by the MastraCode TUI on reload.
([#15605](#15605))

- Improved model ID display in the Mastra Code TUI status line.
Fireworks model IDs are now shown in compact form (e.g.
fireworks/kimi-k2.6 instead of the full
fireworks-ai/accounts/fireworks/models/... path). Version separators in
model names are also normalized (e.g. kimi-k2p6 is displayed as
kimi-k2.6). ([#15631](#15631))

- Updated dependencies
\[[`0a0aa94`](0a0aa94),
[`96f6fb2`](96f6fb2),
[`01a7d51`](01a7d51),
[`6e9ab07`](6e9ab07)]:
    -   @mastra/core@1.27.0-alpha.1
    -   @mastra/tavily@1.0.1-alpha.0
    -   @mastra/memory@1.17.0-alpha.0

## @mastra/agent-builder@1.0.28-alpha.1

### Patch Changes

- Updated dependencies
\[[`0a0aa94`](0a0aa94),
[`01a7d51`](01a7d51),
[`6e9ab07`](6e9ab07)]:
    -   @mastra/core@1.27.0-alpha.1
    -   @mastra/memory@1.17.0-alpha.0

## mastra@1.6.2-alpha.1

### Patch Changes

- Improved server deploy errors: the CLI now shows the platform API
message (for example billing or payment errors) instead of only a
generic status code.
([#15459](#15459))

- Improved env file handling for `mastra server deploy` and `mastra
studio deploy`. Deploy now selects a single env file instead of silently
merging multiple files. When multiple `.env` files exist, you are
prompted to choose one. Added `--env-file` to specify the file directly
(e.g. `--env-file .env.staging`), which is required in non-interactive
mode when multiple env files are present.
([#15641](#15641))

- Updated dependencies
\[[`0a0aa94`](0a0aa94),
[`01a7d51`](01a7d51)]:
    -   @mastra/core@1.27.0-alpha.1
    -   @mastra/deployer@1.27.0-alpha.1

## @mastra/deployer@1.27.0-alpha.1

### Patch Changes

- Updated dependencies
\[[`2a87046`](2a87046),
[`0a0aa94`](0a0aa94),
[`01a7d51`](01a7d51),
[`0a0aa94`](0a0aa94)]:
    -   @mastra/server@1.27.0-alpha.1
    -   @mastra/core@1.27.0-alpha.1

## @mastra/editor@0.7.18-alpha.0

### Patch Changes

- Updated dependencies
\[[`0a0aa94`](0a0aa94),
[`01a7d51`](01a7d51),
[`6e9ab07`](6e9ab07)]:
    -   @mastra/core@1.27.0-alpha.1
    -   @mastra/memory@1.17.0-alpha.0

## @mastra/mcp-docs-server@1.1.27-alpha.2

### Patch Changes

- Updated dependencies
\[[`0a0aa94`](0a0aa94),
[`01a7d51`](01a7d51)]:
    -   @mastra/core@1.27.0-alpha.1

## @mastra/playground-ui@23.0.1-alpha.1

### Patch Changes

- Updated dependencies
\[[`0a0aa94`](0a0aa94),
[`01a7d51`](01a7d51)]:
    -   @mastra/core@1.27.0-alpha.1
    -   @mastra/client-js@1.14.1-alpha.1
    -   @mastra/react@0.2.28-alpha.1

## @mastra/server@1.27.0-alpha.1

### Patch Changes

- Forward `requestContext` from the `/approve-tool-call`,
`/decline-tool-call`, `/approve-tool-call-generate` and
`/decline-tool-call-generate` REST handlers to
`agent.approveToolCall(...)` / `declineToolCall(...)` /
`approveToolCallGenerate(...)` / `declineToolCallGenerate(...)`.
([#15620](#15620))

Previously `requestContext` was destructured from the handler arguments
but never passed through. On resume, `dynamicInstructions` ran with
`requestContext: undefined`, so any value placed on the per-request
`RequestContext` by upstream middleware (or by `body.requestContext`
auto-merge) was lost for the rest of the turn. Agents whose prompt
assembly depends on request-scoped data (e.g. read-only state from the
frontend) produced blank or placeholder responses after the user
approved a HITL tool call. Other agent entry points (`stream`,
`generate`) already forwarded `requestContext` correctly; this brings
the approval routes in line.

- Fixed screencast panel staying "Live" after browser closes due to an
error. The `ViewerRegistry` now broadcasts `browser_closed` status when
a screencast stream emits an error, not just when it stops cleanly.
([#15415](#15415))

- Updated dependencies
\[[`0a0aa94`](0a0aa94),
[`01a7d51`](01a7d51)]:
    -   @mastra/core@1.27.0-alpha.1

## @mastra/express@1.3.11-alpha.1

### Patch Changes

- Updated dependencies
\[[`2a87046`](2a87046),
[`0a0aa94`](0a0aa94),
[`01a7d51`](01a7d51),
[`0a0aa94`](0a0aa94)]:
    -   @mastra/server@1.27.0-alpha.1
    -   @mastra/core@1.27.0-alpha.1

## @mastra/fastify@1.3.11-alpha.1

### Patch Changes

- Fix custom route handlers on the Fastify adapter silently overwriting
request-body fields named `tools` (e.g. `POST /stored/agents`, `POST
/stored/workspaces`). The adapter now exposes registered tools as
`registeredTools` in handler params, matching the Express and Hono
adapters and the `@mastra/server` handler contract.
([#15635](#15635))

- Updated dependencies
\[[`2a87046`](2a87046),
[`0a0aa94`](0a0aa94),
[`01a7d51`](01a7d51),
[`0a0aa94`](0a0aa94)]:
    -   @mastra/server@1.27.0-alpha.1
    -   @mastra/core@1.27.0-alpha.1

## @mastra/hono@1.4.6-alpha.1

### Patch Changes

- Updated dependencies
\[[`2a87046`](2a87046),
[`0a0aa94`](0a0aa94),
[`01a7d51`](01a7d51),
[`0a0aa94`](0a0aa94)]:
    -   @mastra/server@1.27.0-alpha.1
    -   @mastra/core@1.27.0-alpha.1

## @mastra/koa@1.4.11-alpha.1

### Patch Changes

- Updated dependencies
\[[`2a87046`](2a87046),
[`0a0aa94`](0a0aa94),
[`01a7d51`](01a7d51),
[`0a0aa94`](0a0aa94)]:
    -   @mastra/server@1.27.0-alpha.1
    -   @mastra/core@1.27.0-alpha.1

## @mastra/clickhouse@1.5.1-alpha.1

### Patch Changes

- Improved ClickHouse v-next observability initialization errors to
include the underlying ClickHouse message in the standard error text.
This makes init failures actionable in loggers that only print
`error.message`.
([#15588](#15588))

- Updated dependencies
\[[`0a0aa94`](0a0aa94),
[`01a7d51`](01a7d51)]:
    -   @mastra/core@1.27.0-alpha.1

## create-mastra@1.6.2-alpha.1



## @internal/playground@1.6.2-alpha.1

### Patch Changes

- Updated dependencies
\[[`0a0aa94`](0a0aa94),
[`01a7d51`](01a7d51)]:
    -   @mastra/core@1.27.0-alpha.1
    -   @mastra/client-js@1.14.1-alpha.1
    -   @mastra/ai-sdk@1.4.1
    -   @mastra/react@0.2.28-alpha.1
    -   @mastra/playground-ui@23.0.1-alpha.1

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants