Commit Graph

485 Commits

Author SHA1 Message Date
Patrick Erichsen 1b954672b1 feat(ui): move incognito to new-session rail (#124352) 2026-08-15 19:44:08 -07:00
Vyctor H. Brzezowski aed4510bd0 improve(ui): unify chat side rails in a tabbed panel (#123874)
* Control UI: unify chat rails in a tabbed panel

* fix(ui): compact chat side panel navigation

* Polish rail separators around the active tab

* Soften rail tab separators

* Inset the rail tab strip from the resize handle

* Remove the rail tab strip bottom rule

* Add compact close controls to rail tabs

* Redistribute rail tabs after closing a surface

* Restore bottom docking from the rail terminal

* Fade clipped rail tab labels at the edge

* Add drag reordering to rail tabs

* Polish terminal tab chrome

* Polish rail tab close controls

* Regularize rail tab separators

* Space rail tabs evenly

* Add feedback to inactive rail tabs

* Smooth rail tab selection

* Hide unavailable rail menu items

* Preserve browser multi-tab entry

* Support bottom-docked rail tabs

* Polish rail tab sizing

* Refine side panel actions

* Neutralize rail empty-state icons

* Separate dark rail surfaces

* Fade only clipped rail tab labels

* Compact rail header controls

* Refine the surface picker

* Raise and tighten the surface empty state

* Keep Browser and Tasks in the chat topbar

* Unify surface list cards

* Keep embedded rails inside their dock

* Reveal rail tab close actions on demand

* Show full rail labels only when clipped

* Let rail tabs use available space

* Align chat topbar surface controls

* Unify rail surface empty states

* Center rail tab glyphs

* Fade clipped rail labels smoothly

* Keep rail tab labels visible

* Tighten rail tab separator and label fade

* Keep the new-tab control beside the rail tabs

* Keep rail tab separators from reflowing on activation

* Anchor the new-tab control outside the rail tab scroll

* Scroll rail tabs at the floor instead of clipping them

* Tighten spacing between rail tabs

* Report an unusable terminal open response as a readable failure

* Keep the terminal session menu inside the panel

* Share one dock destination cluster across rails, terminal and browser

* Align the terminal mini rail with its management icons

* Tidy panel pickers, browser chrome and task detail retry

* Type the sidebar callbacks object so tsgo:ui passes

* Space out the panel picker rows

* Measure tab strip edges by rect and cancel superseded installs

* Validate every terminal session field and release unusable sessions

* Fold the terminal rail stretch rules into one

* test(ui): match rail tab checks to the tabbed panel header

Anchoring the new-tab control outside the scroll area wrapped the strip in
.side-panel__header-tabs, and every query written as a direct-child chain
started matching nothing. The assertions did not fail loudly: they compared
an empty label list, so three side-panel journeys and the background-tasks
rail went red on a selector detail rather than on behaviour.

Give the label query one named owner scoped to the panel's own header, so
Terminal and Browser strips in the panel body stay excluded while header
layout can keep moving. Freeing that width also means two short tabs now
fit where they used to be squeezed, so the closing step asserts the honest
invariant - a strip that fits again releases the fade - while the overflow
and tooltip regime stays proven earlier in the same journey.

Settle the shared side-panel helper on whichever surface renders: an empty
panel offers its type list and a populated one offers the header menu, and
probing before either existed left it waiting for a control that never
comes. Also drop a shadowed seed binding and an unused locator that oxlint
flagged in the same file.

* fix(ui): adapt session discussion slot to the unified panel layout

* test(ui): capture the bottom dock evidence with a resting divider

* fix(ui): drop the dead previousDock toggle path

* test(ui): align sibling suites with the tabbed panel decisions

* fix(ui): keep narrow-pane hides above the icon-button styling cascade

* fix(ui): create tooltip descriptions via ownerDocument

* fix(lint): merge duplicate terminal header styles and drop dead branches

* refactor(ui): extract embedded panel templates from the pane render

* refactor(ui): delete rail-era dead exports

* test(ui): repair type drift after the rebase

* fix(ui): keep the workspace toggle module-local and settle test caps

* fix(ui): give side-panel tabs their runtime and their own header actions

The tabbed side panel dropped two things the old rails owned. The discussion
tab lost its only runtime registration, so `openclaw-session-discussion` was
never defined and the tab rendered as a permanently blank box; it now loads
through the per-slot lazy runtime map like the other panel types.

Panels also have no header of their own anymore, so any action on the active
panel's content had to reach the shared header. The header now takes panel
contributed actions instead of a discussion-only open-url special case, which
brings back the side-chat "Clear thread" overflow menu — its gateway reset had
no reachable entry point at all in the embedded rail.

* test(ui): anchor the sidebar scroll proof to the panel host

The bounding contract moved from `.sidebar-column__panel` to
`.side-panel__panel`; mounting the detail panel under the removed class let it
grow instead of scroll, so the test failed for its harness rather than for the
behavior it guards.

* refactor(ui): move side-panel wiring out of the chat render monolith

`chat-pane-render.ts` crossed its line budget. The region callbacks are layout
policy, so they resolve in the layout module with the pane injecting only what
it owns, and the two panel-action producers collapse into the single header
contribution the region consumes.

* fix(ui): keep one terminal intent queue per document

A session route mounts the side-panel terminal beside the shell instance kept
for the bottom dock. Each owned a private action array over one sessionStorage
key, so their whole-array writes erased each other's intents and a freshly
mounted panel drained through a reconnect fence it never saw.

The queue, its persisted record, and the fence now belong to the document;
panels bind as executors while connected. The chat pane records a toggle intent
where it observes it, instead of parking the raw event in memory and handing it
to a panel that may not be mounted yet — a service-worker reload in that window
dropped the request, thread id and all, with nothing explaining why. That makes
the deferred-restore handshake dead weight, so it goes.

* fix(ui): let the panel that starts a terminal intent finish it

Opening a catalog terminal from a chat route mounts the side-panel terminal
right after the request, which swapped the queue's executor mid-flight and
stranded the in-flight action; the panel's own mount restore then opened a
plain session instead. The executor that began an action stays responsible for
it as long as it can still run.

* test(ui): cover closed legacy side docks

* refactor(ui): split side panel controllers

* fix(ui): stabilize embedded panel handoffs

* style(ui): format browser panel refresh

* test(ui): settle catalog prepend paint incrementally

* docs(ui): describe the unified chat side panel

* test(ui): settle catalog clock transitions

---------

Co-authored-by: Jason (Json) <263060202+fuller-stack-dev@users.noreply.github.com>
2026-08-15 20:40:00 -06:00
Jason (Json) 4490eaac9f feat(ui): open links in Control UI browser (#123912)
* feat(ui): open links in Control UI browser

* fix(ui): centralize Control UI link routing

* fix(ui): carry narrowed native link poster
2026-08-15 20:05:02 -06:00
Peter Steinberger d2be00e171 feat(ui): persistent emoji session icons in the sidebar (#124034)
* feat(gateway): persist emoji session icons

* feat(ui): add persistent session icon menu

* docs: explain persistent session icons

* docs: list Set icon in the session context menu enumeration

* test(gateway): canonicalize group defaults workspace path

* fix(ci): sync session icon protocol coverage

* fix(sessions): enforce RGI emoji sequences for session icons

ClawSweeper P2: single non-emoji graphemes like 漢 passed the segmenter-based predicate. Anchored \p{RGI_Emoji} admits exactly one interchange emoji sequence and deletes the length cap and ASCII carve-out.

* fix(sessions): compile RGI emoji validation
2026-08-15 00:15:55 -07:00
Jesse Merhi edb5adfbf5 Start new sessions with folder group defaults (#123276)
* feat(ui): add folder group session defaults

* fix(ci): align folder group contracts

* fix(protocol): refresh Android gateway methods

* fix(ui): reuse folder picker for group defaults

* fix(ui): harden session group defaults

* test(ui): align group defaults with current main

* test(ui): keep group catalog defaults path-free

* fix(ui): close folder group CI gaps

* fix(ui): satisfy folder group CI contracts

* fix(session-groups): enforce defaults safety

* test(gateway): keep group defaults in workspace

* fix(session-groups): enforce defaults participation

* fix(session-groups): close review authorization races

* fix(session-groups): canonicalize defaults authorization

---------

Co-authored-by: Jesse Merhi <jesse-merhi@users.noreply.github.com>
Co-authored-by: Jesse Merhi <openclaw@users.noreply.github.com>
2026-08-15 15:24:13 +10:00
Patrick Erichsen f726393812 fix(ui): offer permanent lobster dismissal (#123789)
* fix(ui): add persistent lobster dismissal

* fix(ui): keep the lobster dismiss menu from scrolling its own items

The dismiss menu anchors a synthetic trigger at the raw pointer position,
and the pet always sits on the sidebar footer ledge at the bottom of the
viewport. Web Awesome caps its popup to `--auto-size-available-height`,
and its `size` middleware runs after `flip`, so the menu was shrunk in
place to 60px against 64px of content instead of moving up. The two
dismissal items then scrolled inside a 6px overflow, which shows as a
scrollbar for anyone running the system setting that always renders them.

Clamp the anchor to the viewport the way every other pointer-anchored
menu already does (session-menu.ts:264, catalog-session-menu.ts:55,
native-link-menu.ts, sidebar-menus-controller.ts:270), so the popup keeps
the room it needs and renders both items in full.

* docs(web): describe the lobster dismiss menu options

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: vyctorbrzezowski <krzyszchweski@gmail.com>
2026-08-14 20:00:50 -03:00
ClawSweeper e03d1a42f8 improve(ui): collapse limited access notice (#123735)
* improve(ui): collapse limited access notice

* test(ui): follow limited access banner host

* test(ui): await limited access guidance host

* test(compaction): split context budget coverage

---------

Co-authored-by: RoboClaw <309084314+roboclaw-bot@users.noreply.github.com>
2026-08-14 10:25:28 -07:00
joshavant 6659007352 fix(models): preserve explicit discovery outcomes 2026-08-13 18:57:50 -05:00
Peter Steinberger 0f0d222c37 feat(ui): sync gateway browser viewport to the dock panel size (#123104)
* feat(ui): sync browser viewport to panel

* docs(web): note browser panel follows dock size

* refactor(ui): split browser panel input handling out of the controller

* refactor(ui): keep browser panel input host interface module-local

* fix(ui): fence browser viewport sync on dock close and document change

ClawSweeper review findings: a debounced resize could fire after the dock
closed (silently resizing the agent-controlled browser), and the per-target
resize guard survived tab/document changes, suppressing a needed re-sync when
revisiting a tab an agent had resized. Gate syncViewport on the panel being
open and clear the guard in invalidateViewOperations; both paths now have
regression tests that fail pre-fix.
2026-08-13 03:52:48 -07:00
Peter Steinberger cc2fc55f9b feat: portals — expose agent-run dev servers to the operator (#122536)
* feat(protocol): add portal methods and event

Bump the reviewed protocol owner-module count from 55 to 56.

* feat(gateway): add portal service and reverse proxy

* feat(agents): add portal tool

* fix(gateway): refine portal URL and proxy auth

* refactor(gateway): keep portal helper types private

* fix(gateway): declare portal transport service

* test(gateway): satisfy portal proxy lint

* test(gateway): narrow websocket payload types

* refactor(protocol): compact portal schema exports

* fix(gateway): export portal protocol types

* feat(ui): add portals page

* docs(gateway): add portals guide

* fix(gateway): dial portal targets via localhost dual-stack

Vite and other Node >=17 dev servers bind ::1 only for localhost, so a
fixed 127.0.0.1 dial 502s on the default path. Use hostname localhost
with family autoselection and rewrite Host to match.

* fix(gateway): type portal dual-stack connection

* fix: satisfy portal integration gates

* fix(gateway): isolate portal cookie jars per target

Cookies are hostname-scoped, not port-scoped, so the per-port origin
split alone let Gateway plugin-auth cookies reach agent-run targets.
Forward only cookies carrying this portal's own name prefix (stripped),
rewrite target Set-Cookie names to the prefixed form incl. the WS 101
handshake, and drop Domain attributes.

* fix(ui): detect unreachable portals behind proxied gateways

Probe the portal origin from the browser (no-cors, 4s timeout) and show
a recovery notice with the gateway-host URL instead of a dead iframe
when only the gateway port is exposed (Serve/Funnel/reverse proxy).
Docs: cookie isolation + reachability; zh-CN glossary entry.

* test(ui): satisfy portal reachability lint

* test(gateway): provide control UI request hosts

* chore(protocol): regenerate after rebase

* fix(gateway): namespace portal auth cookies by listener

* fix(gateway): scope portal token URLs to write-capable clients

The portal bearer token rides in the summary url/tokenQuery; portal.list
is operator.read and portal.changed fans out to read subscribers, so a
read-only client could harvest an openable URL. Make those fields
optional, redact them from read-scope list responses, and drop them from
every portal.changed broadcast; write/admin clients still receive them
and the UI refetches the list on change.

* docs(web): list the portals route

* fix(gateway): type portal open credentials

* docs(gateway): clarify portals PORT/PUBLIC_URL are agent-set

Opening a portal creates only the proxy listener; the agent sets PORT
and PUBLIC_URL in its own exec command, matching the portal tool
contract. Removes the implication of an automatic env handoff.

* chore(protocol): regenerate portal models

* style(gateway): format portal method-order assertions

Rebase union-merge left the portal.list assertion wrapped; oxfmt fits it
on one line.

* chore(plugin-sdk): refresh API baseline after rebase

* chore(plugin-sdk): refresh API baseline after rebase

* chore(protocol): refresh portal event order after rebase

* chore(plugin-sdk): refresh API baseline after rebase

* fix(gateway): pin portal referrer policy to no-referrer

The portal URL carries its bearer token in the query, and upstream
response headers are copied verbatim, so a target answering with
Referrer-Policy: unsafe-url could leak that URL to every third-party
origin it references. Force no-referrer after the copy and drop any
inbound Referer that still carries the token before forwarding.
2026-08-13 00:46:11 -07:00
Peter Steinberger c68039e7fb refactor(ui): one task-detail surface for rail and subagent clicks (#123003)
* refactor(ui): unify task details in sidebar panel

* docs(ui): describe task detail sidebar

* refactor(ui): collapse onSelectTask into onOpenTaskDetail

Two names for the same open-the-panel action; rows now call the
callback directly.

* fix(ui): clear rail open-task highlight when detail slot closes

openTaskId derived from sidebarContent alone, but region close only
collapses the layout and retains content, leaving a stale
aria-current row; gate the highlight on detail-slot visibility.

* fix(ui): keep requester sessions out of subagent transcripts and let reopen retry failed lookups

A queued subagent has no child session yet; falling back to its
sessionKey rendered the requester's conversation as the task
transcript. And a failed tasks.get permanently suppressed the panel's
render-driven load; selection now clears the recorded error so
reopening retries, matching the old rail behavior.
2026-08-12 21:51:07 -07:00
Peter Steinberger f5ad8735d1 feat(ui): open subagent details in chat sidebar (#122941)
* feat(ui): open subagent details in chat sidebar

* chore: drop changelog edit (release generation owns it)

* refactor(ui): drop duplicate close in subagent detail panel

The sidebar region header already owns a Close Details control in both
wide and narrow layouts; the panel-local X duplicated it 40px away.

* fix(ui): stop subagent transcript loader when pane presentation retires

Pane retention wipes sidebarContent directly, so the detail slot's
render-time reset can never run again; a pending refresh timer plus
incoming task events kept refetching chat.history for a hidden panel.

* docs(ui): note close-control ownership in subagent detail header

* fix(ui): break transcript renderer import cycle

* fix(ui): use shared action cursor for subagent rows
2026-08-12 20:11:13 -07:00
Peter Steinberger cef071582e feat: let limited browsers request admin access (#121459)
* feat(gateway): add live device scope upgrades

* feat(ui): add limited-access upgrade flow

* fix(protocol): refresh Swift scope upgrade models

* perf(ui): lazy-load device scope upgrades

* fix(ci): complete scope upgrade generated surfaces

* perf(ui): lazy-load GitHub link hovercards

* fix(ui): keep admin repair guidance focusable

* fix(ui): gate and refresh scope upgrade banner

* refactor(ui): keep gateway client within line budget

* fix(ci): align rebased scope upgrade checks

* fix(ui): resolve scope upgrade in browser tests

* fix(gateway): honor refreshed scope upgrade deadline

* fix(gateway): honor refreshed scope upgrade deadline

* fix(gateway): coalesce scope upgrade waiters

* fix(ui): gate scope upgrade actions

* chore(plugin-sdk): refresh rebased API baseline

* fix(scope-upgrade): return canonical request ids

* fix(ui): preserve gateway event type binding

* fix(protocol): generate scope upgrade result models

* fix(ui): preserve scope upgrade recovery guidance

* chore(plugin-sdk): refresh rebased API baseline

* test(ui): avoid scope upgrade navigation race

* docs(control-ui): clarify scope upgrade approver

* test(gateway): align appended method counts

* chore(plugin-sdk): refresh rebased API baseline

* refactor(ui): keep place picker within line budget

* chore(plugin-sdk): refresh rebased API baseline

* chore(plugin-sdk): refresh rebased API baseline

* chore(plugin-sdk): refresh rebased API baseline

* chore(plugin-sdk): refresh rebased API baseline

* chore(plugin-sdk): refresh rebased API baseline

* chore(plugin-sdk): refresh rebased API baseline

* chore(plugin-sdk): refresh rebased API baseline

* chore(plugin-sdk): refresh rebased API baseline

* chore(plugin-sdk): refresh rebased API baseline

* chore(plugin-sdk): refresh rebased API baseline

* chore(plugin-sdk): refresh rebased API baseline

* fix(gateway): preserve scope-upgrade browser origin
2026-08-12 18:32:00 -07:00
Peter Steinberger d44f70eb4b feat(pairing): one-paste device pairing via oc-pair setup links (#120768)
* feat(pairing): one-paste device pairing via oc-pair setup links

Implements milestone 3 from docs/plan/runners.md.

* fix(pairing): sign bootstrap handshake, keep URL candidates, wire pairing countdown

* test(gateway): update client callsite guard

* fix(pairing): preserve setup URL context paths

* fix(ui): keep pairing help aligned with setup mode

* fix(pairing): isolate bootstrap credentials

* perf(ui): keep one-paste pairing within bundle budget

* refactor(pairing): isolate native pair URL prefix parsing

* fix(pairing): preserve candidate lifecycle state

* fix(pairing): retire shared credentials after bootstrap

* fix(pairing): apply rotated manifest through client owner

* test(pairing): prove bootstrap retirement across reconnect

* fix(pairing): preserve native gateway context paths

* fix(pairing): carry native context paths through reconnect

* fix(ios): preserve encoded gateway context path

* chore(plugin-sdk): refresh pairing API baselines
2026-08-12 02:01:33 -07:00
Peter Steinberger 85a176b98a feat(ui): commit-scoped session diff viewer with file, sync, and view menus (#122470)
* feat(ui): commit-scoped session diff viewer with file, sync, and view menus

The Control UI session diff panel becomes a dense Amp-style viewer: per-file
menus (copy path, open file, reveal in file tree, open in editor), a Sync
Locally popover with a copyable git fetch command, view options (collapse all,
wrapping, split/unified layout), and a sticky footer that reports how far the
branch is ahead of its merge base and switches between all changes,
uncommitted work, and individual commits.

sessions.diff gains an additive scope param (all | uncommitted | commit) plus
commits, aheadCount, and mergeBase metadata. Commit-scope diffs read only the
object database, skip untracked collection, and bypass session-start baseline
filtering; unknown commits surface a typed unavailableReason. Offscreen file
bodies use content-visibility so large diffs stay responsive.

* fix(ui): satisfy session diff CI gates

* fix(gateway): fence commit-scoped session diffs to the advertised branch history

ClawSweeper found that commit-scoped sessions.diff accepted any commit resolvable in the checkout. Fence operator.read commit reads to the advertised merge-base..HEAD history and cover sibling-branch and base-history commits.
2026-08-12 01:08:33 -07:00
Peter Steinberger 87ecc4085d feat(webui): auto-request notification permission on first chat send (#122446)
* feat(webui): auto-request notification permission on first chat send

The Control UI now asks for notification permission automatically on the first chat message you send (once per browser/origin; web push in browsers, native prompt in the macOS app; denied permission is never re-asked automatically — Settings -> Notifications stays the manual path).

* fix(webui): preserve notification prompt gesture boundary

Restrict the one-shot notification prompt to direct non-command composer sends, invoke browser permission synchronously, and keep Settings documented as the management and recovery path.

* fix(webui): exclude deferred notification sends

Require an actively dispatching browser input event before consuming the notification one-shot, so retained catalog handoffs and other deferred sends cannot suppress the first real composer prompt.
2026-08-11 23:52:05 -07:00
Shakker 0e78a64df5 fix: simplify session companion grounding 2026-08-12 03:26:33 +01:00
Peter Steinberger 8876528f7c feat: clone GitHub projects from session picker (#121818)
* feat(projects): add managed GitHub clones

* feat(ui): clone GitHub projects from session picker

* fix(projects): integrate current gateway owners

* fix(protocol): derive Swift error detail accessors

* fix(ui): gate project cloning by scope

* test(gateway): isolate session prewarm probes

* revert: drop duplicate session prewarm repair
2026-08-11 18:49:16 -07:00
Peter Steinberger cd0a1235a3 feat: sync new-session preferences and recents by identity (#121816)
* feat(gateway): add identity preferences and project recents

* feat(ui): sync new-session identity state

* docs: explain identity-scoped session state

* test: track preference temp directories

* fix(gateway): preserve identity preference boundaries

* chore(protocol): refresh identity preference bindings

* test: refresh historical schema hashes

* style(gateway): format method order assertion

* fix(protocol): emit project recent Swift models

* test(gateway): track preference RPC release train

* fix(gateway): harden identity preference state

* fix(state): keep preference errors internal

* chore: refresh split plugin SDK baseline

* fix(gateway): use core session store loader

* refactor(state): fold additive migration checks

* chore: regenerate plugin SDK baseline

* chore: regenerate plugin SDK baseline

* chore: regenerate plugin SDK baseline

* chore: regenerate plugin SDK baseline

* chore: regenerate plugin SDK baseline

* chore: regenerate plugin SDK baseline

* chore: regenerate plugin SDK baseline

* test(ui): relocate identity recents e2e

* chore: regenerate plugin SDK baseline

* chore: regenerate plugin SDK baseline

* chore: regenerate plugin SDK baseline

* chore: regenerate plugin SDK baseline

* chore: regenerate plugin SDK baseline
2026-08-11 16:52:59 -07:00
joshavant c9a04ab33f docs(ui): document Activity inspector links 2026-08-11 18:44:38 -05:00
joshavant acb7f7192a fix(ui): support exact run execution selection 2026-08-11 18:44:38 -05:00
joshavant 7ab5d99a6c feat(ui): add durable run inspector 2026-08-11 18:44:38 -05:00
Shakker 70fbc35da2 fix: ground session companion context 2026-08-12 01:17:17 +02:00
Peter Steinberger 1dcac5b15f fix: open terminals no longer delay gateway updates (#121601)
* fix(gateway): allow updates with open terminals

* fix(updater): support terminal policy upgrades

* chore(plugin-sdk): refresh API baseline
2026-08-11 09:37:58 -07:00
Shakker 4def22ebfc docs: document coherent browser tab ownership 2026-08-11 18:20:36 +02:00
Peter Steinberger ea06d72e85 feat(secrets): manage team secrets in Control UI (#121724)
* feat(secrets): add gateway store settings

* perf(control-ui): trim secrets startup copy

* perf(control-ui): reduce secrets startup payload

* fix(secrets): harden store mutation refresh

* perf(control-ui): meet secrets startup budget

* test(control-ui): update secrets navigation copy

* fix(ui): pluralize secret-detection count and drop duplicated dialog hint

* chore(protocol): regenerate gateway clients and SDK baseline after rebase

* fix(gateway): merge secrets store methods after project RPCs in advertised order

* chore: leave changelog to release generation

* test(gateway): retain desktop launch train coverage
2026-08-11 07:20:38 +00:00
Vyctor H. Brzezowski 9327c10d2b feat(ui): edit a queued chat message in place (#121692)
* feat(ui): edit a queued chat message in place

The pencil on a queued row, or a double-click, lifts the message and its
attachments into the composer. The row stays where it is, marked Editing,
so the position the corrected message will take is visible rather than
promised. The composer shows an edit banner with an X to cancel.

Cancelling touches storage not at all, because the row never left the
queue. Sending retires the original and hands its position to the
replacement, which lands in the same slot. While an edit is open the
drain refuses that row, so a reconnect cannot deliver the text the
operator is visibly rewriting; the queue behind it waits, which is the
same contract the held position promises.

* fix(ui): retire an edited queued row only with its replacement

The send retired the source row first and admitted the replacement second,
so a rejected store write left the operator with neither: the original gone,
the replacement never persisted, and the attachments it dropped already
released. Admission now carries the source id and swaps both rows in one
store write, so a rejected write changes nothing and the edit stays open on
the row that is still there, which is what cancelling already promises.
Filtering the source before the cap check also keeps a replacement
admissible on a full queue.

The edit token recorded only the session key, but an outbox is scoped by
session and agent, and a raw global session keeps its key while the selected
agent changes underneath it. The token now carries the agent that owned the
row and every reader validates the whole scope, so a send after a switch
cannot retire a row in the outbox it left behind.

* fix(ui): hold an edited queued row across every pane

Panes share one outbox and one drain lane while composer state stays per
pane, so the hold that keeps a row from being delivered while it is rewritten
was invisible to the pane that drained it: a session event in a second split
pane resumed every stored outbox, saw no edit, and sent the original text.
The hold is now read across every pane subscribed to the outbox, which keeps
one source of truth — each pane's own scope-validated edit — and needs no
lifecycle of its own, so closing a pane or switching agent releases it by
simply no longer being an active edit.

The payloads an edit owns now travel with its token. The write that admits
the replacement retires the source row and tells every pane synchronously, so
by the time the send released the images the replacement had dropped, the row
holding them was already gone and nothing was released. That leaked the
payload and its object URL for the rest of the browser session.

* fix(ui): release attachments from cancelled queue edits
2026-08-11 01:59:56 -03:00
Vyctor H. Brzezowski 619f3c9ab1 feat(ui): identify project files in Control UI chat (#121775)
Recognized workspace paths in chat now read as their basename behind a
file-type glyph, so a file reference is identifiable before it is read.
Markdown, package manifests, TypeScript and other code, TSX/JSX components,
config/data, shell scripts, and images each get their own mark; anything else
falls back to a plain document. Paths sharing a basename keep the smallest
trailing suffix that tells them apart, and Windows paths keep their own
separator.

Classification wins over authoring syntax: a path written in backticks is a
file link first, so it drops the inline-code chip and renders exactly like a
bare path beside it. Code spans that are not file links keep the chip.

The full path stays addressable: it drives the file panel, the new tooltip,
and the message Copy action (which returns the original Markdown).
Author-written labels in [label](path) links are never rewritten, and text
that is not a recognizable path stays plain prose.

The glyph is painted as a masked ::before like the sibling GitHub mark, so it
stays out of the accessibility tree and out of copied text and follows the
link color in every theme. Extension classification moves to one shared
resolver that the file preview modal now uses too, replacing its own
code/text extension set.
2026-08-11 00:48:43 -03:00
Peter Steinberger 32894a3ba5 feat: start sessions from registered projects (#121465)
* feat(gateway): add project registry sessions

* fix(gateway): authorize project branch discovery

* test(gateway): reuse worktree test imports

* fix(protocol): register projects schema owner

* fix(gateway): gate project paths by write scope

* feat(control-ui): register projects from folder browser

* refactor(gateway): extract session diff baseline preparation

* fix(gateway): honor admin scope in project listings
2026-08-10 17:56:12 -07:00
Vyctor H. Brzezowski 1d989027cf feat(ui): let operators reorder queued chat messages (#121682)
Queue position becomes an explicit, operator-owned fact on ChatQueueItem
instead of an accident of arrival time and storage array order. One
comparator now serves the visible projection, the drain head selection,
the steer rebuild, and the alias merge, so what the composer shows is
what the Gateway receives.

Reordering lives entirely on one handle at the left of a queued row:
drag it, or focus it and press the arrow keys. Keyed rows keep focus on
the handle as it moves. Rows already handed to a run keep their place.

Also routes .chat-image-action through var(--cursor-action); its
hardcoded pointer was failing the cursor-policy test on main.
2026-08-10 21:44:40 -03:00
Vyctor H. Brzezowski 94490bd69b feat(ui): show GitHub link identity in Control UI chat (#121728)
* feat(ui): mark GitHub links in Control UI chat

Chat markdown anchors pointing at github.com now carry a decorative GitHub mark before their text, so a bare URL and a [#3434] shorthand read as the same destination. The mark is painted from the bundled brand path through a CSS mask on currentColor: it stays out of the accessibility tree and out of copied text, tracks the chat text size, and follows the link color in every theme. Image-only links such as badges and code spans stay undecorated.

* feat(ui): break long autolinked URLs at any character in chat

An autolinked URL is one unbreakable word, so the inherited overflow-wrap only splits it after every other option is exhausted: a long URL was pushed whole onto the next line, and with the new GitHub mark ahead of it the mark was left stranded alone at the end of the previous line. Autolinked anchors now carry markdown-bare-url and get unconditional character break opportunities, so the URL fills the line it starts on. Authored link labels keep word-boundary wrapping.

* fix(ui): drop resting underline on chat GitHub links

* test(ui): split markdown link tests into a sibling file

markdown.test.ts crossed the 1000-line oxlint budget once the GitHub-link
cases landed. Move the link-rendering describes (autolinks, explicit
protocol links, file links, bare-url marks, GitHub marks) into
markdown-links.test.ts. Pure move; no assertion changes.

* test(ui): assert GitHub links rest without an underline

Covers the resting/hover decoration contract through the real rendered
page, and pins the rule's scope with a non-GitHub link that keeps the
base underline.
2026-08-10 20:57:35 -03:00
Ittiz bad30d5a74 feat(ui): add generated image actions (#77017)
Give Control UI managed images bounded previews and shared full-image Open, Download, and Copy actions. Keep artifact access transcript-bound; the existing ticket is intentionally attachment-scoped to the lower-fidelity thumbnail.

Co-authored-by: Ittiz <github@daein.org>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-08-10 21:30:58 +05:30
Shakker c5a5ba2263 fix: preserve staged attachments across chat remounts 2026-08-10 16:10:18 +02:00
Peter Steinberger 4f046b5dc6 docs(web): document the sidebar unsent-draft pencil indicator 2026-08-10 04:56:43 -07:00
Peter Steinberger bab4546b41 refactor(gateway): remove dead rpc surfaces (#121387) 2026-08-10 02:41:57 -07:00
Peter Steinberger e0262e6aa4 feat(ui): add people session sorting (#121470)
* feat(ui): add people session sorting

Offer People sorting in the Control UI session sidebar when the Gateway reports multiple session-sharing identities.

* fix(ui): restore sidebar translation dependency

* fix(ui): preserve session sort ordering contracts
2026-08-10 00:18:02 -07:00
Peter Steinberger 3621c4b511 feat: browse and start sessions inside agent workspaces (#121417)
* feat(gateway): allow workspace-contained browsing

* fix(ui): preserve canonical workspace selections
2026-08-09 23:57:46 -07:00
Peter Steinberger a6a464f4c0 feat(terminal): fullscreen /terminal route and dot-matrix claw intro (#121451)
* feat(ui): add fullscreen /terminal route and open button

* feat(terminal): dot-matrix claw intro banner and CLI mascot

* fix(terminal): satisfy lint and wizard-header assertions for the new mascot

* ci: account for fullscreen terminal startup bytes

* test(terminal): split session manager coverage
2026-08-09 23:52:59 -07:00
Peter Steinberger ada95a20c1 docs(concepts): add session synchronization and attachment guide (#121091)
* docs(concepts): add session synchronization and attachment guide

* docs: mark session attachment page schema

* docs: distinguish gateway and local TUI sessions
2026-08-09 18:06:18 -07:00
Peter Steinberger 42283cb609 fix: completed tasks no longer block gateway suspension (#121267)
* fix(gateway): close task terminals at run end

* refactor(agents): keep terminal task ownership internal

* test(gateway): type task-owned terminal fixtures

* test(infra): split agent run task ownership coverage
2026-08-09 15:45:44 -07:00
Peter Steinberger b4104e29b5 fix(sessions): stop active runs before archiving (#120892)
* fix(sessions): stop active runs before archiving

Archive now fences the exact session, stops and drains active work before commit, preserves main/global-main/unknown protections and Delete behavior, and keeps patchMany per-target ordering.

* test(gateway): provide archive lifecycle context

* test(ui): expect active sessions to remain archivable

* refactor(gateway): keep archive drains internal

* fix(gateway): keep abort lifecycle contract acyclic

* fix(sessions): fence sharing across archive drains

* test(gateway): type archive lifecycle responses

* fix(sessions): reclaim cloud workers before archive

* fix(sessions): align archive request integration

* test(gateway): omit default deferred type

* docs(sessions): clarify cloud archive retries
2026-08-09 09:03:20 -07:00
Shakker f06e3675a6 docs: clarify browser annotation session scope 2026-08-09 13:56:55 +02:00
Shakker 7874024748 fix: preserve staged browser annotations across remounts 2026-08-09 13:56:55 +02:00
Shakker d0e76dea49 fix: bound browser annotation composer lifecycle 2026-08-09 13:56:55 +02:00
Shakker a3938ef435 fix: couple browser annotations to composer attachments 2026-08-09 13:56:55 +02:00
Peter Steinberger 1273657993 fix(ui): archive eligible sessions from mixed selections (#120835)
Batch Archive now delegates protected-row failures to ordered patchMany outcomes, while single Archive and Delete remain guarded.
2026-08-08 19:49:21 -07:00
Peter Steinberger 909509a5db fix(codex): resume catalog sessions with the catalog's CODEX_HOME; add main-content terminal placement (#120708)
* fix(codex): pin catalog CODEX_HOME onto terminal resume spawns

The Codex session catalog lists threads through the plugin's supervision
app-server connection, which can run on the agent-scoped Codex home
(homeScope: "agent"). The terminal resume plan spawned a bare
'codex resume <threadId>' under the operator login shell, so the CLI
resolved its default ~/.codex and failed with "No saved session found".

Terminal plans now carry the catalog connection's CODEX_HOME (local and
paired-node paths), the gateway merges plan env into the PTY spawn, and
the triplicated home-scope resolution collapses into one shared
resolveCodexAppServerLocalHomeDir.

* feat(ui): add main-content terminal placement

The terminal panel gains a third dock placement, "main", filling the
content region next to the sidebar like the transcript pane. The panel
header gets a bottom/right/main placement switcher (persisted in the
existing openclaw.terminal.panel.v1 store), and opening a Codex/Claude
session from the session catalog selects main mode so resumed sessions
appear as a full window instead of a bottom strip.
2026-08-08 14:33:20 -07:00
Peter Steinberger 4930c9d7de refactor(ui): remove local-only chat message hide feature (#120681)
Remove the localStorage-backed per-browser hide action ("Hide in this browser only") so the transcript mirrors agent context. The action was un-undoable because restore() had no UI path.

Stale openclaw:deleted:* localStorage keys become inert.
2026-08-08 13:57:15 -07:00
Peter Steinberger 0b85f9c174 feat: resolve short session references in the gateway (#120512)
* feat(gateway): resolve short session references

* refactor(ui): delegate short session resolution to gateway

* fix: complete short session resolver integration

* fix(gateway): filter deleted-agent short sessions

* fix(ui): support older gateway short links

* docs(web): qualify short-link candidate limits for older gateways

* refactor(protocol): split session resolve schema

* fix(gateway): filter session resolver visibility
2026-08-08 12:52:06 -07:00
Shakker 5d5bc74121 docs: document live dashboard index refresh 2026-08-08 17:25:38 +01:00