Skip to content

[feat]: add clipboard API#2171

Open
seanmcguire12 wants to merge 9 commits into
mainfrom
add-clipboard-api
Open

[feat]: add clipboard API#2171
seanmcguire12 wants to merge 9 commits into
mainfrom
add-clipboard-api

Conversation

@seanmcguire12
Copy link
Copy Markdown
Member

@seanmcguire12 seanmcguire12 commented May 29, 2026

why

  • to give users a first class primitive for basic clipboard functionality: cut, copy, paste, read, write, & clear

what changed

  • added context.clipboard which has the following methods:
    • readText, writeText, clear, paste, copy, & cut
  • clipboard read/write runs in the active page context, with browser permissions granted for the page origin
  • paste/copy/cut focus the target page before dispatching keyboard shortcuts
  • clipboard methods use context.activePage() by default, but callers can pass { page } to target a specific Page
  • added an integration spec for text clipboard behavior across active & explicit pages

test plan

  • added clipboard.spec.ts to cover readText, writeText, paste, copy, & cut
  • the spec asserts the exact clipboard & textarea values for pasted, copied, cut, & read text
  • also verifies clipboard actions use the active page by default
  • also verifies passing { page } targets that specific Page, even when another page is active

Summary by cubic

Adds a native clipboard API to the context for read/write and cut/copy/paste. Defaults to the active page, handles permissions, focuses the target page, and logs actions via FlowLogger without duplicates.

  • New Features

    • New context.clipboard with readText, writeText, clear, paste, copy, and cut.
    • Grants origin clipboard permissions and brings the page to front before sending shortcuts; emits FlowLogger events for each action.
    • Uses context.activePage() by default; pass { page } to target a specific Page. paste supports a shortcut override.
    • Exports public types (BrowserClipboard, options), and includes an example and integration tests verifying active vs explicit page behavior.
  • Bug Fixes

    • Prevents duplicate FlowLogger events for clipboard actions.

Written for commit d7103c2. Summary will update on new commits.

Review in cubic

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 29, 2026

🦋 Changeset detected

Latest commit: d7103c2

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

This PR includes changesets to release 4 packages
Name Type
@browserbasehq/stagehand Minor
@browserbasehq/browse-cli Patch
@browserbasehq/stagehand-evals Patch
@browserbasehq/stagehand-server-v3 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

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

2 issues found across 7 files

Confidence score: 3/5

  • There is some moderate merge risk because both reported issues are medium severity (6/10) with high confidence, including a concrete cleanup gap and potential instrumentation inconsistency.
  • The most impactful issue is in packages/core/examples/clipboard.ts: missing stagehand.close() in a try/finally can leak Browserbase sessions when users follow this example, which can cause resource/cost problems.
  • In packages/core/lib/v3/types/public/index.ts, new public clipboard understudy methods appear not to be fully wired through flowLogger, creating observability/tracing gaps for those APIs.
  • Pay close attention to packages/core/examples/clipboard.ts and packages/core/lib/v3/types/public/index.ts - ensure session cleanup is guaranteed and new public methods are consistently instrumented.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/core/lib/v3/types/public/index.ts">

<violation number="1" location="packages/core/lib/v3/types/public/index.ts:8">
P2: Custom agent: **Ensure all public methods added to the stagehand class, agent, or understudy (page, locator, etc.) interfaces are properly instrumented with the flowLogger**

New `clipboard` public understudy methods lack flowLogger instrumentation</violation>
</file>
Architecture diagram
sequenceDiagram
    participant Client as User Code / Test
    participant Ctx as V3Context
    participant Clip as ContextClipboard
    participant Page as Page (target)
    participant CDP as CDP Session
    participant Browser as Browser Process

    Note over Client,Browser: context.clipboard - Write / Read Text Flow
    
    Client->>Ctx: clipboard.writeText(text, options?)
    Ctx->>Clip: resolvePage(options?.page)
    alt Explicit page provided
        Clip->>Ctx: params.resolvePage(page)
        Ctx-->>Clip: specific Page
    else Default to active page
        Clip->>Ctx: params.resolvePage(undefined)
        Ctx->>Ctx: activePage()
        Ctx-->>Clip: active Page
    end

    Clip->>Page: ensurePageFocused(page)
    Page->>Page: context.setActivePage(this)
    Page->>CDP: Page.bringToFront
    Page->>CDP: Runtime.evaluate("window.focus()")

    Clip->>Page: grantClipboardPermissions(page)
    Page->>Page: originForPage() - parse URL
    alt Valid http/https origin
        Page->>CDP: Browser.grantPermissions
        CDP-->>Page: OK
    else Invalid origin (about://, data:)
        Page-->>Clip: skip permission grant
    end

    Clip->>Page: Runtime.enable (catch errors)
    Clip->>Page: Runtime.evaluate
    Note over Clip,Page: navigator.clipboard.writeText(text) or readText()
    Page->>CDP: Forward evaluation
    CDP->>Browser: Execute in page context
    Browser-->>CDP: Result / Error
    CDP-->>Page: EvaluateResponse
    Page-->>Clip: Response

    alt Evaluation succeeded
        Clip->>Clip: throwIfEvaluationFailed() - no exception
        Clip-->>Ctx: void / text string
        Ctx-->>Client: Result
    else Exception in evaluation
        Clip->>Clip: throwIfEvaluationFailed() - extract exception
        Clip-->>Ctx: throw StagehandEvalError
        Ctx-->>Client: Error
    end

    Note over Client,Browser: context.clipboard - Paste / Copy / Cut Flow

    Client->>Ctx: clipboard.paste(options?)
    Ctx->>Clip: resolvePage(options?.page)
    Ctx-->>Clip: target Page

    Clip->>Page: ensurePageFocused(page)
    Page->>CDP: Page.bringToFront
    Page->>Page: window.focus()

    Clip->>Page: keyPress(options?.shortcut ?? "ControlOrMeta+V")
    Page->>CDP: Input.dispatchKeyEvent
    CDP->>Browser: Keyboard shortcut
    Browser-->>CDP: Key processed
    CDP-->>Page: OK
    Clip-->>Ctx: void
    Ctx-->>Client: void

    Note over Client,Browser: context.clipboard - Clear (delegates to writeText)

    Client->>Ctx: clipboard.clear(options?)
    Ctx->>Clip: writeText("", options)
    Clip->>Page: ensurePageFocused + grantPermissions
    Clip->>Page: Runtime.evaluate("navigator.clipboard.writeText('')")
    Page->>CDP: Forward
    CDP->>Browser: Execute
    Browser-->>CDP: Result
    Clip-->>Ctx: void
    Ctx-->>Client: void

    Note over Client,Browser: Integration Test - Active Page vs Explicit Page

    Client->>Ctx: clipboard.writeText("active page text")
    Ctx->>Clip: resolvePage(undefined)
    Ctx-->>Clip: page2 (active)
    Clip->>Page: writeText + paste on page2
    Ctx-->>Client: OK
    Client->>Page: textareaValue(page1) = ""
    Client->>Page: textareaValue(page2) = "active page text"

    Client->>Ctx: clipboard.writeText("explicit page text", { page: page1 })
    Ctx->>Clip: resolvePage(page1)
    Clip->>Page: writeText on page1
    Ctx-->>Client: OK
    Client->>Ctx: clipboard.paste({ page: page1 })
    Ctx->>Clip: resolvePage(page1)
    Clip->>Page: paste on page1 (even when page2 is active)
    Ctx-->>Client: OK
    Client->>Page: textareaValue(page1) = "explicit page text"
    Client->>Page: textareaValue(page2) = ""
Loading

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/core/lib/v3/types/public/index.ts
Comment thread packages/core/examples/clipboard.ts Outdated
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 2 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/core/lib/v3/types/public/index.ts">

<violation number="1" location="packages/core/lib/v3/types/public/index.ts:8">
P2: Custom agent: **Ensure all public methods added to the stagehand class, agent, or understudy (page, locator, etc.) interfaces are properly instrumented with the flowLogger**

New `clipboard` public understudy methods lack flowLogger instrumentation</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.
Tip: cubic used a learning from your PR history. Let your coding agent read cubic learnings directly with the cubic MCP.

Re-trigger cubic

Comment thread packages/core/lib/v3/understudy/clipboard.ts
@seanmcguire12
Copy link
Copy Markdown
Member Author

@cubic-dev-ai

@cubic-dev-ai
Copy link
Copy Markdown
Contributor

cubic-dev-ai Bot commented May 29, 2026

@cubic-dev-ai

@seanmcguire12 I have started the AI code review. It will take a few minutes to complete.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

No issues found across 7 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.
Architecture diagram
sequenceDiagram
    participant Client as Client Code
    participant Clipboard as ContextClipboard
    participant V3Context as V3Context
    participant FlowLogger as FlowLogger
    participant Page as Target Page
    participant CDP as CDP/Browser

    Note over Client,CDP: Clipboard API Flow

    Client->>V3Context: access clipboard property
    V3Context->>Clipboard: NEW: create ContextClipboard instance
    
    alt readText / writeText
        Client->>Clipboard: readText() / writeText(text)
        Clipboard->>Clipboard: resolvePage(page?) 
        Clipboard->>V3Context: resolvePage(optional page)
        V3Context-->>Clipboard: return Page instance
        Clipboard->>Page: ensurePageFocused()
        Page->>Page: setActivePage(page)
        Page->>CDP: Page.bringToFront
        Page->>CDP: Runtime.evaluate(window.focus())
        Clipboard->>Page: grantClipboardPermissions()
        Page->>V3Context: get origin from page.url()
        V3Context->>CDP: Browser.grantPermissions(origin, [clipboardReadWrite, clipboardSanitizedWrite])
        Clipboard->>Page: sendCDP("Runtime.evaluate", navigator.clipboard.writeText/readText)
        Page->>CDP: Runtime.evaluate with awaitPromise
        CDP-->>Page: evaluation result
        Page-->>Clipboard: response with value/exception
        alt Evaluation Failed
            Clipboard->>Clipboard: throwIfEvaluationFailed() -> StagehandEvalError
        else Success
            Clipboard-->>Client: return text value
        end
    else paste / copy / cut
        Client->>Clipboard: paste() / copy() / cut()
        Clipboard->>Clipboard: resolvePage(page?)
        Clipboard->>Page: ensurePageFocused()
        Page->>CDP: Page.bringToFront + window.focus()
        Clipboard->>Page: keyPress(shortcut)
        Page->>CDP: keyboard shortcut (ControlOrMeta+V/C/X)
        Clipboard-->>Client: void
    else clear
        Client->>Clipboard: clear()
        Note over Clipboard: delegates to writeTextInternal("")
        Clipboard->>Page: writeTextInternal("")
        Page->>CDP: Runtime.evaluate(navigator.clipboard.writeText(""))
        Clipboard-->>Client: void
    end

    Note over FlowLogger: All public methods decorated with @FlowLogger.wrapWithLogging
    FlowLogger->>FlowLogger: emit event type (ClipboardReadText/WriteText/etc.)
Loading

Re-trigger cubic

Copy link
Copy Markdown
Member

@pirate pirate May 29, 2026

Choose a reason for hiding this comment

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

You should add a test for clicking an element that triggers copy to clipboard in JS. Then confirm users can read the copied value via the new APIs. Chrome uses user-initiated input actions to gate clipboard behavior, so it's important to test it works with their gating system.

e.g.

var copyTextareaBtn = document.querySelector('.js-textareacopybtn');

copyTextareaBtn.addEventListener('click', function(event) {
  var copyTextarea = document.querySelector('.js-copytextarea');
  copyTextarea.focus();
  copyTextarea.select();

  try {
    var successful = document.execCommand('copy');
    var msg = successful ? 'successful' : 'unsuccessful';
    console.log('Copying text command was ' + msg);
  } catch (err) {
    console.log('Oops, unable to copy');
  }
});
<p>
  <button class="js-textareacopybtn" style="vertical-align:top;">Copy Textarea</button>
  <textarea class="js-copytextarea">Hello I'm some text</textarea>
</p>

Comment on lines +17 to +18
copy(options?: ClipboardOptions): Promise<void>;
cut(options?: ClipboardOptions): Promise<void>;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

should cut/copy return the text? prevents having to read after

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

initially i had them returning a string, but then this kind of paints the API into a corner. the thinking was to keepcopy & cut generic APIs, so we can support more content types in the future

Comment on lines +13 to +14
readText(options?: ClipboardOptions): Promise<string>;
writeText(text: string, options?: ClipboardOptions): Promise<void>;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

is Text explicit because this doesn't support images?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

yeah also clipboard supports all kinds of extra interesting things, like multiple mimetype representations for the same text that consumers can choose between, images, etc.

we may want to think about how to design it to not preclude supporting those things in the future with optional args.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

yeah, I intentionally made Text explicit so that we can add future read/write functions for the various content types, or add generic read/write APIs which return an object containing nullable fields of all the possible types.

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.

3 participants