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

# View Extensions

> Learn how Xpert plugins use view providers and manifests to add declarative views or remote components to Workbench and other host surfaces.

# View Extensions

A View Extension is the standard contract for adding an interactive plugin view to an Xpert host surface. It defines where the view appears, when it is visible, which data and operations it can use, and how it is rendered.

For Assistant Workbench, a Remote Component is one View Extension rendering mode. It owns the custom UI inside an iframe; the View Extension owns the host slot, feature activation, permissions, data, and action contract.

```text theme={null}
Assistant feature
  -> Workbench View Host
  -> agent.workbench.main / agent.workbench.fixed
  -> ViewExtensionProvider
  -> XpertExtensionViewManifest
  -> platform renderer or Remote Component
```

## Core concepts

| Concept | Responsibility |
| - | - |
| Host | The product surface and current user context, such as an Assistant, project, or knowledge base |
| Slot | A location where the host accepts extension views, such as `agent.workbench.main` |
| View Provider | The plugin server implementation that supplies manifests, data, actions, and an optional remote entry |
| Manifest | The declaration of placement, activation, rendering, and allowed capabilities |
| Remote Component | Plugin-owned custom UI running in a host-controlled iframe |

A Remote Component is not a standalone plugin type and cannot register itself without a manifest. A complete Workbench UI normally combines a server-side View Provider with a frontend Remote Component.

## Hosts and slots

`hostType` identifies a host category. The platform can expose `agent`, `project`, `knowledgebase`, `integration`, and `sandbox` hosts.

Assistant Workbench uses the `agent` host. Common slots include:

| Slot | Purpose |
| - | - |
| `agent.workbench.main` | A primary view that follows the current Workbench content |
| `agent.workbench.fixed` | A fixed view available from the Workbench menu |
| `agent.profile.tabs` | Lazy-loaded tabs inside Assistant Profile |
| `detail.sidebar` | Supporting content in a host detail sidebar |

A plugin can only contribute to slots declared by the host. Slot names describe product placement, not plugin business domains.

## Feature activation

Assistant Workbench slots require `activation.requiredFeatures`. A Feature is the capability token that connects Agent functionality with its human-facing Workbench view:

```text theme={null}
Agent middleware declares a Feature
  -> the Assistant connects that middleware
  -> the View Host receives the Feature
  -> the matching View becomes visible
```

```ts theme={null}
activation: {
  requiredFeatures: ['contract-review']
}
```

Bind a View to the domain capability that owns its data and actions. Removing the middleware should remove both its Agent tools and its gated Workbench view.

## View Providers

Register a provider with `@ViewExtensionProvider(providerKey)`:

```ts theme={null}
import {
  type IXpertViewExtensionProvider,
  ViewExtensionProvider
} from '@xpert-ai/plugin-sdk'
import type {
  XpertExtensionViewManifest,
  XpertResolvedViewHostContext,
  XpertViewDataResult,
  XpertViewQuery
} from '@xpert-ai/contracts'

@ViewExtensionProvider('contract_review')
export class ContractReviewViewProvider
  implements IXpertViewExtensionProvider
{
  constructor(private readonly reviewService: ContractReviewService) {}

  supports(context: XpertResolvedViewHostContext) {
    return context.hostType === 'agent'
  }

  getViewManifests(
    _context: XpertResolvedViewHostContext,
    slot: string
  ): XpertExtensionViewManifest[] {
    if (slot !== 'agent.workbench.main') return []
    return [createContractReviewManifest(slot)]
  }

  async getViewData(
    context: XpertResolvedViewHostContext,
    viewKey: string,
    query: XpertViewQuery
  ): Promise<XpertViewDataResult> {
    return this.reviewService.getViewData(context, viewKey, query)
  }
}
```

The provider receives the local manifest key. The platform exposes a public view key in this form:

```text theme={null}
<providerKey>__<manifestKey>
```

For example, the `review` manifest from provider `contract_review` becomes `contract_review__review`.

## Choose a rendering mode

The manifest `view.type` selects the renderer:

| Type | Best for | UI owner |
| - | - | - |
| `stats` | A small metric summary | Platform |
| `table` | Standard tables, search, sorting, and pagination | Platform |
| `list` | Standard lists | Platform |
| `detail` | Read-only field details | Platform |
| `raw_json` | Diagnostics or raw payloads | Platform |
| `remote_component` | Editors, canvases, complex workflows, or multi-panel Workbenches | Plugin |

Prefer a declarative renderer when it meets the product need. Use a Remote Component when the interaction and layout clearly exceed the platform table, list, or form surface.

## The manifest is a capability allowlist

The manifest describes both the view and the host capabilities available to it:

| Manifest field | Capability |
| - | - |
| `dataSource` | Data queries, pagination, search, sorting, and parameter support |
| `parameters` | View parameters and provider-backed options |
| `actions` | JSON or file actions |
| `fileAccess` | Preview or download files resolved by the provider |
| `clientCommands` | Host UI commands such as open file, navigate, or send an Assistant message |
| `hostEvents` | Host-side events such as completed Agent middleware tools |
| `permissions` | Permissions required to access the entire view |

A Remote Component must not treat the bridge as a generic RPC tunnel. Declare every data or operation capability in the manifest before the host and provider handle it.

## Opening and rendering are independent

A host can list a view from a slot, or a tool result can open it on demand with `xpert.extension_view`. This changes only the entry path; the manifest, permissions, provider data, and Remote Component implementation remain the same.

A tool result should contain only the public view key, initial query, and business parameters. Do not include access tokens, API URLs, Assistant IDs, tenant IDs, or organization IDs.

## Security boundary

* The host resolves `hostType`, `hostId`, tenant, organization, and user from authenticated server-side state.
* The platform validates manifests and filters them by feature activation and permissions.
* Remote Components do not receive access tokens, platform API URLs, or internal host identity fields.
* Providers must re-check business permissions and must not trust iframe-supplied business identifiers on their own.
* Use JSON actions for bounded structured data and dedicated capabilities for files or large payloads.

## Next steps

* [Workbench Remote Components](./remote-component): build a custom plugin Workbench UI.
* [Remote Component Host Bridge](./remote-component-bridge): map messages to manifest declarations and provider methods.
* [Runtime Capabilities](./runtime-capabilities): consume platform services from plugin server code.

## Assistant Profile tabs

Import `AGENT_PROFILE_TABS_SLOT` from `@xpert-ai/contracts` and contribute to the existing `agent` host. Declare a domain Feature in middleware metadata and require it in every Profile manifest. The same provider can supply Workbench and Profile views.

```ts theme={null}
import { AGENT_PROFILE_TABS_SLOT, type XpertExtensionViewManifest } from '@xpert-ai/contracts'

const profileTab: XpertExtensionViewManifest = {
  key: 'recent-cases',
  title: { en_US: 'Recent cases', zh_Hans: '最近案件' },
  hostType: 'agent',
  slot: AGENT_PROFILE_TABS_SLOT,
  activation: { requiredFeatures: ['case-profile'] },
  view: {
    type: 'remote_component', runtime: 'react', protocolVersion: 1,
    dataSource: { mode: 'platform' },
    component: { isolation: 'iframe', entry: 'case-profile' }
  },
  dataSource: { mode: 'platform', cache: { enabled: false } },
  actions: [{ key: 'approve_and_continue', label: 'Approve and continue',
    actionType: 'invoke', placement: 'row', requiredHostAccess: 'read' }],
  clientCommands: [
    { key: 'assistant.profile.interaction' },
    { key: 'assistant.profile.close' }
  ]
}
```

### Identity and authorization

`getViewData(context, viewKey, query)` and `executeViewAction(context, viewKey, actionKey, request)` receive `XpertResolvedViewHostContext`. For Agent hosts, its backend-only `assistant` contains:

| Field | Meaning |
| - | - |
| `instanceId` | Resolved published Assistant used for this host |
| `currentId` | Current editable instance in the same version family, when uniquely available |
| `versionIds` | IDs in the same Assistant version family, including current and published instances |

The host resolves this identity inside the tenant, organization, and Workspace boundary. Never infer identity from a name, role, or shared template. Never accept Assistant, user, tenant, or organization IDs from iframe query/action input as authorization. The iframe receives no authentication credentials and submits only business parameters through the bridge.

The platform checks host access, Feature activation, declared actions, and the view session. **The plugin owns Assistant-to-business-resource permissions.** For example, Factory Operations intersects explicit assignments or execution participation with the current human's readable Case Projects before pagination and totals.

Server plugins can use `ProjectAccessRuntimeCapability` (`platform.project.access`) from `@xpert-ai/plugin-sdk` to list a human actor's readable Projects (including explicit `assistantIds` bindings), assert owner/manager access with `assertManage`, or assert owner/manager/editor access with `assertEdit`. This capability is available to plugin server services; it does not grant Agent tools human approval. `requiredHostAccess: 'read'` permits a Profile reader to invoke the route; the plugin must independently authorize every decision on the selected business resource.

### Lifecycle and decisions

The host fetches basic information and manifests when opened. An extension is instantiated and queried on first selection, then remains mounted while the Profile stays open. On tab changes, the host sends `viewActive: false` to the hidden Remote View; it must pause polling and ignore stale responses while retaining its UI/data state. Returning sends `viewActive: true`, reuses cached data, and resumes from the next polling interval. Changing Assistant or closing tears down every cached view and its view session. Basic data comes from `GET /xpert/:id/profile`, which returns a display whitelist, never prompts, credentials, or the full Assistant graph.

Profile Remote Views use an opaque-origin iframe. Do not access `localStorage` or `sessionStorage`. Use React state for ephemeral state and View actions for durable state.

Declare and invoke `assistant.profile.interaction` with `{ busy: true }` while a decision dialog is open or an action is pending; send `{ busy: false }` afterward. This prevents auto-dismissal and tab switching. Handle Escape in the dialog first. When no dialog or action is active, `assistant.profile.close` closes the card and restores focus. Unmounting removes the command scope.

For durable decisions, use a business revision and stable operation ID. Show the proposal and execution mode before approval. Persist approval and continuation intent in one transaction, then dispatch Managed Queue work with checkpoints. The browser must not orchestrate the continuation. Factory Operations' `approve_and_continue` preserves the existing `approve_recovery_plan` behavior and uses the saved Case coordinator to dispatch verification; it never substitutes the currently viewed specialist.
