* feat(workspaces): add operations widgets * fix(workspaces): format agent progress translation * fix(workspaces): keep operations widget helpers private * feat(workspaces): harden operations widgets Co-authored-by: EVA <eva@100yen.org> * chore(i18n): defer generated locale refresh * refactor(workspaces): isolate approval state * fix(release): declare direct checklist execution * fix(release): satisfy checklist declaration lint * fix(release): align checklist declaration order --------- Co-authored-by: Eva <eva@100yen.org> Co-authored-by: Peter Steinberger <steipete@gmail.com>
6.4 KiB
summary, read_when, title
| summary | read_when | title | |||
|---|---|---|---|---|---|
| Agent-composable Workspaces in the Control UI |
|
Workspaces |
The Workspaces tab in the Control UI is a surface you and your
agents arrange together. Tabs, widgets, their positions on a 12-column grid, and their
data bindings all live in one document. Anything that can edit that document can compose
the workspace: you, the openclaw workspaces CLI, or an agent calling workspace_* tools.
Every write goes through the same validated path, so a human's layout and an agent's
layout cannot diverge. Each accepted write bumps a version and broadcasts
plugin.workspaces.changed, so an agent's edit appears in an already-open browser without
a reload.
Enable Workspaces
The bundled Workspaces plugin is disabled by default. In the Control UI, open Plugins, find Workspaces, and select Enable. You can also enable it from the CLI:
openclaw plugins enable workspaces
Enabling the plugin adds the Workspaces tab and makes the openclaw workspaces CLI
and workspace_* agent tools available. Disabling it removes those surfaces without
deleting the workspace database or widget assets.
The default workspace
On first load you get an Overview workspace: cost and token cards, instance health, sessions, cron status, and an activity feed. It is ordinary workspace content — drag it, collapse it, hide it, or delete it.
Built-in widgets
Thirteen trusted widgets ship with the plugin and render as first-party UI:
stat-card, markdown, table, iframe-embed, sessions, usage, cron,
instances, activity, chart, preview, agent-status,
custom-widget-approvals.
The chart widget renders dependency-free inline SVG as line, bar, area,
sparkline, or gauge. Bind value to a numeric array or to an object shaped like
{ "points": [1, { "y": 2 }, { "value": 3 }] }. Set props.type to select the
visual and optionally set finite numeric props.min and props.max bounds. Invalid
types, bounds, points, and series longer than 500 entries render a safe error state;
empty series render an empty state.
The preview widget embeds a live page with reload plus desktop, tablet, and mobile
viewport controls. Set props.url, or bind value to a URL when the preview target is
data-driven; a binding takes precedence over the prop. Relative and same-origin HTTP(S)
URLs are allowed. External HTTP(S) URLs follow the gateway's external-embed policy, and
other schemes are blocked. Preview frames share the iframe-embed sandbox ceiling and
never receive same-origin access.
The agent-status widget presents the current Busy or Idle state from a
sessions.list RPC binding. Session-list events refresh it immediately, while the normal
visibility-gated binding poll remains a fallback. The custom-widget-approvals widget
lists only pending entries from the Workspaces custom-widget registry. Its Approve and
Reject controls are disabled unless the current connection holds operator.approvals;
it does not expose exec, plugin, or system-agent approvals.
Widgets declare data through bindings, they never fetch on their own:
| Binding | Resolves to |
|---|---|
static |
A literal value stored in the document (8 KB max). |
file |
A JSON, Markdown, or CSV file under <stateDir>/workspaces/data/, optionally narrowed by a JSON pointer. |
rpc |
One of a fixed allowlist of read-only gateway methods, resolved by the trusted Control UI. |
The file binding is the simplest way to put your own numbers in a workspace: write a
JSON file into the data directory and point a stat-card at it.
Provenance
Tabs and widgets carry a createdBy stamp — user, system, or agent:<id> — set from
whoever made the write. It cannot be supplied by the caller, so an agent cannot label its
work as yours, and the "AI" chip on an agent-authored widget always means what it says.
Custom widgets
An agent can author a real HTML widget with workspace_widget_scaffold (or you can, with
openclaw workspaces widget-scaffold <name>). Agent-authored code is treated as hostile:
- A scaffolded widget enters the registry as pending. No iframe is created, and the asset route returns 404 for its files, until an operator approves it.
- Approval is a separate decision from editing a layout:
workspaces.widget.approverequires theoperator.approvalsscope, the same scope that guards exec approvals. - An approved widget renders in an
<iframe sandbox="allow-scripts">— neverallow-same-origin— so its origin is opaque and it cannot reach the parent's DOM, storage, or cookies. - Its assets are served with
connect-src 'none', blocking script networking such asfetch, XHR, and WebSockets. It holds no credential and never talks to the gateway. - Data reaches it only through a versioned
postMessagebridge. Custom code can receive declaredstaticbindings, which are already agent- or operator-authored workspace values. RPC and file bindings stay in trusted built-in widgets: browsers allow a sandboxed child to navigate its own frame, so privileged data is never posted into agent-authored HTML.
Sending a prompt into chat from a widget additionally requires a manifest capability, a per-invocation confirmation quoting the exact text, and passes a rate limit.
CLI
openclaw workspaces tabs list
openclaw workspaces tabs create --title Financials
openclaw workspaces widget-scaffold revenue-chart --title "Revenue Chart"
openclaw workspaces widget-approve revenue-chart
widget-approve and the Control UI decision controls both need a device paired with the
operator.approvals scope.
Storage
The workspace document, the custom-widget registry, and a 20-entry undo ring live in
<stateDir>/workspaces/workspaces.sqlite. Agent-authored widget assets stay on disk under
<stateDir>/workspaces/widgets/<name>/, and file-binding data under
<stateDir>/workspaces/data/, because an agent authors those with ordinary file tools and
the widget route serves their bytes.