Skip to content

th-e7aa17: SEP Phase 6 (smooth) — daemon ui/* relay + smooth-web components#159

Merged
brentrager merged 1 commit into
mainfrom
th-e7aa17-sep-phase6
Jul 3, 2026
Merged

th-e7aa17: SEP Phase 6 (smooth) — daemon ui/* relay + smooth-web components#159
brentrager merged 1 commit into
mainfrom
th-e7aa17-sep-phase6

Conversation

@brentrager

Copy link
Copy Markdown
Contributor

Problem

SEP Phase 3–5 gave a dispatched operative a live ExtensionHost, but on the daemon dispatch path it loaded headless with the engine's DefaultHostDelegate — so any extension ui/* request got -32001 NoUI. The todo and permission-gate demos couldn't surface a dialog/widget in the browser.

What this ships (smooth side of Phase 6)

Round-trip, reusing the host_tool callback channel (no new transport, no operative stdin protocol):

  • smooth-operative — new HttpUiProvider (HostDelegate) relays each ui/* to Big Smooth via POST {SMOOTH_NARC_URL}/api/ui/request (bearer SMOOTH_HOST_TOKEN). Wired in load_pretrusted_extension_host: under dispatch it declares the full UI capability set (select/confirm/input/notify/set_status/set_widget/set_title) in mode web; on a bare local run it falls back to the headless DefaultHostDelegate. Every other delegate method keeps the engine default (kv → file, exec/session → denied). Relay failure degrades to {cancelled}/{} so a broken daemon never crashes a turn.
  • smooth-bigsmoothPOST /api/ui/request (operative→daemon, bearer-authed) broadcasts a UiRequest ServerEvent and, for interactive kinds, parks a oneshot in AppState.ui_pending until POST /api/ui/answer (frontend→daemon) fires it. Unattended (no client connected) → {cancelled} immediately; SMOOTH_AUTO_MODE=bypass → auto-confirm confirm (audited); else wait SMOOTH_UI_TIMEOUT_SECS (default 120s) → {cancelled}. New UiResolved event dismisses the dialog on other clients.
  • smooth-web — global UiRelay overlay (mounted in Layout, own /ws subscription): modal for select/confirm/input (posts /api/ui/answer), toast for notify, chrome for set_status/set_title, docked panel for set_widget. Render blocks markdown/keyvalue/progress/table/diff/stack, each with the mandatory text fallback.

Also removes the dangling smooth-plugin workspace dependency entry (crate deleted in Phase 5).

Tests

  • ui_relay.rs: bearer rejection, one-way returns immediately, no-receivers cancels, live interactive round-trip (park → broadcast → answer → resolve), unknown-request ack. All green (dolt-backed, real AppState).
  • ui_relay_provider.rs: interactive classification, graceful degrade by kind, capability coverage.
  • events.rs: new variants added to the exhaustive serialize test.
  • pnpm build (web) green; cargo fmt --check + cargo clippy --workspace --all-targets green (pre-commit hook).

Coordination — smooth-daemon epic (th-c89c2a)

The epic branch th-c89c2a-smooth-daemon is 121 commits ahead of main and unmerged, and introduces a durable /api/event surface + a React control surface (its Phase 4, th-bd0def). This PR builds on current main, not the epic: it rides the event surface main already has (event_tx broadcast → /ws, the access/pending pattern) and adds /api/ui/* as additive seams. When the epic lands, the UiRequest/UiResolved events can move onto its /api/event SSE with no protocol change (same JSON frames). No file overlap with the epic's web work beyond mounting one component in Layout.

Not in this PR (Phase 6 item 3)

chat-widget select/confirm button-frames on the five smooth-operator servers live in the smooth-operator repo (spec + servers + widget) and ship as a separate PR.

🤖 Generated with Claude Code

…onents

A dispatched operative runs headless, so an extension's ui/* request had
nowhere to render (it got -32001 NoUI). Wire the round-trip:

- smooth-operative: new HttpUiProvider (HostDelegate) relays each ui/* to Big
  Smooth over the existing SMOOTH_NARC_URL + SMOOTH_HOST_TOKEN callback channel
  (POST /api/ui/request — same channel host_tool uses). Declares the full UI
  capability set at handshake when dispatched; falls back to the headless
  DefaultHostDelegate on bare local runs. Relay failure degrades to
  {cancelled}/{} so a broken daemon never crashes a turn.
- smooth-bigsmooth: /api/ui/{request,answer}. request broadcasts a UiRequest
  ServerEvent and, for interactive kinds, parks a oneshot until a frontend
  answers (POST /api/ui/answer), times out to {cancelled}, or (no client
  connected) cancels immediately. SMOOTH_AUTO_MODE=bypass auto-confirms
  policy-covered confirms with an audit entry.
- smooth-web: global UiRelay overlay — modal (select/confirm/input), toast
  (notify), chrome (set_status/set_widget/set_title). Render blocks
  markdown/keyvalue/progress/table/diff/stack with text fallback.

Also drops the dangling smooth-plugin workspace dep (crate deleted in Phase 5).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Jul 3, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 378ad60

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@smooai/smooth Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@brentrager brentrager merged commit 479035e into main Jul 3, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant