Commit Graph

18 Commits

Author SHA1 Message Date
Peter Steinberger fd4404d0b6 fix(dashboard): open widget links through the host instead of sandbox popups (#128125)
* fix(dashboard): open widget links through the host instead of sandbox popups

Widget frames granted allow-popups/allow-popups-to-escape-sandbox to every
rendered widget, including grantState "none" widgets that never reach an
operator approval card. CSP has no navigate-to, so a popup bypassed the
connect-src 'none' containment those widgets otherwise run under. Scripted
window.open was blocked only by the document guard's open lock, which is
emitted solely when blockDescendantFrames is set, so two unrelated knobs had
to agree for the boundary to hold.

Restore both sandbox strings and route user-clicked links through the existing
ticket-bound bridge: a trusted click on a ticketed board widget's http(s)
target="_blank" anchor forwards to the Control UI, which opens the tab with
noopener,noreferrer. Links keep working for every rendered widget, the host
owns the scheme filter instead of trusting widget-authored rel attributes, and
scripted popups are impossible by construction rather than by coincidence.

Also collapses the unreachable controlUiBaseUrl branch and corrects both docs
pages, including the claim that links opened for every rendered widget, which
was never true for MCP-App board widgets.

Follow-up to #127315; refs #127314.

* fix(dashboard): open widget links through the Control UI external-url owner

The widget bridge opened links with a local window.open helper, which the
lint:ui:no-raw-window-open boundary guard rejects. Delegate to
openExternalUrlSafe(), the Control UI owner for external navigation: it
applies noopener/noreferrer, detaches the opener handle, and keeps one
canonical open path. The bridge keeps a narrower absolute-http(s) gate,
since widget-supplied links must not reach blob: URLs that the shared
external-link policy permits.

* fix(dashboard): route middle-button widget links and honor canceled clicks

Address ClawSweeper review on #128125.

The wrapper only handled primary click, so with popup permission removed from
both sandbox layers a middle-button activation silently did nothing. It also
listened on capture, so a widget handler's preventDefault could not cancel the
link the wrapper then opened itself.

Match the Control UI activation contract in ui/src/app/native-link-routing.ts:
listen for click and auxclick, gate on !defaultPrevented && !shiftKey &&
!altKey && (click+button 0 || auxclick+button 1), and listen on bubble so
widget cancellation stays effective. A middle click now routes for any widget
link, not just target=_blank, since middle-click always means new tab and
leaving plain links unrouted would keep the same silent-no-op class.

Move the wrapped-document test into src/canvas/wrap.test.ts: it exercises
buildWidgetDocument, so it belongs with its owner, and the added assertions
would otherwise push widget-tool.test.ts past the max-lines cap that AGENTS.md
forbids suppressing.
2026-08-23 08:47:04 -07:00
Hannes Rudolph 5b67b61964 feat(dashboard): let widgets run granted actions and open links (#127315) 2026-08-21 14:57:40 -06:00
Vyctor H. Brzezowski 68ae235318 improve(ui): refine chat widget host and image actions (#125250)
* improve(ui): refine chat widget host actions

* fix(ui): keep widget pin presentation unchanged

* fix(ui): compact widget actions and restore rounded host

* style(ui): format widget action menu

* test(canvas): refresh widget host document contract

* test(ui): model the widget host wrapper

* fix(ui): preserve retained widget downloads

* fix(ci): register Codex tool coverage

* fix(protocol): keep version exports compact

* fix(ui): preserve widget action outcomes

---------

Co-authored-by: RoboClaw <309084314+roboclaw-bot@users.noreply.github.com>
2026-08-20 05:27:56 -03:00
Peter Steinberger ae55a4090c refactor(canvas): make the panel a widget presenter (#126030)
* refactor(canvas): retire legacy host and commands

* refactor(apple): narrow shared Canvas contracts

* refactor(macos): keep Canvas as widget presenter

* refactor(ios): remove Canvas client

* refactor(android): remove Canvas client

* refactor(linux): remove Canvas client

* fix(ci): isolate native locale artifacts

* fix(linux): regenerate companion lockfile

* fix(canvas): refresh native tool display metadata

* test(canvas): align coverage with presenter surface

* test(canvas): remove obsolete asset root seam

* test(canvas): stabilize retirement CI coverage

* refactor(swift): remove orphaned resource wrapper

* test(ios): remove retired canvas layout assertion

* fix(macos): reserve retired canvas command namespace

* refactor(macos): isolate canvas command policy

* fix(canvas): select only eligible macOS panels

* fix(canvas): keep panel selection plugin-owned
2026-08-19 08:21:07 -07:00
Peter Steinberger 97557ec3f5 fix(widgets): route show_widget through Discord Activities (#126294)
* refactor(widgets): unify Discord presentation

* fix(discord): keep incomplete Activity routes private

* fix(discord): require usable Activity accounts

* docs(discord): clarify hidden Activity routes
2026-08-19 05:41:37 -07:00
Peter Steinberger 3a96fbd64d feat(canvas): present widgets on native device panels (#125818)
* feat(canvas): present widgets on native panels

* refactor(agents): inline gateway caller selection

* fix(canvas): restrict widget panels to macOS

* refactor(agents): reuse resolved spawn workspace
2026-08-18 11:05:41 -07:00
Peter Steinberger ba3711bb82 feat(linux): render widgets in Quick Chat (#111933) 2026-07-20 11:56:03 -07:00
Peter Steinberger 154a49a78b feat(dashboard): widget capabilities — net CSP, host tools, hardened shared sandbox (#111687)
* feat(boards): enforce widget capability grants

* feat(ui): expose dashboard widget capabilities

* test(dashboard): align rebased gateway contracts

* refactor(dashboard): finish capability alignment

* chore: internalize unused board sandbox exports

* test(dashboard): bind grants to widget instances

* fix(dashboard): close widget authority lifetime gaps

* refactor(ui): preserve modular widget frame ownership

* test(ui): preserve explicit MCP App lease refresh

* test(ui): use shared dashboard sandbox server

* fix(ui): settle widget requests on reconnect

* fix(ui): retry transient widget document loads

* refactor(dashboard): document WebRTC egress residual

* fix(dashboard): clear landing checks
2026-07-20 06:54:44 -07:00
Peter Steinberger 06480d767c feat(dashboard): stitch session dashboards end-to-end — live provider, show_widget pin, board commands (#111218)
* feat(dashboard): pin canvas widgets through board domain

* feat(control-ui): wire session dashboards end to end

* fix(control-ui): preserve direct reset confirmation

* fix(control-ui): localize dashboard default tab

* test(dashboard): satisfy changed typecheck gate

* fix(dashboard): satisfy changed lint gate

* fix(dashboard): harden pinned widget lifecycle

* fix(dashboard): use extracted mock normalizer

* test(dashboard): reconcile hardened board admission tests

* fix(dashboard): align grant and canvas protocol requests

* fix(dashboard): preserve refresh and pin invariants

* fix(boards): treat missing board tables as empty on read-only paths

* fix(boards): accept read-only handles in board table probe

* fix(control-ui): preserve virtual board widget types

* fix(dashboard): align view types with protocol

* fix(dashboard): keep resolved board view typed

* chore(canvas): drop test-only html read wrapper

* chore(release): defer dashboard note generation

* fix(protocol): align board widget put params type

* fix(control-ui): preserve queued reset approval

* fix(control-ui): gate dashboard pinning capability

* chore(test): isolate dashboard capability fixture

* fix(control-ui): refresh dashboard pin affordance
2026-07-19 12:36:54 -07:00
Peter Steinberger ec998a0f3f feat(canvas): export widgets as PNG — copy or download from the card menu (#110992)
* feat(canvas): export widgets as PNG — copy to clipboard or download from the card menu

* fix(ui): widget-export lint, knip, and lines-budget cleanup
2026-07-19 00:39:59 +01:00
Peter Steinberger 242093fb00 fix(ui): dark theme AA contrast for coral fills — deepen primary/destructive, add widget accent-fill token (#110957) 2026-07-18 21:56:24 +01:00
Peter Steinberger 1cacb12c4d feat(canvas): give show_widget a design system — theme tokens, base styles, live host theme bridge (#110832) 2026-07-18 21:05:16 +01:00
Peter Steinberger 9710a1339e refactor(canvas): promote inline-widget hosting and show_widget to core (#110475)
* refactor(canvas): promote inline widgets to core

* docs(agents): note discord show_widget ownership at core registration

* ci: retrigger after GitHub scheduling outage

* fix(canvas): satisfy dead-code and test type checks
2026-07-18 14:40:45 +01:00
Peter Steinberger 28a3540f32 feat: add native inline widget support (#109212)
* feat: add native inline widget support

* refactor: simplify plugin surface refresh

* fix: preserve plugin surface refresh API

* fix: reload widgets after WebKit termination

* fix: harden native widget refresh

* fix: bound native widget recovery

* docs: defer native widget release note

* chore: refresh native i18n inventory

* fix: harden native widget delivery

* refactor: simplify native widget API surface

* fix: serialize native widget capability refresh

* fix: harden native widget recovery

* fix: recover native widget capabilities

* style: format widget refresh role selection

* fix: correct widget refresh formatting

* fix: harden cross-platform widget recovery

* fix: bind widget trust and recovery to routes

* chore: refresh native i18n inventory

* chore: regenerate Android gateway protocol

* test: remove unused release workflow read
2026-07-16 19:24:41 -07:00
Peter Steinberger 16d1f487fe feat(widgets): unify show_widget across surfaces (#108983)
* feat(widgets): unify show_widget across surfaces

* docs(canvas): keep lazy show_widget descriptor in sync with the loaded tool
2026-07-16 05:24:11 -07:00
Peter Steinberger 2d9584ccda feat(canvas): interactive widgets can send follow-up prompts in web chat (#108889)
* feat(canvas): let widget buttons send prompts into web chat

Inline show_widget documents get a sendPrompt(text) bridge: a private
MessageChannel offered to the Control UI before widget code runs. The chat
adopts only the first offer per frame, requires transient user activation,
a visible focused frame, plain text (no slash commands), and rate limits
10 prompts/minute per widget. Accepted prompts run the normal user send
path of the owning chat pane.

* fix(canvas): appease lint/knip/docs gates for widget prompts

Split widget prompt tests into their own file, drive validation and rate
limits through the real port path instead of exported internals, use
addEventListener on the adopted port, and regenerate docs_map.

* fix(canvas): install widget listeners per window for non-isolated tests

Module-boolean listener flags broke in shared vitest workers where each test
file gets a fresh jsdom window; key installation by window instead and give
port flushing more headroom.
2026-07-16 04:52:26 -07:00
Peter Steinberger b5ba0771f9 feat: carry gateway client capabilities through the CLI loopback backend (#102835)
* test: shorten loopback token fixtures below secret-scanner thresholds

* feat: carry gateway client capabilities through the CLI loopback backend

CLI-backed model runs now transport the originating client's declared
capabilities along the existing per-field loopback contract: RunCliAgentParams
gains clientCaps, prepare emits OPENCLAW_MCP_CLIENT_CAPS, the loopback header
template forwards x-openclaw-client-caps, the request context parses and
normalizes it (grant-authenticated callers keep ignoring spoofable headers),
and the loopback tool cache keys on a stable caps serialization so capless and
capped requests never share tool lists. Capability-gated tools such as
show_widget now work on CLI backends; the CLI session binding hash includes
caps, consistent with messageProvider. Cron and command-attempt runs stay
capless (fail closed).

Fixes #102577
2026-07-09 15:09:09 +01:00
Peter Steinberger 3ccf2a0739 feat: render inline web chat widgets via capability-gated show_widget tool (#101840)
Adds client-capability-gated tool availability: gateway clients declare
capabilities at connect (new inline-widgets cap), chat.send stamps them into
the run context, and every tool assembly path (embedded runner, queued
followups, Codex app-server harness, plugin-only construction plans) drops
tools whose requiredClientCaps the originating client did not declare. The
Canvas plugin ships the first such tool, show_widget: agents pass SVG or an
HTML fragment plus a title; the plugin hosts it as a bounded, retention-scoped
Canvas document and returns the existing canvas preview handle, which web chat
renders as a sandboxed iframe fitted to the widget's reported content height.
Widget frames never get allow-same-origin (per-preview sandbox ceiling,
including the sidebar path) and the Canvas host serves widget documents with a
CSP sandbox header so direct navigation runs in an opaque origin. Verified
live end-to-end on a Testbox with gpt-5.5 (screenshots on the PR). CLI-backed
model backends do not carry client caps yet and stay fail-closed (#102577).

Closes #101790
2026-07-09 12:29:50 +01:00