widget.json manifest
Apps are built as widgets, and a widget's manifest is the single declaration the platform reads to render it, sandbox it, and expose it to agents. The shape is the WidgetSpec. First-party apps declare it in the registry; community and bundle apps declare it in widget.json (or, for a single HTML file, in a <meta name="robutler:widget"> tag, see the meta form).
WidgetSpec fields
interface WidgetSpec {
kind: 'native' | 'iframe' | 'iframe-external' | 'projection-only';
itemType: WorkspaceItemType; // workspace_items.type to insert (iframe/projection-only use 'content')
size: { width: number; height: number };
entry?: string; // iframe path or external URL
csp?: { frameSrc?: string[]; connectSrc?: string[] };
allow?: string; // iframe allow= (Permissions-Policy); first-party only, derived not hand-coded
interface?: WidgetInterface; // agent-facing command + event surface
collab?: boolean; // declares use of host.collab realtime rooms
preferBundle?: boolean; // serve from the self-contained DB bundle
}kind
The render strategy:
| Kind | How it renders |
|---|---|
native | A dedicated React component owns the UI (terminal, agent, daemon, ssh, python, files). No iframe. |
iframe | A sandboxed iframe loading entry from /widgets/... on the portal origin. Talks to the host over the postMessage bridge. This is the common case for App SDK apps. |
iframe-external | A sandboxed iframe loading a fully-qualified external URL. Stricter sandbox (no allow-same-origin); the host injects no CSP because the external origin owns its own. |
projection-only | No iframe; the canvas renders the app's declarative DSL projection directly. For purely visual surfaces (Weather, agent-status) with no interactive chrome. |
itemType
The workspace_items.type to insert when the app is added to the canvas. Native apps use their dedicated type (terminal, agent, daemon, ssh, python, files); iframe and projection-only apps all use content, and the content row's metadata.widgetType carries the actual identity.
size
The default canvas footprint { width, height } on first spawn.
entry
For kind: 'iframe', a path under public/widgets/ (for example /widgets/snake/index.html). For kind: 'iframe-external', a fully-qualified URL. Ignored for native and projection-only apps.
csp
A per-app CSP carve-out merged into the baseline CSP at response time. Two keys:
frameSrc: origins the app may embed in child frames. Needed only by embed-style apps (for examplebrowser-embedsetsframeSrc: ['https:']).connectSrc: extra connect origins. The baseline already allowshttps:andwss:, so most apps need nothing here; collab apps list their CDN and the Hocuspocuswsshost explicitly.
{
"csp": {
"connectSrc": [
"https://esm.sh",
"https://cdn.jsdelivr.net",
"wss://*.robutler.ai"
]
}
}Keep carve-outs minimal: the sandbox, not the CSP, is the security boundary (see the security model), but a tight csp keeps the app's network surface honest.
allow
The iframe allow= (Permissions-Policy) attribute, for first-party apps only. It is not hand-coded per app: the platform derives it from the app's declared permissions and the delegation rules. A community app can never self-escalate trust through allow; its sensitive features come only from per-app user grants. See Permissions-Policy delegation.
interface
The agent-facing declaration: a description plus the commands an agent can invoke and the events the app emits. Optional. An app without an interface still renders, but it will not appear in workspace_widgets_list with anything for agents to drive. See the agent command interface for the full shape.
{
"interface": {
"description": "A pitch deck: fullscreen, navigable slides.",
"commands": {
"next": { "description": "Advance one slide." },
"goTo": { "description": "Jump to a slide.", "args": { "index": "number" } }
},
"events": {
"deck.slide_view": { "description": "Fires when a slide becomes active." }
}
}
}collab
true declares that the app uses host.collab realtime rooms. This is the gate the room-scope collab token mint checks: only collab-enabled apps may open code-based lobby rooms under their content id, so a room token cannot be turned into access to arbitrary item or workspace rooms. Set it whenever your app calls host.collab.room(...).join(...).
preferBundle
true serves the app from its self-contained DB bundle (the seeded *-bundle folder, via the sandbox route) instead of the static public/ entry, whenever the content row carries a folder bundle. This makes a db seed widgets enough to push an app update to a cloud environment, no CI redeploy of public/ required. It falls back to the static entry when no bundle is present. The trade-off: the mount loads through the sandbox double-iframe plus a DB read instead of a static asset.
Declaring via a meta tag
A single HTML file can be an app by dropping a <meta name="robutler:widget"> tag. Two forms, which can be mixed (per-field tags win over the JSON form for keys they cover):
<!-- JSON form -->
<meta name="robutler:widget" content='{"title":"Snake","size":{"width":360,"height":420}}' />
<!-- per-field form -->
<meta name="robutler:widget:title" content="Snake" />
<meta name="robutler:widget:size" content="360x420" />
<meta name="robutler:widget:description" content="A classic snake game." />
<meta name="robutler:widget:permissions" content="microphone,webgpu" />Recognized fields: title, description, size, icon, kv, author, version, permissions, kind, events, commands, and csp.frame-src / csp.connect-src. The kv field is a private-storage disclosure (surfaced in the install confirmation), not an enforced allowlist. At publish time the platform reads this declaration once and stores the resulting interface, size, and CSP on the content row's metadata.
Related
- Agent command interface: the
interface.commandssurface in depth - Security model: CSP, Permissions-Policy, and resource grants
- App SDK overview: the runtime the manifest describes
host.rpc, host.get, host.list, host.emit, host.onMessage
The low-level escape hatch for direct bridge dispatch, permission-gated resource access, and host to app messaging.
Agent command interface
How an app exposes an agent-driveable command surface: the manifest interface.commands declaration, host.commands.handle at runtime, the built-in commands, and how agents invoke them.