Trusted native UI extensions
Native UI extensions implement the native component interface of the
CAIPE App Contract. The shared application declaration uses
AgenticAppManifest; the component interface defines the module exports and
host-supplied props. Both new-publisher versions are 2.0.
Native UI extensions let first-party teams ship a React surface in the CAIPE shell without an iframe. They are installed while building a CAIPE image, but the feature code and release history stay in the team's repository.
Use this mode only for reviewed, trusted code. A native package shares the CAIPE document, React runtime, browser storage, and same-origin privileges. The current same-origin External App iframe also shares browser privileges. Use an isolated origin/sandbox profile when those privileges are inappropriate.
Package contract
New packages use AgenticAppManifest 2.0, with a
native UI profile declaring contractVersion: "2.0" and the same installation
model as hosted apps. Existing packages can retain the following native v1.1
descriptor as an explicit compatibility input; it is not the new-publisher format:
{
"id": "example-app",
"displayName": "Example App",
"description": "Example first-party workspace",
"contractVersion": "1.1",
"hostPaths": ["/example"],
"navigation": {
"label": "Example",
"href": "/example",
"placement": "after-chat",
"icon": "layout-grid"
},
"api": {
"appId": "example-app",
"basePath": "/api/agentic-apps/runtime/example-app",
"mounts": ["/api/example"]
},
"auth": { "mode": "app-scoped-token" }
}
New publisher modules declare the same 2.0 version as their native profile:
export interface NativeExtensionProps {
apiBasePath: string;
pathname: string;
search: string;
navigate(href: string): void;
Link?: typeof import("next/link").default;
setBreadcrumbs(items: Array<{ label: string; href?: string }>): void;
}
export default {
contractVersion: "2.0",
Component,
};
Legacy descriptors must instead pair with a 1.1 module. The host rejects
unknown versions or a descriptor/module mismatch. The 2.0 release retains
the same props and browser trust boundary; aligned versioning does not add
isolation or grants.
The package must expose . for its ESM entry, ./manifest for the JSON
manifest, and ./styles.css for scoped styles. The manifest export may target
a nested built file such as ./dist/manifest.json; publisher and host checks
use that export, not a separate root-level declaration. React, React DOM, Next.js,
NextAuth, and next-themes must remain peer dependencies so the host supplies
one copy.
The package may not export a server entry. Its entry must be compiled client
JavaScript beginning with "use client"; the checker rejects declared server
exports, listed server-only import patterns, and start/postinstall hooks.
These checks do not prove that dependencies or arbitrary code are safe.
Next.js can prerender Client Components, so this is not a browser-only execution
boundary. App APIs, MCP endpoints, ingestion, and agents remain separately
deployed services.
navigation is optional. An app with owned routes but no navigation item can
be opened only through an authorized deep link or another host surface. A
route-less app can instead declare "hostPaths": [] and "slots": ["home"];
the host mounts that component on the home page. Every app must claim at least
one route or supported slot. Slots, routes, and API mounts are exclusive across
installed packages.
For a non-advertised internal app, the Agentic App installation may set
visible: false: the native route/slot and gateway still enforce installed,
enabled, session, role, and API policy checks, while the public app catalog
and left navigation remain hidden. The same visibility/access distinction now
applies to hosted iframe apps.
Build and routing
- Build the package and run the versioned
@caipe-io/app-sdkconformance checker against its package directory:caipe-native-check <package-directory>. The equivalent in-tree command isnode ui/scripts/check-native-extension.mjs <package-directory>. It verifies the manifest, export targets, contract version, and shared runtime peer dependencies. Publisher CI should run the same checker against each supported CAIPE host release. Checker2.0.0-rc.1also enforces a 64 KiB entry budget, a 2 MiB compiled-JavaScript budget, and rejects document-wide CSS selectors, including selector-list members inside nested rule blocks. Class-scoped descendant universal selectors remain supported. These are conformance checks, not complete CSS containment. These are uncompressed publisher gates, not network latency estimates. Apps can lazy-load editor and visualization chunks. - Install a reviewed package at an exact version in the derived CAIPE image,
or install its immutable
.tgzartifact. Commit the resultingpackage-lock.jsonin the derived build context; the host build rejects missing integrity data, version mismatches, and floating dependency ranges. For tarballs, place the artifact inui/native-extension-artifacts/and install it withnpm install --save-exact ./native-extension-artifacts/<name>.tgz. - Set
CAIPE_NATIVE_EXTENSION_MODULESto a comma-separated allowlist of packages already installed in the image. This is a build-time selection, not a browser-controlled or runtime remote-JavaScript loader. - Run the normal UI build. The prebuild generator validates ownership and
emits static imports, manifest data, and Tailwind source declarations.
Docker builds must pass the package list as the
CAIPE_NATIVE_EXTENSION_MODULESbuild arg (a runtime environment variable alone cannot bake the module into the image). - CAIPE rewrites claimed browser paths to its internal native host while the
public URL stays unchanged. Client navigation keeps the host and extension
mounted in one React tree. Use the optional host
Linkfor ordinary links (ornavigatefor imperative transitions); directnext/linkis compatible only when it resolves to the host-provided Next singleton. Links to owned routes should use canonical browser paths, including on direct loads.
Package manifests cannot claim CAIPE internal paths. Overlapping route, slot,
or API claims fail the build. An image rollback restores the prior pinned
package and host combination; runtime enablement cannot install new code.
The host reserves its built-in sections; /projects is an intentional
extension point and can be claimed by one package at build time.
Authentication and authorization
The browser continues to use the CAIPE session. Claimed API paths are rewritten to the existing External App gateway, which authenticates that session, evaluates the configured app policy, and applies the manifest's explicitly declared authentication mode. The installed app configuration must agree with that mode or the gateway fails closed.
In the default app-scoped-token mode the gateway:
- mints a short-lived, audience-bound user JWT;
- forwards the stable user subject and trusted roles; and
- returns decision and correlation IDs.
The gateway is a browser-cookie ingress. It obtains a CAIPE session, ignores
caller-supplied identity headers, and is not the machine-to-machine API for an
extension. Bearer clients, webhooks, and MCP callers use an app-owned ingress
that independently validates their token and scopes. Neither app should treat
x-caipe-roles or another forwarding header as proof of authorization.
Trusted deployments may opt into forward-user-access-token when the target
service validates that token's issuer and audience. The gateway forwards the
existing access token server-to-server; the native-package contract does not
pass it as a component prop. CAIPE's current NextAuth session callback also
exposes accessToken to first-party browser code, so a trusted native package
is not an isolation boundary for that token. In both modes, the service
remains responsible for resource-level
authorization, such as OpenFGA checks using the stable user subject. Native
JavaScript shares the CAIPE document and same-origin privileges, so package
review is part of the security boundary.
Record the package tarball digest, source commit, CAIPE commit, checker version, and image tag in the deployment record. Roll back the entire pinned host image; changing runtime enablement cannot roll back compiled JavaScript by itself.
Performance marks
The host emits caipe-native-extension:<id>:route-ready. Packages should emit
their own render-ready and data-ready marks so browser benchmarks can separate
shell routing, bundle execution, and API latency.