> ## Documentation Index
> Fetch the complete documentation index at: https://docs.open.cx/llms.txt
> Use this file to discover all available pages before exploring further.

# Widget v5 Companion mode

> Bring Companion into your website with Widget v5. Configure floating layouts, page context, marked screenshots, mentions and available AI features.

Companion brings help into the page your visitor is already using. Open it from a small launcher, ask a question, and move between a floating workspace, sidebar and fullscreen as the task grows.

<Frame>
  <video src="https://platform.open.cx/changelog/widget-v5-companion.mp4" controls muted playsInline aria-label="Widget v5 Companion: opening, streaming replies, page annotations and a scheduled follow-up" />
</Frame>

<Info>
  This guide describes **Widget 5.0.0**. Check [version availability](/widget/install-widget#choose-your-version) before installing. Companion controls layout; agent features also work in the classic popover when their delivery and permission requirements are met.
</Info>

## Add Companion to your site

Get your widget token from [Channels → Web chat](https://platform.open.cx/channels/configure/widget). For a React integration:

```bash theme={"dark"}
npm install @opencx/widget-react@5.0.0
```

Mount the widget once in your app's client-side layout:

```tsx theme={"dark"}
import { Widget } from "@opencx/widget-react";

export function SupportWidget() {
  return (
    <Widget
      options={{
        token: "<WIDGET_TOKEN>",
        displayMode: "companion",
        companion: {
          layouts: ["compact", "sidebar", "fullscreen"],
          defaultLayout: "compact",
        },
      }}
    />
  );
}
```

For an HTML site, follow [Install Widget](/widget/install-widget) and pass the same options directly to `initOpenScript`. Use one integration on a page. If you set `inline: true`, inline rendering takes precedence over Companion mode.

<Tip>
  [Install the integration skill](/widget/agent-skills), then ask your coding
  assistant: “Use opencx-widget-integration to add Widget v5 in Companion
  mode to this app. Exclude query strings, fragments and private path or title data from page context.”
</Tip>

## Choose the layouts visitors can use

| Option | What visitors see |
| - | - |
| `compact` | A floating workspace. The layout picker calls this **Floating**. |
| `sidebar` | A panel at the page edge. It floats over the page by default. |
| `fullscreen` | An expanded workspace with more room for longer replies. |

`companion.layouts` controls the choices and their order. Keep one layout to hide the picker. `defaultLayout` accepts `compact` or `sidebar`; when omitted, the first allowed layout is used. An empty layout list falls back to all three.

To place Companion beside your page instead of over it:

```tsx theme={"dark"}
<Widget
  options={{
    token: "<WIDGET_TOKEN>",
    displayMode: "companion",
    companion: {
      defaultLayout: "sidebar",
      layouts: ["sidebar", "fullscreen"],
      sidebar: { side: "auto", mode: "docked", width: 400 },
    },
  }}
/>
```

Docked mode reframes the host page to make space for the sidebar. Use `mode: 'floating'` when the page layout should stay unchanged. `side: 'auto'` uses the right edge on left-to-right pages and the left edge on right-to-left pages.

## Starter questions, bubbles and avatars

Starter questions appear in order above the Companion composer. Visitors can type instead unless you require a choice:

```tsx theme={"dark"}
const options = {
  token: '<WIDGET_TOKEN>',
  displayMode: 'companion',
  initialQuestions: ['Where is my order?', 'How do returns work?'],
  requireInitialQuestion: true,
  companion: { bubbles: false, avatars: true },
};
```

With `requireInitialQuestion: true`, the composer stays visible but disabled until a starter is selected. Selection sends the question and enables follow-ups. Resuming a session with messages does not require another choice. Empty question lists leave typing available. Set the option to `false` or omit it for optional starters.

`companion.bubbles` and `companion.avatars` are independent. Use either, both, or neither. If `avatars` is omitted, it follows `bubbles`. The popover keeps its chat bubbles and avatars.

## Enable the capabilities you need

Open [Channels → Web chat](https://platform.open.cx/channels/configure/widget) to copy your widget token and manage website features next to a live preview. Feature switches save as you change them; a successful save refreshes the preview. On smaller screens, use the **Settings** and **Preview** tabs.

**Try your widget** opens a simple demo landing page. Select **Try page actions** to open a small form with a name field, contact preference and updates checkbox. You can edit and save these preferences, or ask your agent to help when the corresponding page features are enabled for your organization. **Back to demo** returns to the landing page and keeps your changes; **Reload preview** resets them. Form changes affect only the demo; chat still uses your organization's agent. The form does not change contact or notification settings. The demo chats as you, with your dashboard name and email. Sessions you start there appear in your inbox under your name and stay in the demo history on that browser.

Under **Appearance**, use **Companion mode** to switch the demo between Companion and classic chat. It starts on and changes only the preview; your website's layout still follows its embed settings. Switching layouts keeps the demo's form values.

Use **Enter fullscreen** in the demo toolbar to fill the current page area without resetting the preview. The OpenCX sidebar stays available. Select **Exit fullscreen** or press **Escape** to return to settings.

If your organization has a website configured, **Open your website** opens it in a new tab. Install the widget there to test your actual pages, sign-in, cookie notices and navigation. The dashboard demo does not install the widget on your website.

| Web chat setting | What it enables |
| - | - |
| **Rich replies** | Supported cards and tables in streamed replies and saved history. |
| **Voice dictation** | Speaking into the message composer. |
| **File uploads** | Visitors sending files and images. On by default. |
| **Sees the page** | Page context, selected text, marked regions and mentions. |
| **Points at the page** | Letting the agent mark the control it is talking about. |
| **Acts on the page** | Letting the agent click, type and choose for the visitor. |
| **Asks to narrow a request** | Offering choices when a request needs clarification. |

Use **Advanced settings** at the bottom of the page, or open [Settings → Advanced](https://platform.open.cx/settings/ai-features), for capabilities used across channels:

* **Progress updates** lets the agent send short updates during longer tasks on web chat, WhatsApp, Instagram, Messenger, and Slack. These are separate messages and do not require streamed replies. Web chat still follows the organization and embed preferences, including when it uses polling. Updates are withheld in assist mode and when the reply must stay silent; they are not sent on SMS, email, voice, API, or app review channels. Connected messaging channels must be able to send a reply; WhatsApp's reply window still applies. If delivery fails or is cancelled, the update is recorded as an internal note with unconfirmed delivery rather than a customer reply.
* **Remembers past sessions** controls whether the agent can recall the contact's earlier sessions.
* **Team in the loop** lets the agent ask your team for help in Inbox without transferring: it leaves a note, mentions the team, tags the session **AI needs help**, and stays on the session. A teammate's reply in the note's thread answers the request and the agent continues with the answer; after a direct reply to the customer, the agent picks up on the customer's next message. The tag stays for historical filtering. To take over, open the **AI** assignee menu in the session header and choose **Take over**. Handoff keeps working exactly as configured, and your team must work in Inbox. See [Handoff settings](/handoff/settings#team-in-the-loop).
* **Scheduled follow-ups**, **Task plans**, **Workspace** and **Builds mini apps** are available on request. Enabled scheduled follow-ups let the agent check back and continue the same session later.
* Features available to your organization have a switch. Other features show **Contact us** to request access. Editing requires the appropriate settings permission.

Progress updates follow your AI profile and [training scenarios](/ai-instructions/training-scenarios). Within a session, new updates are written to avoid repeating the wording of updates the customer has already seen. To change how often updates appear, add a **Behavioral** scenario, for example: *Send a progress update only for a new request or a retry. Do not send one for follow-up questions, clarifications or extra details about something you just looked up; answer those directly.* Frequency rules are followed more consistently in a behavioral scenario than in the AI profile. Describe the length or tone you want in the same way, then review a few sessions after the change. A scenario-specific answer that scripts the reply, such as a link or fixed text, is sent without a progress update. To turn updates off for one website, add `preamble: false` to that embed's `features`.

Manage transfer to your team separately in [Handoff settings](https://platform.open.cx/settings/handoff).

To show final replies as they are written, set `streaming: true` and enable streaming for your organization. Selecting Companion or installing v5 does not turn it on. Omitted or `false` keeps polling, including with the v2 agent. `presentation.streaming: false` hides incremental text while keeping the streaming transport for supported interactions. If replies still arrive all at once, contact your OpenCX administrator or support team to check availability.

You can narrow supported features for one embed. For example, add `features: { dictation: false, clientTools: false }` to its options to hide dictation and prevent the agent from highlighting that page. In Widget 5.0.0, page access requires explicit embed opt-in: `pageContext: true` for reading, also `clientTools: true` for pointing, and also `pageActions: true` for acting. Omission keeps these page permissions off. Organization permission is also required.

Turn off **File uploads** if your team must not receive documents from visitors. The widget then hides the attach button, drag and drop, and paste, and messages with files are refused for every embed and custom client. Marked regions still send, without their screenshot. To hide uploads on one embed only, add `attachments: false` to its `features`; widget versions without this option ignore it.

## Opt into page assistance

For page reading, pointing and actions, enable the corresponding Web chat settings and use:

```tsx theme={"dark"}
const options = {
  token: '<WIDGET_TOKEN>',
  streaming: true,
  features: {
    pageContext: true,
    clientTools: true,
    pageActions: true,
  },
};
```

This works with the default popover or with `displayMode: 'companion'`. Enable only the permissions you intend: reading needs `pageContext`; pointing also needs `clientTools`; actions need all three. Each action asks the visitor to approve. A headless client also needs its own page-effect renderer and consent implementation before declaring support.

Generic forms and a customer-defined catalog of components for agent-generated rich replies are not part of the built-in rich-reply catalog. [Custom action-result components](/widget/custom-components-react) and [personal-connection forms](/widget/connections#review-tool-requests) are separate integrations.

## Share the current page

The opt-in defaults above require Widget 5.0.0. Earlier v5 beta embeds can inherit enabled organization settings for omitted flags. Until you upgrade, disable page access explicitly with `features: { pageContext: false, clientTools: false, pageActions: false }`; a server update alone does not change an older embed's defaults.

Host-supplied context is sent with messages, stored in the session history, and can be included in the agent’s input. Send only the page information the agent needs. The examples combine `window.location.origin` and `window.location.pathname`, so query parameters and URL fragments are excluded. Those values can contain login tokens, search terms or other private data and should not become part of the stored session. If your path or page title also contains private information, replace it with an approved route label or omit it.

Use a `context` function so each message includes the page at the time it is sent. You can also provide the record the visitor is viewing:

```tsx theme={"dark"}
<Widget
  options={{
    token: "<WIDGET_TOKEN>",
    displayMode: "companion",
    streaming: true,
    features: { pageContext: true },
    context: () => ({
      page: {
        url: window.location.origin + window.location.pathname,
        title: document.title,
      },
      entity: {
        type: "order",
        id: "order_123",
        title: "Order #123",
        meta: { status: "processing" },
      },
    }),
  }}
/>
```

Replace the example entity with the current record from your application. It appears as a removable context pill in the composer when page context is enabled. URL changes refresh the pill. If the selected record changes without navigation, notify the widget:

```js theme={"dark"}
window.dispatchEvent(new Event("opencx:context-changed"));
```

In an app that updates without reloading, have the callback read current state from a store or ref. Keep one mounted widget and update its options as the signed-in user or account changes.

<Note>
  Only supply context you intend the agent to receive. `features.pageContext:
      false` disables widget-collected page context and its controls; it does not
  remove the host application's `context` from messages. Remove or narrow that
  callback separately when needed.
</Note>

## What the agent reads from the page

With **Sees the page** and `features.pageContext: true` enabled, the widget also reads the controls a visitor can see — buttons, links, fields, tabs — and sends their names with each message, so the agent can answer "where do I turn that off" without the visitor hunting for the switch first.

The automatic control list includes names, roles, references and disabled state; it excludes input values. The widget skips private and sensitive controls, hidden controls, its own interface and controls it cannot name. This does not scrub data you deliberately supply in `context`, messages, uploaded files or action request fields. Review that data separately.

To keep part of a page out entirely, mark it private:

```html theme={"dark"}
<section data-opencx-private>
  <!-- Exclude this region from widget-collected page context. -->
</section>
```

Put the attribute on the outermost element of the region; everything inside it is excluded, at every depth.

<Note>
  The agent can only ever refer to a control the widget offered it for that
  message. A control inside a private region, or one the widget skipped, cannot
  be named, pointed at or used.
</Note>

## Let the agent act on the page

**Acts on the page** is off until you turn it on, and it is the only capability that changes something on a visitor's screen rather than describing it. With it on, the agent can click a control, type into a field, choose from a dropdown, or switch something on or off — always by pointing at a control the widget read for that message, never by a selector it wrote.

With Widget 5.0.0, before every action — including typing or changing a selection — the visitor sees the named control and proposed value and chooses Allow or No. A No is final for that request: the agent is told, and does not ask again or reach the same control another way.

What it will not do, whatever it is asked:

* Password fields and file pickers.
* Controls inside another site embedded in your page.
* Anything inside a region you marked `data-opencx-private`.
* Anything covered by a dialog or banner, or no longer on the page.

The agent receives the browser outcome and an updated list of controls. The new list replaces the previous screen, even when empty. If a control disappeared, the widget returns the current controls without clicking a replacement automatically.

In Widget 5.0.0, a control reused for another row or item requires a fresh page reading and approval. The widget checks changes to the control's name, native operation, and surrounding content. On pages without a clear item boundary, other content changes can also require a fresh reading. These checks cannot detect changes held only in your application's private state with no corresponding page change.

An interrupted action is not a confirmed completion, even when a hover, press, or focus handler already changed the page. It also does not prove that nothing happened. The agent must explain the uncertainty and ask the visitor to verify before trying again.

Widget 5.0.0 waits up to three seconds for visible regions marked `aria-busy="true"` to finish loading. A page without a loading signal can appear settled before a delayed request finishes. A timeout or visible change does not confirm that a payment, save, or other remote operation succeeded. The agent must report uncertainty and ask the visitor to verify before repeating an action.

Clicks run your site's normal event handlers. Test multi-step flows on your site, including loading states, navigation and auto-saving fields. Full page reloads can interrupt the live session response; the visitor may need to send an updated message after the page opens.

## Mark a region and attach a screenshot

With **Sees the page** enabled and `features.pageContext: true`:

1. Open the expanded Companion panel and choose **Mark something on the page to ask about it** in the composer.
2. Click the page element you want to discuss. Choose a shape such as **Box**, **Circle** or **Arrow**, and adjust the marked region.
3. Add a note such as “Explain this balance.” Press Enter or choose **Attach mark**.
4. Review the marked screenshot in the composer, add your question, and send. Remove the attachment first if you change your mind.

Press Escape to cancel marking. These controls appear in the expanded panel; set `companion.quickAskTools: 'all'` if you also want attachment and page-mark controls in the quick-ask bar.

## Let visitors mention records

Configure `mentions.items` for a fixed list, or `mentions.search` for records loaded as the visitor types. Each item needs `type`, `id` and `title`:

```tsx theme={"dark"}
<Widget
  options={{
    token: "<WIDGET_TOKEN>",
    displayMode: "companion",
    streaming: true,
    features: { pageContext: true },
    mentions: {
      items: [
        { type: "order", id: "order_123", title: "Order #123" },
        { type: "order", id: "order_124", title: "Order #124" },
      ],
    },
  }}
/>
```

Visitors type `@` and pick a record. The selected records travel with the message when page context is enabled. Supply only records the signed-in visitor is allowed to access; mentioning an ID does not grant access to it.

## Verify the experience

Open the launcher, switch layouts and send a message. With streaming enabled, confirm text arrives progressively and **Stop** interrupts the reply. A message sent during a reply can steer the current task or wait for it to finish.

Then navigate to a different page before sending, attach a marked screenshot, and reload to check session history. If scheduled follow-ups are enabled, ask for a check-in at a specific time and confirm the schedule the agent creates. Test human handoff as part of your normal support flow.

See the [configuration reference](/widget/configuration-reference#widget-v5-options) for more options and [Migrate to v5](/widget/migrate-to-v5) for existing integrations.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.