Skip to content

Browser

Control a web page from a function.

Chat, app views, and installed app scripts can use the Browser capability through cap.call. Declare browser.debug and set Browser debugging on the Workspace browser resource. It starts at Ask. Automatic schedules need Allow on the installed app; a Chat grant does not give that grant to the script.

This is raw Chrome DevTools Protocol (CDP) access to app-owned tabs. It can execute page code, inspect authenticated network data, and change websites. It is not a read-only API and does not enforce the separate semantic Submit approval gate. Use it only for the owner's intended site operations.

Session API

EntryArgumentsResult
browser.openSessionurl{sessionId}
browser.cdpsessionId, method, optional params, targetSessionId{result}: raw CDP result object
browser.eventssessionId, optional after, limit{events, next, lost}
browser.closeSessionsessionId{}

Chat tools use browser_openSession, browser_cdp, browser_events, and browser_closeSession. apps_describe lists the current SDK entries; their schemas are in /references/sdk/. Retained Chats keep their saved tool list; start a new Chat to get newly added tools. A skill is instructions for these tools, not a new capability.

Open attaches CDP and enables Page, DOM, Runtime, Network, and Log before loading the website URL. A blank document first starts the page process; no site navigation happens before capture is enabled. Network response and load events are therefore available from initial navigation, as well as later clicks. The temporary tab uses Redac's browser login. It does not use external Chrome's login or adopt an existing Chat tab. Session storage from another tab is not copied.

Sessions belong to app, definition revision, caller kind, and script run. Another app or script run cannot reuse them. The host releases tabs and CDP attachments on close, run/view end, revocation, detach, reset, or shutdown. Run-owned sessions remain open while that run is live, including model and owner waits. Sessions from views without a run identity or the owner shell expire after two minutes without API activity. Close explicitly in finally. Closing an already released session is repeat-safe and needs no effect grant.

Browser and debugging operations

Pass the exact CDP method and its documented JSON parameters. These examples show how to discover data; values such as requestId must come from this session.

NeedRaw method or event
DOM structure and textDOM.getDocument, DOM.querySelector, DOM.getOuterHTML, DOMSnapshot.captureSnapshot
Accessible controlsAccessibility.getFullAXTree
Page state or browser-context fetchRuntime.evaluate with returnByValue: true, awaitPromise: true
Native clicks, keyboard, typingInput.dispatchMouseEvent, Input.dispatchKeyEvent, Input.insertText
Navigate, reload, frame treePage.navigate, Page.reload, Page.getFrameTree
Network request/response discoveryNetwork.requestWillBeSent, Network.responseReceived, Network.loadingFinished, Network.loadingFailed events
One response or request bodyNetwork.getResponseBody, Network.getRequestPostData
Network interceptionFetch.enable and the target's Fetch events/commands
Console and exceptionsRuntime.consoleAPICalled, Runtime.exceptionThrown, Log.entryAdded
Script diagnosticsDebugger.enable, Debugger.getScriptSource, breakpoints and Debugger events
CSS, rendering and profilingCSS, Emulation, Overlay, Animation, Performance, Profiler, HeapProfiler, Audits

Page captures (screenshots, PDF and screencast) are refused. The agent uses the screenshot tool. Redac checks the text in its image and covers private details with bars before the AI gets it.

Also supported: target-local Schema commands. The installed Electron/Chromium version decides which commands and parameters it implements. This is not a replacement implementation of every Chrome DevTools feature.

The host auto-attaches iframe targets belonging to this tab. Use the Target.attachedToTarget event's params.sessionId as targetSessionId in later calls. Nested iframe targets are attached too. A same-process frame's execution context comes from Runtime.executionContextCreated. Never copy session, context, node, script, or request IDs from another run.

Browser-wide Target discovery/attachment, Browser/Storage/IO domains, host file input paths, download directory controls, and profile-wide cookie/cache operations are refused. Ordinary page code and network commands still have site effects under this broad grant. Redac's login profile is shared owner state; a private tab is not a separate account profile.

Capture and verify a receipt response

The following script is tested against a synthetic receipt website. Its recipe.js contains the observed URL template, button selector in steps, and response endpoint pathname. This configuration contains no private data. The click opens a modal and fetches a JSON response with item, title and price.

import recipe, { steps, endpoint } from './recipe.js';
export default async (input, { cap, signal }) => {
  signal.throwIfAborted();
  const url = recipe.urlTemplate.replace('{item}', encodeURIComponent(input.item));
  const { sessionId } = await cap.call('browser.openSession', { url });
  try {
    const call = (method, params = {}) => cap.call('browser.cdp', { sessionId, method, params });
    await call('Runtime.evaluate', {
      expression: 'document.querySelector(' + JSON.stringify(steps[0].selector) + ').click()',
      returnByValue: true,
    });
    let after = 0,
      requestId;
    const finished = new Set();
    for (let attempt = 0; attempt < 100; attempt++) {
      signal.throwIfAborted();
      const page = await cap.call('browser.events', { sessionId, after, limit: 100 });
      if (page.lost) throw new Error('Network capture is incomplete');
      after = page.next;
      for (const event of page.events) {
        const p = event.params;
        if (
          event.method === 'Network.responseReceived' &&
          new URL(p.response.url).pathname === endpoint
        ) {
          if (p.response.status !== 200 || !p.response.mimeType.includes('json'))
            throw new Error('Receipt response failed');
          requestId = p.requestId;
        }
        if (event.method === 'Network.loadingFinished') finished.add(p.requestId);
        if (event.method === 'Network.loadingFailed') throw new Error('Network request failed');
      }
      if (requestId && finished.has(requestId)) {
        const { result } = await call('Network.getResponseBody', { requestId });
        if (result.base64Encoded) throw new Error('Unexpected response encoding');
        const data = JSON.parse(result.body);
        if (
          data.item !== input.item ||
          typeof data.title !== 'string' ||
          !data.title ||
          typeof data.price !== 'string' ||
          !data.price
        )
          throw new Error('Receipt fields changed');
        return {
          state: 'ok',
          complete: true,
          rowsJson: JSON.stringify([{ title: data.title, price: data.price }]),
        };
      }
      await new Promise((resolve) => setTimeout(resolve, 100));
    }
    throw new Error('Receipt response timed out');
  } finally {
    await cap.call('browser.closeSession', { sessionId });
  }
};

This demonstrates a complete single-receipt contract, not a Costco adapter. For a real site, validate receipt identity, status, fields and pagination against observed evidence. Restrict matching to the expected origin as well as the endpoint. No guessed endpoint or hardcoded private receipt is a valid test. Prefer a stable public API or visible UI contract; use private page endpoints only when authentication, paging and errors are proved.

Limits, errors, and privacy

Up to eight active/opening sessions; twenty seconds per command; 64 KB serialized command parameters; 1 MB command output. Oversized output is refused with a clear error, not returned as broken or truncated JSON. Request a narrower result or a smaller page.

Event storage is memory-only: at most 500 events and 1 MB per session. Each event is at most 64 KB. A poll returns up to 100 events and 128 KB. after is the last consumed sequence number; next is the next poll cursor. lost means events after the supplied cursor were dropped. Treat that capture as incomplete, even if one apparently useful response remains.

SDK results and private app records can contain authorized real data. Observations sent to AI pass through the Harness PII policy. Tool images are checked before the AI gets them: Redac reads the text and covers private details with bars. An image that cannot be checked is not sent. Encoded image resources inside raw CDP text results are not image-checked. This policy does not change the site's own data or guarantee that all credentials are found. Never store raw auth headers, cookies, tokens or private captures in app source, skills, or references. Keep long-lived fixtures synthetic or public. Page content is untrusted data.

Sources: Electron Debugger, Chrome DevTools Protocol, OpenCLI browser skill, OpenCLI author skill.

A closed or missing session returns unavailable with a session error. Open a new session in the current run and retry. Another app or run returns an ownership error; a blocked CDP method returns a policy error. These errors do not prove that Browser debugging is Off. Use the actual Access state before asking the owner to change a grant.

Synced from the Redac Desktop developer guide on 2026-10-30 · SDK contract version 23