From 460e3e669c1c8d581c5348e4c6f844ae11e72484 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 18 Jul 2026 23:00:00 +0100 Subject: [PATCH] fix(canvas): restore shipped policy/host-disable contracts; harden workspaces doctor cleanup (#110927) * fix(dashboard): harden canvas and workspace compatibility * docs(web): add dashboard architecture * chore: remove release-owned changelog entry * docs: update dashboard architecture map --- docs/docs.json | 1 + docs/docs_map.md | 20 ++ docs/web/dashboard-architecture.md | 333 ++++++++++++++++++ src/agents/openclaw-tools.client-caps.test.ts | 20 ++ src/agents/openclaw-tools.ts | 3 +- src/agents/tool-policy-pipeline.test.ts | 38 ++ src/agents/tool-policy.ts | 16 +- src/canvas/config.ts | 18 + src/canvas/constants.ts | 9 +- src/flows/doctor-health-contributions.test.ts | 78 ++++ src/flows/doctor-health-contributions.ts | 13 +- ...tor-removed-workspaces-state-check.test.ts | 146 +++++++- .../doctor-removed-workspaces-state-check.ts | 120 ++++++- src/gateway/server-http.canvas.test.ts | 95 +++++ src/gateway/server-http.ts | 7 +- src/gateway/server-runtime-state.ts | 8 +- src/gateway/server.impl.ts | 7 +- 17 files changed, 903 insertions(+), 29 deletions(-) create mode 100644 docs/web/dashboard-architecture.md create mode 100644 src/canvas/config.ts create mode 100644 src/gateway/server-http.canvas.test.ts diff --git a/docs/docs.json b/docs/docs.json index 84755db44c41..12f325496fe0 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -1752,6 +1752,7 @@ "web/index", "web/control-ui", "web/dashboard", + "web/dashboard-architecture", "web/webchat", "web/tui", "web/lobster" diff --git a/docs/docs_map.md b/docs/docs_map.md index cf2443aca186..86bf5d914105 100644 --- a/docs/docs_map.md +++ b/docs/docs_map.md @@ -10551,6 +10551,26 @@ Do not edit it by hand; run `pnpm docs:map:gen`. - H2: Debugging/testing: dev server + remote Gateway - H2: Related +## web/dashboard-architecture.md + +- Route: /web/dashboard-architecture +- Headings: + - H2: Vision + - H2: Concepts + - H2: UX flows + - H2: Interaction tiers + - H2: Widget model and hosting + - H3: Widgets host content; MCP apps are one content kind + - H3: Transcript display: one widget card + - H3: Server-sourced widgets (pinned MCP apps) + - H2: Layout: fluid grid + - H2: Data model (per-agent DB) + - H2: Protocol surface + - H2: Agent tools + - H2: What this replaces + - H2: Non-goals (this program) + - H2: Implementation plan + ## web/dashboard.md - Route: /web/dashboard diff --git a/docs/web/dashboard-architecture.md b/docs/web/dashboard-architecture.md new file mode 100644 index 000000000000..5e7124a2115a --- /dev/null +++ b/docs/web/dashboard-architecture.md @@ -0,0 +1,333 @@ +--- +summary: "Session dashboards: architecture and implementation plan (technical design, pre-GA)" +read_when: + - Implementing or reviewing the session dashboard (boards) feature + - Changing widget hosting, the widget bridge, or board storage +title: "Dashboard Architecture" +--- + + +Technical design document for the session dashboard feature, written before and +during implementation. It is the source of truth for the build-out. When the +feature ships, `/web/dashboard` becomes the user-facing page and this page stays +as the architecture reference. + + +## Vision + +Working with an agent today is a text stream. The dashboard makes it a +workbench: the agent renders live, interactive widgets; the user pins them onto +a persistent surface; chat docks to the side (or hides) and the main content is +the board. You go from "talking to the agent" to "operating a control panel the +agent built for you" without ever leaving the session. + +Principles: + +- **A board is a face of a session, not a new object.** Every session (thread) + has two faces: the transcript and the board. A session with no pinned widgets + is plain chat. Pin one widget and the board exists. Boards inherit the + session's identity, agent ownership, naming, pinning, and lifecycle. There is + no `dashboard_create`, no board registry, no separate ACL model. +- **Agent parity.** Everything the user can do on a board, the agent can do + with tools: add/update/remove widgets, arrange them, manage tabs, switch the + visible tab, dock or hide the chat. +- **Native, not embedded.** The board is Lit components in the Control UI shell + (the same design system as the rest of the app). Only widget _content_ is + sandboxed in iframes. No URL bar, no browser chrome. +- **Small agent surface.** Widgets are addressed by stable name and updated in + place. Layout is a fluid auto-compacting grid; the agent speaks sizes and + anchors, never pixels or coordinates. +- **Capabilities over trust.** Widget code is arbitrary agent-authored HTML/JS + in a hard sandbox. Reach (gateway data, actions, network) exists only through + a declared, operator-granted capability manifest. + +## Concepts + +| Concept | Definition | +| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Session (thread) | Existing gateway session, keyed by stable `sessionKey`. Owned by an agent. | +| Board | The widget face of one session. Exists iff the session has widgets/tabs. Survives `/new`/`/reset` (attached to `sessionKey`, not the transcript). | +| Tab | A presentation page of a board: which widgets, their arrangement, and the chat dock state (`left`/`right`/`bottom`/`hidden`). Boards start with one implicit tab. | +| Widget | Named, sandboxed HTML/JS program owned by the session. Addressed as `sessionKey` + `name`. Updated in place by name. | +| Capability manifest | Per-widget declaration of reach: `data` (read bindings), `actions` (allowlisted verbs), `prompt` (send to session), `net` (allowed origins). | +| Pin (widget) | Moving a transcript widget onto the session's board (user affordance or agent tool arg). Unpin removes it from the board. | +| Pin (session) | Existing sidebar pinning of sessions. A pinned session with a board opens on its board face. | + +## UX flows + +- **Graduation:** agent calls `show_widget` in any chat → widget renders inline + in the transcript exactly as today → hover shows **Pin to dashboard** → widget + appears on the session's board. The agent can pass `pin: true` to do the same. +- **Board view:** a session with a board gets a face toggle (Chat / Dashboard). + Board view = tab strip (only when >1 tab) + fluid grid + docked chat pane. + Chat dock is resizable, movable (left/right/bottom), and collapsible exactly + like the sidebar. Per-tab dock state is remembered. +- **Drag:** user drags widgets; grid auto-compacts (widgets float up, neighbors + reflow). Resize by handle snaps to size steps. No pixel placement — for + anyone. +- **Reset warning:** `/new` / `/reset` on a board-bearing session asks for + confirmation in the web UI ("context resets, the dashboard stays") and keeps + the board. +- **Sidebar:** pinned sessions render their board face when they have one. + The Home session's board is the default "agent dashboard". +- **Interactions** (three tiers, see below): silent state events, visible + prompt sends, and automation triggers. + +## Interaction tiers + +1. **State events (default).** Widget UI interactions the model should know + about but not respond to. `bridge.emitState({...})` appends a structured + session notice (same mechanism as group-activity notices). No agent turn is + started; the model sees accumulated notices on its next run. +2. **Prompts (explicit talk).** `bridge.sendPrompt(text)` — requires user + activation; sends a visible user message into the session (the docked chat + shows it). Rate-limited; each send is user-confirmed unless the widget holds + the `prompt` capability grant. +3. **Automation.** `bridge.runAction(name, args)` — fires a manifest-declared + action. Initial verb set: `cron.trigger` (run an existing cron job now) and + `binding.refresh`. Cron jobs already run in visible, isolated run-sessions + and can use a cheaper model: that is the "small model powers the widget" + path. No hidden sessions anywhere. + +## Widget model and hosting + +Widget HTML/JS is authored by the agent (typically via `show_widget`), wrapped +in the standard document shell (CSP meta, size reporter, bridge bootstrap) and +rendered in `