* feat(chat): native session scroll-back with import-aware history paging
* fix(chat): page imported session history
* fix(chat): restore terminal import snapshots
* fix(chat): gate cli import resolver on ignore flag to stop history recursion
* fix(chat): correct scroll-back types and drop orphaned recent-messages reader
* fix(ui): explain grayed-out rows in the new-session folder picker
Disabled device rows now carry a reason tooltip (device offline vs. no
folder-browsing support), the admin-gated folder trigger says it needs an
admin connection, and dimmed dot-directories are labeled as hidden folders.
* chore(i18n): sync locale bundles for new-session picker tooltips
* feat(ui): add a Create PR branch row with rate-limit staleness to session PR chips
* fix(ui): gate the Create PR row on a pushed branch
* improve(ui): gate the Create PR row on comparable pushed commits and count untracked work
* fix(ui): satisfy spread lint and align branch-row test fixtures
* chore(ui): translate Create PR branch row copy
* chore(ui): drop zh-CN retranslation churn from the aborted backfill
* fix(ui): guard branch diff stats against checkout diff drivers and fail closed without a default branch
* improve(ui): fold the new-session targets into click menus
The /new target controls become Cursor-style trigger menus instead of an
inline control row: a folder trigger opens the directory browser as a
floating panel (with editable path in its head for manual entry), and a
where trigger opens one menu holding Gateway/node selection plus the
worktree toggle with labeled base-branch/name fields; the agent picker
becomes a menu when several agents exist. Triggers show live labels
(folder basename, node name, a branch badge when a worktree is on), so
the row stays one quiet line even on phones. Menus reuse the shared
session-menu item styles and the chat composer's details/summary
dismissal contract (outside pointerdown, Escape restores trigger focus).
Node-bound drafts no longer show an inert worktree toggle; the item
disappears because managed worktrees are Gateway-only.
* fix(ui): harden the new-session trigger menus
Review fixes: the folder trigger no longer shows the Gateway workspace
basename for node sessions (an empty node folder means that node's
default directory); sibling menus now close on the details toggle so
keyboard-opened menus stay mutually exclusive; and phone menus anchor to
the trigger row full-width so a wide browser panel under a non-leftmost
trigger cannot overflow the viewport.
* fix(ui): keep filesystem roots visible in the folder trigger
folderDisplayName falls back to the raw path so picking '/' (or a drive
root) labels the trigger instead of leaving it blank.
* fix(ui): give the new-session menus the native-select keyboard contract
Opening a menu moves focus to its first item, ArrowUp/Down wrap through
the items (browser rows included), and Home/End jump to the edges,
restoring what the replaced native selects provided. Re-picking the
checked agent no longer resets the draft (the native select never fired
change for the same option).
* fix(ui): keep native key behavior in menu text fields
Menu arrow/Home/End navigation now ignores events from text inputs so
the browser path, base-branch (datalist), and worktree-name fields keep
their native caret and picker behavior.
* fix(ui): let typed paths apply without a directory listing
Admins on hosts without a working fs.listDir keep manual folder entry:
the browser head input tracks a draft path and Use this folder applies a
typed absolute path even when listing fails; shapeless fs.listDir
payloads render as an empty directory instead of crashing the render.
The folder menu also initializes browser state before the focus hook
captures updateComplete, so keyboard-opened menus focus their first row
as advertised.
* fix(ui): apply exactly the shown browser path
Use this folder now applies the head input's absolute path only; the
draft syncs to every listed directory, so browsing is unchanged while an
edited path can never lose to a stale listing.
* fix(ui): let the browser express host-default directories
Review fixes: a cleared path input now applies '' — the host's default
directory (workspace on the Gateway, home on a node) — restoring the
state the replaced clearable folder textbox could express, and directory
results no longer overwrite a path the user typed while the listing was
in flight. E2E covers clearing back to the node default.
* fix(ui): apply the requested directory during browser navigation
loadBrowser assigns the head input the destination it is fetching, so a
mid-flight Use this folder applies where the user is heading instead of
the directory they just left.
* feat(ui): auto-load older catalog transcript on scroll with a stable viewport
* fix(ui): surface the load-older fallback when auto-load is blocked on a short thread
* fix(ui): clamp oversized aggregated tool output in catalog transcripts
* fix(ui): stop catalog auto-pagination when an older read makes no progress
* fix(ui): continue catalog paging through advancing empty pages; exhaust only on a stalled cursor
* fix(ui): detect catalog cursor cycles to bound auto-pagination
* feat(mac): swap dashboard titlebar buttons with sidebar state
The Control UI reports sidebar collapsed/width over a new openclawNav
WKScriptMessage; the titlebar accessory shows toggle+back/forward
right-aligned to the sidebar edge while expanded, and
toggle+search+new-session while collapsed. Search opens the command
palette and + opens the new-session surface via openclaw:native-*
events. Older gateway bundles that never report keep the shipped
toggle/back/forward layout.
Refs #105129
* docs: describe state-dependent mac titlebar buttons
The /new draft page now renders the same welcome block as the empty-chat
start screen, with the draft controls folded in launcher-style: hero on
top, a quiet borderless target row (agent, node, folder, worktree)
directly above the mid-screen composer, and the recent chats below. The
page reuses renderWelcomeState via hint/composer slots so the surfaces
cannot drift apart; the shell treats /new as a chat-like route that owns
its scrolling, with the top inset doubling as the native titlebar drag
band. The welcome is content-sized on this route so a tall draft (open
folder browser, short window) stays scrollable from the top. Also
repairs the stale new-session e2e heading wait (the h1 was removed in
Closes#105068
Rebuilds the Control UI cron page as a sidebar-first Automations master-detail workspace: a task list pane (All/Active/Paused tabs, search, filter popover, curated emoji suggestion cards, scheduler footer) beside a detail pane that edits the selected task inline (prompt, row-style Details/Frequency groups, collapsible Advanced, per-task run history) and shows all-task recent activity when nothing is selected. Create & run now kicks a new task immediately after creation. The modal editor and quick-create wizard are retired; the automation-ideas catalog from #104761 is ported into the new suggestions list. Nav item renamed to Automations and pinned by default; route (/cron) and cron.* RPC contracts unchanged.
Closes#104217
Co-authored-by: Claude <noreply@anthropic.com>
* feat(gateway): standalone approval deep-link page
Squash-rebased #103698 segment onto the typed-actions tip on current main.
Adds the tokenless /approve/{id} standalone approval UI served by the
gateway control-UI router, gateway-store ephemeral login fix (selectGateway
only on changed URL), and approval-page i18n strings.
Drift reconciliation: union-merged i18n translation-memory caches and
regenerated locale metadata via control-ui-i18n sync (33 approvalPage keys
remain English fallbacks in minor locales, as on the original branch).
(cherry picked from commit c82f56011bfe9f0debb8ebe052f14590c187340d)
(cherry picked from commit 80f5636a8b67a3d6a5f330620541a39d4ef3fe6f)
(cherry picked from commit a3e300d337a92bf1b593a0bb52b7ec4549910a21)
(cherry picked from commit 860ad01f9658bba54cb3d59644e5e1a032c0bb3f)
(cherry picked from commit d8b697b28a847950a1797da07fc0ae4c24928492)
(cherry picked from commit 8a654e3271fd5721358812f961a7a7bb386d6726)
(cherry picked from commit 2f776d2e7bf910271fcbd9697597f6f5478b0f30)
(cherry picked from commit 14e64e6e4d763be830830801eeec1a6f17441688)
(cherry picked from commit 4df9ec828d0034fdc76540ff0fe341e599b0f281)
(cherry picked from commit e52968f4ab)
(cherry picked from commit 7214fc25013966f99ff4b2d506aa2f5f789e2113)
(cherry picked from commit c6259ff79787c759c136086e1a54c9ead8e89539)
(cherry picked from commit a58d9f271d27481615f0c94a828704c563d5354f)
(cherry picked from commit dcf1cb6d98)
* fix(ui): preserve approval page gateway auth
* fix(ui): keep approval disconnects fail closed
* style(ui): format approval disconnect copy
* feat(ui): replace Overview page with Connection settings and sidebar attention chips
* feat(ui): open new-session drafts on the chat start screen hero
* refactor(ui): drop old overview-hints paths after rename
* chore(i18n): sync control-ui locale bundles for connection/palette/attention keys
* test(ui): expect attention slot above the sidebar update card
* chore(i18n): re-sync locale bundles after rebase onto main
* test(ui): stub sidebar-attention RPCs in app-sidebar unit tests
* test(ui): use a non-secret-shaped token fixture in connection view test
* refactor(ui): destructure gateway connection in connection settings draft
* refactor(ui): keep connection settings code out of secret-scanner shapes
* test(ui): expect Connection in the settings Connections group
* improve(ui): idle-refresh sidebar attention chips for always-visible windows
* chore(i18n): refresh raw-copy baseline after rebase
* docs(ui): note the unknown-route chat fallback covers retired paths
* feat(ui): redesign composer run-status indicator as chrome-free status text
* feat(ui): replace run-status text with transcript working spark
* fix(ui): keep working spark through reloads, dodge running tool rows, show mobile interrupted toast
* fix(ui): satisfy lint on tool-stream marker access
* fix(control-ui): use canonical thinkingDefault key in Quick Config
The Control UI Quick Config panel was reading and writing the
non-canonical key agents.defaults.thinkingLevel, which is rejected
by the gateway's strict Zod schema with INVALID_REQUEST errors.
Fix: read from and write to agents.defaults.thinkingDefault instead.
The schema already defines this key and the runtime already reads it
at cfg.agents?.defaults?.thinkingDefault, so this is purely a UI path
correction with zero runtime changes.
The fast-mode control is left unchanged pending a maintainer decision
on its intended scope (per-agent, per-model, or new global default).
Refs #104501
* test(control-ui): prove quick thinking persistence
* docs(changelog): note Quick Config thinking fix
* chore: keep changelog release-owned
---------
Co-authored-by: Erick Kinnee <erick@ekinnee.dev>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
The Codex/Claude sidebar sections leaked long titles across the row
timestamps and past the sidebar edge because the row body was never a
flex container, so the title span stayed inline and ellipsis rules did
not apply. The sections also rendered every loaded session (Codex swept
up to 100 catalog pages per host every 30s) with no height cap, growing
past the sidebar shell and painting over the footer.
Rows now reuse the regular session-row __text anatomy so titles
ellipsize, the sidebar renders only the newest 10 sessions per host
with a derived truncation note (the full catalog stays on the sessions
page), each section shrinks and scrolls inside the sidebar body, and
the shell body clips as a final guard. The sidebar full-catalog
hydration loop and the one-off __body class are deleted.
Formatting proof: oxfmt --check clean on changed files via Testbox
tbx_01kx92cwpr1x9ccp2fgn441y8h (local hook skipped: no node_modules).
Fixes#104584
* feat(dashboard): modular dashboard — workspace store, Workspaces tab, sandboxed custom widgets
Squashes openclaw/openclaw#101094 + #101097 + #101098 onto current main and
applies the maintainer review fixes to the backend control plane.
Co-authored-by: 100yenadmin <239388517+100yenadmin@users.noreply.github.com>
* fix(dashboard): UI review fixes — grid, error boundary, embed sandbox, locale
* fix(dashboard): make the CLI and agent broadcasts actually reachable
Three defects only a live run surfaces, all invisible to the unit suites:
- The plugin claimed the CLI command name `dashboard`, which core already owns
(it opens the Control UI). A plugin CLI group that overlaps a core command is
dropped at registration behind a `logger.debug`, so the entire CLI face was
unreachable while `cli.test.ts` kept passing against its own Commander
program. Renamed to `openclaw workspaces`, matching the tab it drives.
- The manifest never declared `activation.onCommands`, so the CLI root resolved
to no owning plugin even once the name was free.
- `dashboard.widget.approve` needs `operator.approvals`; the CLI asked for
`operator.write` on every call. It now requests the approvals scope only for
the approve call, matching `operator-approvals-client.ts`.
Also: agent tools resolved their broadcast from the plugin runtime's
gateway-request scope, an AsyncLocalStorage set only around gateway RPCs and
plugin HTTP routes. An agent turn started from a channel, cron, or heartbeat
therefore wrote the document without emitting `plugin.dashboard.changed`, so an
open Control UI never saw the edit — the feature's headline promise. The gateway
broadcast is server-lifetime, so the plugin now remembers it in a single slot and
agent tools fall back to it.
* docs(web): document dashboard workspaces, provenance, and the custom-widget sandbox
* fix(dashboard): agent-tool ergonomics + close two approval-boundary gaps
From a source-blind agent driving the dashboard_* tools with nothing but their
schemas, and from a Codex review of the hardening delta.
- dashboard_widget_update could never succeed. It passed its whole parameter
record to the patch reader, whose allowlist rejects the very `tab`/`id` keys
the tool's own schema marks required, so every call died on
"unexpected param: tab". Its test only ran Value.Check against the schema and
never executed the tool.
- dashboard_data_read surfaced an `rpc` binding as a thrown error, though its
description promised `binding_client_resolved`. It now returns that as a
result the model can act on.
- Valid widget kinds and the rpc allowlist were undiscoverable: a model saw only
"builtin:<name> or custom:<name>" and "Allowlisted gateway read method", then
brute-forced ~40 calls against errors that named no alternatives. Both schemas
and both validator errors now enumerate them, and the kind description says
what each builtin renders and which binding id it reads. widget_move documents
that grid and toTab are exclusive; widget_scaffold says an operator must
approve, because no agent tool can.
- workspace.replace could mint a pending registry entry for a name that was
never scaffolded. An operator could then approve a widget whose code did not
exist yet, and the agent could write it afterwards. Registry entries now come
from dashboard_widget_scaffold and nowhere else, and approve refuses a name
with no manifest on disk.
- dashboard.widget.approve answered with the whole workspace document, so a
connection holding only operator.approvals could read it through the approvals
door. It now returns the registry entry it changed.
* fix(dashboard): approval pins the code it approves
Codex review found the scaffold-before-approval gate still nameable rather than
binding: approve only proved that widget.json parsed, and the Control UI loaded a
hardcoded index.html rather than the manifest's entrypoint. An agent could
scaffold a widget, win approval on an innocuous or absent entrypoint, then write
the real payload afterwards — code appearing after the human said yes.
Approval now hashes every servable file in the widget directory and stores the
digests on the registry entry, refusing a manifest whose declared entrypoint is
missing. The asset route re-hashes each file it reads and 404s anything that does
not match, so a file edited or added after approval never reaches a browser. The
Control UI loads the manifest's entrypoint, which is the file that was hashed.
The content-type allowlist moves to manifest.ts so the set of files approval
hashes and the set the route can serve cannot drift apart.
Proof, against a running gateway: scaffold -> 404, approve -> 200, rewrite
index.html -> 404, add late.js -> 404.
* fix(dashboard): parse the approved manifest from the bytes that were hashed
Codex found a TOCTOU in the approval path: it loaded and validated widget.json,
then walked the directory again to compute the digests. An agent could swap
widget.json between the two reads, so the operator validated one entrypoint while
the digest froze — and the Control UI later mounted — a different one.
snapshotApprovedWidget now reads the widget directory once: it hashes every
servable file, parses the manifest out of the same widget.json bytes it hashed,
and requires the declared entrypoint to be among them.
Proof, against a running gateway: approve -> index.html 200; rewrite widget.json
to point at evil.html and drop evil.html in -> both 404.
* fix(dashboard): cap approval asset reads; bound the grid fallback search
Two findings from the fourth Codex pass.
Approval hashes agent-authored files that are untrusted until it runs, and read
each one into memory with no size check — dropping one huge .png into a scaffold
directory would stall or OOM the gateway during approve. Sizes are now checked
before the read, with a 2 MB per-file and 8 MB total cap.
nearestFreeSlot searched one band below the lowest occupied row, so a crowded
layout near the bottom could return y=500 as the closest free slot: a placement
the store rejects, which the UI applies optimistically and then snaps back. The
search now stops at the last row a widget of that height can legally occupy.
* fix(dashboard): refuse oversized widget assets before reading them
Approved widget files stay writable and the asset route is unauthenticated, so
swapping an approved small file for a very large one made every GET buffer the
whole file before the digest check rejected it. The route now refuses anything
past the same per-file cap approval enforces, on the stat it already performs.
* fix(dashboard): enforce widget approval boundaries
* docs(changelog): note modular dashboard workspaces
* fix(dashboard): enforce static custom-widget data boundary
* fix(dashboard): satisfy UI lint
* test(dashboard): avoid legacy proto access
* feat(dashboard): make plugin opt-in
* docs(dashboard): refresh workspaces map
* refactor(workspaces): standardize plugin naming
* fix(workspaces): make widget prompt sends idempotent
* docs(workspaces): fix internal path references
* test(workspaces): make prompt assertion lint-safe
* test(workspaces): type prompt request mock
* fix(workspaces): harden approval and binding boundaries
* test(workspaces): complete stale binding client mock
* fix(workspaces): harden widget file boundaries
* fix(workspaces): scope custom widget capabilities
* fix(workspaces): align approval provenance
* fix(workspaces): close branch contract gaps
* test(workspaces): complete builtin context fixtures
* fix(workspaces): aggregate overview usage
* chore(workspaces): defer release note
* chore(workspaces): refresh i18n metadata
---------
Co-authored-by: 100yenadmin <239388517+100yenadmin@users.noreply.github.com>
The sidebar footer icon row got crowded with the inline branch label.
The dev-checkout branch now renders on a slim tinted strip above the
icon bar with a git-branch icon, keeping the full name readable and
the footer controls uncluttered.
* feat: sidebar update card (web + macOS) with app-first mac update flow and Sparkle beta track
Squashed from claude/update-notification-display-c6cfb9 after semantic merge
with #104178 (channel-aware CLI installs). See PR #104171 body for details.
* chore(i18n): resync generated inventories after rebase
* chore(i18n): resync locale metadata after rebase
Source-checkout (non-release) gateways now report their git branch in the
Control UI bootstrap config; the sidebar footer renders it in red next to
the connection dot. Package installs, main/master, and detached HEAD never
show it.
Adds a git-backed session diff panel to the Control UI, complementing the existing PR status chips.
New sessions.diff gateway method (operator.read): resolves the session checkout and returns structured per-file diffs (status, renames, +/- counts, capped unified patch) of branch + uncommitted + untracked work against the default-branch merge base. Hardened against git textconv execution and hardlinked out-of-tree content leaks; handles repos before their first commit via the empty-tree base.
Control UI renders a "Changes" panel in the chat detail sidebar with collapsible per-file diffs, hunk-gap markers, stat chips, and untracked/binary badges, gated on gateway method advertisement. Schemas additive (Swift models regenerated), 20 locales translated.
Closes#104182