Skip to content

feat(workspaces): add agent-composable Workspaces#104139

Merged
steipete merged 31 commits into
mainfrom
dashboard/integration
Jul 11, 2026
Merged

feat(workspaces): add agent-composable Workspaces#104139
steipete merged 31 commits into
mainfrom
dashboard/integration

Conversation

@steipete

@steipete steipete commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Related: #101093

Supersedes the stacked implementation in #101094, #101097, and #101098 while preserving @100yenadmin's original product direction and contributor credit.

What Problem This Solves

OpenClaw's Control UI is a fixed dashboard: agents cannot assemble a durable workspace around the task they are doing, expose useful live data, or refine that workspace without a code change and redeploy.

Why This Change Was Made

Adds an opt-in, agent-composable Workspaces surface with tabs, built-in widgets, typed agent tools, guarded commands, and an approval flow for custom widgets. Workspace state and provenance live in SQLite; custom widget code remains a named user artifact, is content-pinned at approval, size-bounded, served with a restrictive CSP, and rendered in a sandboxed iframe.

The integration was rebuilt from the original three-PR stack, rebased onto current main, and hardened through repeated source review and live lifecycle checks. The core boundary stays generic: the bundled Workspaces plugin owns persistence, commands, tools, and widget serving, while Control UI consumes the plugin surface.

User Impact

The bundled Workspaces plugin is disabled by default and can be enabled from the Control UI Plugins page or with openclaw plugins enable workspaces. Once enabled, agents can create and refine task-specific workspaces on demand. Users can inspect provenance, reorder and resize widgets, move them across tabs, approve or reject custom widget proposals, and retain the result across sessions without editing JSON or redeploying OpenClaw.

Evidence

  • Live OpenAI proof: an OpenClaw agent using openai/gpt-5.4 created and refined an OpenAI Live workspace with six widgets across five built-in kinds in 46.8 seconds. The resulting workspace persisted in SQLite with no JSON workspace sidecar.
  • Screenshot proof and artifact manifest show the agent-authored Workspaces surface and widget controls.
  • Exact-head PR CI passed all 56 jobs for rebased commit a0a02ad1d2b3eea34e0db47bf0f3f931b9ee8413.
  • Focused Testbox proof passed 103 capability tests (run), 26 widget-approval provenance tests (run), and 78 bridge/widget/schema/tool contract tests (run).
  • Browser proof passed all four custom-widget E2E cases (run); the final manifest suite passed all 25 cases (run).
  • Fresh whole-branch autoreview iterated through approval, content pinning, capability-gated serving, registry preservation, optimistic writes, provenance, binding authorization, base-path handling, and usage aggregation until no accepted/actionable findings remained.

@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation app: web-ui App: web-ui size: XL maintainer Maintainer-authored PR labels Jul 11, 2026
@steipete
steipete force-pushed the dashboard/integration branch from cf75e95 to 2e0ad1d Compare July 11, 2026 03:52
@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. labels Jul 11, 2026
@clawsweeper

clawsweeper Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Codex review: stale review; fresh review needed.

Summary
The latest durable ClawSweeper review was for head 728e0954f6b6962f0b11e5b9151e612a61a60ec5, but the PR head is now a0a02ad1d2b3eea34e0db47bf0f3f931b9ee8413. Its old verdict and PR readiness labels are no longer current.

Next step
Run or wait for a fresh ClawSweeper review on the current PR head.

Review history (13 earlier review cycles; latest 8 shown)
  • reviewed 2026-07-11T05:53:56.184Z sha a86f2de :: needs real behavior proof before merge. :: [P1] Keep approval state out of write-scoped undo | [P1] Revoke bridge access after off-route iframe navigation | [P2] Add an idempotency key to widget prompt sends | [P2] Request read scope for read-only workspace CLI calls | [P2] Use the registered Workspaces command in onboarding
  • reviewed 2026-07-11T06:29:23.369Z sha 958cfe5 :: needs real behavior proof before merge. :: [P2] Request read scope for read-only CLI operations | [P2] Add an idempotency key to widget prompt sends | [P2] Keep shared prompt locks owned by the bridge that acquired them | [P2] Close the workspace store when the plugin is retired
  • reviewed 2026-07-11T07:08:16.672Z sha 9d97776 :: found issues before merge. :: [P2] Request read scope for read-only CLI operations | [P2] Keep shared prompt locks owned by the acquiring bridge | [P2] Close the workspace store when the plugin is retired | [P2] Stop menu pointer events before drag handling | [P2] Preserve parameters on RPC bindings
  • reviewed 2026-07-11T07:17:58.271Z sha 9d97776 :: needs changes before merge. :: [P2] Request read scope for read-only CLI operations | [P2] Keep shared prompt locks owned by the acquiring bridge | [P2] Close the workspace store when the plugin is retired | [P2] Stop menu pointer events before drag handling | [P2] Preserve parameters on RPC bindings
  • reviewed 2026-07-11T07:56:55.993Z sha c13e0c6 :: needs changes before merge. :: [P2] Request read scope for read-only CLI operations | [P2] Keep shared prompt locks owned by the acquiring bridge | [P2] Close the workspace store when the plugin is retired | [P2] Stop menu pointer events before drag handling | [P2] Preserve approvals across workspace replacement | [P2] Count declined prompts in the rate limit
  • reviewed 2026-07-11T09:42:01.362Z sha 4825c2b :: needs real behavior proof before merge. :: [P2] Close the workspace store when the plugin is retired | [P3] Remove the release-owned changelog entry
  • reviewed 2026-07-11T09:52:24.803Z sha 4825c2b :: needs real behavior proof before merge. :: [P2] Register cleanup for the workspace store | [P2] Rebuild the bridge when widget bindings change | [P3] Remove the release-owned changelog entry
  • reviewed 2026-07-11T10:15:16.669Z sha 728e095 :: needs real behavior proof before merge. :: [P2] Register cleanup for the workspace store

@steipete
steipete force-pushed the dashboard/integration branch from 2e0ad1d to 6fa1b2b Compare July 11, 2026 04:39
@steipete

Copy link
Copy Markdown
Contributor Author

OpenClaw QA Artifacts

Summary

Live OpenAI gpt-5.4 proof: the agent created and refined its own six-widget workspace through dashboard tools. Screenshots show the resulting dashboard and operator widget controls.

Evidence

@steipete
steipete marked this pull request as ready for review July 11, 2026 04:49
@steipete
steipete requested a review from a team as a code owner July 11, 2026 04:49
@github-actions github-actions Bot added the dependencies-changed PR changes dependency-related files label Jul 11, 2026
@github-actions

github-actions Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Dependency Guard

This PR changes dependency-related files. Maintainers should confirm these changes are intentional.

Changed files:

  • extensions/workspaces/package.json
  • pnpm-lock.yaml

Maintainer follow-up:

  • Review whether the dependency changes are intentional.
  • Inspect resolved package deltas when lockfile, shrinkwrap, or workspace dependency policy changes are present.
  • Treat package-lock.json and npm-shrinkwrap.json diffs as security-review surfaces.
  • Run pnpm deps:changes:report -- --base-ref origin/main --markdown /tmp/dependency-changes.md --json /tmp/dependency-changes.json locally for detailed release-style evidence.

@github-actions

github-actions Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Dependency graph changes noted

This PR includes dependency graph changes. The dependency guard is informational because the PR author is a repository admin or a member of @openclaw/openclaw-secops.

  • Current SHA: a0a02ad1d2b3eea34e0db47bf0f3f931b9ee8413
  • Trusted actor: @steipete
  • Trusted role: pull request author; openclaw-secops

Security review is still recommended before merge when the dependency graph change is intentional.

@steipete
steipete force-pushed the dashboard/integration branch from 6fa1b2b to 6eeffe8 Compare July 11, 2026 04:55

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6fa1b2b5d4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +224 to +229
const onMessage = (event: MessageEvent): void => {
// IDENTITY accept filter — never compare origin strings (opaque origin = null).
if (event.source !== iframe.contentWindow) {
return;
}
bridge.handleMessage(event.data);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reject messages after widget iframe navigates away

For an approved custom widget that navigates its own iframe to an external URL, this source-only check still accepts messages from the same contentWindow WindowProxy; because the sandboxed origin is opaque and serve.ts's CSP does not include a navigate-to 'self' restriction, the external page can use the approved bridge grants (static binding values/theme and, when the manifest has prompt:send, prompt dispatch) even though those bytes were not what the operator approved. The bridge needs to revoke/deny after non-widget navigations or the served document needs to be prevented from navigating off the approved widget route.

Useful? React with 👍 / 👎.

Comment on lines +302 to +304
void resolveBinding(client, binding).then((result) => {
viewState.bindingResults.set(widget.id, result);
viewState.bindingLoads.delete(widget.id);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Ignore stale binding resolutions

When a workspace update or poll tick changes bindingVersion while a previous resolveBinding call is still pending, the older promise can resolve later and repopulate bindingResults for the same widget id after the cache was cleared (and can even overwrite the newer result). This leaves the Workspaces tab showing data from an older binding/doc version until a later refresh; capture key for the request and only write the result if viewState.bindingVersion still matches.

Useful? React with 👍 / 👎.

@clawsweeper clawsweeper Bot removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jul 11, 2026
@steipete
steipete force-pushed the dashboard/integration branch from 6eeffe8 to b4f1edf Compare July 11, 2026 05:01

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b4f1edfa70

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread extensions/workspaces/src/store.ts Outdated
this.db.prepare("DELETE FROM undo WHERE version = ?").run(row.version);
// transact() stamps the next version, so the restored document lands as a
// forward write rather than a rewind.
return validateWorkspaceDoc(JSON.parse(row.doc));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep widget approvals out of layout undo

When the undo snapshot was captured by dashboard.widget.approve or a later reject, this restores the full widgetsRegistry including status: "approved" and approvedFiles. Because both dashboard.workspace.undo is only operator.write-scoped and the dashboard_undo agent tool calls the same store.undo(), an actor that cannot hold operator.approvals can resurrect a previously approved custom widget after it was rejected, bypassing the separate approval gate. Keep approval state out of layout undo snapshots or require the approval scope for restoring them.

Useful? React with 👍 / 👎.

Comment on lines +216 to +220
await context.client.request("chat.send", {
sessionKey: context.sessionKey,
message: text,
deliver: false,
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include chat.send idempotency keys for widget prompts

For an approved widget with the prompt:send capability, this request is sent only after the operator confirms, but chat.send requires an idempotencyKey in its protocol schema (the normal chat composer passes the run id). Without that field the gateway rejects the call as invalid, so every widget prompt dispatch fails after confirmation instead of sending the prompt.

Useful? React with 👍 / 👎.

@clawsweeper clawsweeper Bot added proof: 📸 screenshot Contributor real behavior proof includes screenshot evidence. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. labels Jul 11, 2026
@steipete
steipete force-pushed the dashboard/integration branch from 728e095 to a0a02ad Compare July 11, 2026 10:22
@clawsweeper clawsweeper Bot removed the rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. label Jul 11, 2026
@steipete

Copy link
Copy Markdown
Contributor Author

Merged via squash.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a0a02ad1d2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

const entrypoint = requireString(record, "entrypoint", "widget.json");
// The entrypoint is a logical path served through the jail; normalize it the
// same way the serving route does so a manifest cannot name an out-of-dir file.
normalizeWorkspaceDataLogicalPath(entrypoint);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject non-HTML widget entrypoints

When an approved widget declares a servable but non-HTML entrypoint such as widget.svg, index.txt, or app.js, this validation accepts it and approval succeeds, but serveWidgetAsset only injects the openclawWorkspaceBridge bootstrap into text/html responses. The Control UI still mounts the iframe, so any widget that expects the documented bridge/data flow is approved into a permanently nonfunctional frame instead of failing clearly at approval time; require the entrypoint to be an HTML file or make the bootstrap work for every allowed entrypoint type.

Useful? React with 👍 / 👎.

name,
title,
entrypoint: "index.html",
bindings: [{ id: "value", source: "static", value: "Hello from your workspace widget." }],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Make the scaffolded binding usable by default

A scaffolded widget's generated HTML immediately requests the value binding, and this manifest advertises a default static value, but the parent bridge resolves only widget.bindings[bindingId] from the workspace document and ignores the manifest value. If the operator or agent adds custom:<name> without manually duplicating a value binding in the layout, the freshly scaffolded/approved widget renders binding_denied instead of the bundled “Hello” sample; either create/return a matching workspace binding with the scaffold flow or use the manifest static value as the default grant.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app: web-ui App: web-ui dependencies-changed PR changes dependency-related files docs Improvements or additions to documentation maintainer Maintainer-authored PR P2 Normal backlog priority with limited blast radius. plugin: workspaces Agent-composable Workspaces plugin size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant