The raw-command-detail streaming card test shipped in #121826 asserting
raw exec text under the default commandText mode, but the same commit
made "status" (tool label only) the documented default with raw as an
explicit opt-in. The test has failed since introduction; change-classified
CI never selected the feishu lane on main pushes. Opt the test into
streaming.progress.commandText: "raw" so it proves the documented path.
* feat(normalization-core): add promise-like guard
* refactor: consolidate promise-like guards
* fix(normalization-core): keep isPromiseLike non-throwing on hostile then getters
ClawSweeper finding on #121861: the diagnostics-path local guard caught throwing
then getters; the canonical guard must classify, never throw.
* test(normalization-core): annotate intentional hostile-thenable fixture
A pinned session led with a generic message-bubble glyph that no other
session row gets. renderSessionLeadingState fills each row's single
leading slot from a priority ladder, and the pinned branch outranked the
creator avatar chip, so a pinned row could never show who created it and
carried a glyph whose only claim was "this is a session".
Drop the pinned branch and its styling. Pinned rows already render
through the same renderSessionTree anatomy as the session list, so they
now fall through the same ladder as any other row: attention icon, owner
avatar, or nothing. The leading slot stays width-reserved, so titles do
not shift.
* fix(ui): confirm destructive sidebar session actions in-app
The sessions sidebar still gated batch delete, single delete, cloud-worker
stop, and preserved-worktree removal on native window.confirm. In an
embedded WebView without a dialog bridge that call resolves false, so each
action returned early with no request, no error, and no visible outcome.
PR #118250 introduced showConfirmDialog and converted the Sessions,
Worktrees, and Nodes pages but not the sidebar operations layer, leaving
one product action with two confirmation mechanisms. Route all four sites
through the same helper and let the already-present post-decision scope
guards do their job, extracting the cloud-worker reclaim guard above the
await so the modal never opens for a stop this surface never performs.
Closes#121275
* feat(ui): let operators opt out of the session delete confirm
Session deletes are the repeatable, per-row destructive action in the
sidebar, so their confirm now offers "Don't ask me again". Stopping a cloud
worker and removing a preserved worktree deliberately get no opt-out: the
first is a rare shared-resource action, the second destroys the only copy
of uncommitted work. The checkbox exists only for callers that pass a skip
preference, so the serious confirms stay unskippable by construction.
The preference is device-local in UiSettings rather than a synced ui.prefs
key, so opting out on one browser cannot lower the bar on the operator's
other devices. Appearance -> Sidebar carries the matching toggle with the
standard reset affordance, which is how asking gets turned back on.
* fix(ui): scope the delete opt-out copy and refresh its settings view
Autoreview surfaced two preference-consistency defects.
The setting copy promised a general "session or a selection" policy, but
only the sidebar honours it; the Sessions page delete paths still prompt.
Extending the preference there would cross into another owner's in-flight
surface, so narrow the copy to what actually ships and keep the extension
as the recorded follow-up.
Persisting the opt-out also wrote local storage without notifying the
appearance subscription, so a mounted Settings -> Appearance kept showing
the toggle enabled while deletes already skipped the prompt. Publish the
refresh through the scope's theme capability after persisting.
* fix(ui): space the confirm opt-out away from the message copy
The checkbox rendered flush under the message with no separation, so it
read as a third line of the paragraph rather than a distinct control, and
the group crowded the action row.
Give it the 12px separation the details block already uses in this card,
keeping the 16px action gap below, plus the 10px control gap and 16px box
the config-form checkboxes use so the box and label align. Verified in
light and dark at desktop and mobile widths.
* fix(ui): use the cursor-action token on the confirm opt-out
The spacing fix hardcoded cursor: pointer, which the Control UI cursor
policy forbids outside link rules; controls consume var(--cursor-action)
so the hand stays configurable from one place.
Caught by ui/src/styles/cursor-policy.node.test.ts. Token resolves to the
same hand, so the published dialog captures are unaffected.
* fix(ui): keep the delete opt-out to the surface its setting names
deleteSession is shared: the chat-pane header menu calls it too. The
skip preference was applied inside the operation, so opting out from the
sidebar also silenced future header confirmations while the setting text
promised it applied to sidebar deletes only.
Make the opt-out opt-in per caller with a default that keeps asking, and
let the sidebar be the one caller that offers it. The header and any
future caller now match the copy without having to remember anything.
* refactor(ui): split the session group catalog out of the operations runtime
Adding the in-app group confirm on main pushed
session-organizer-operations.runtime.ts past the 700-line ceiling once
this branch's session confirms landed beside it.
Move the catalog and section-order operations into their own module. They
write the group catalog directly and never touch session rows, so the
dependency runs one way and no import cycle appears. The shared access
gate moves to the batch-mutations sibling both now import, and the
runtime keeps re-exporting the catalog names the controller loads as one
namespace.
* fix(agents): record intentional non-delivery truthfully
* fix(agents): stop reporting sinkless cron completions as delivered
The inactive-cron guard runs before requester-agent dispatch. Its regression now asserts the recorded intentional non-outcome before verifying that queue, gateway, and message sends remain untouched.
* test(agents): migrate announce outcome fixtures
* chore: drop changelog edit per repo policy
* 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
Machine-created probe/run sessions (health checks, internal effect sessions) previously surfaced in the Control UI sessions sidebar as raw first-message rows and buried real conversations. Classify them from recorded creation provenance only (system actor, or unnamed run/internal creation without a human actor) and hide them by default behind a persisted "Show system sessions" toggle, mirroring the automation-sessions toggle. Cron rows stay owned by the automation toggle; the selected session, the Sessions page, and the toggle keep hidden rows reachable. Legacy rows without provenance stay visible.
Closes#121851
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(ui): align sidebar menu rows on one leading column
The sidebar dropdown family never received the Web Awesome shadow-row
corrections that .session-menu__item already documents, so the agent
switcher stacked our 8px flex gap on top of Web Awesome's forced
`margin-inline-end: 0.75em` slotted-icon margin, kept a layout-only
native checkmark it never shows, and mixed a 24px avatar column with a
16px icon column. Agent rows started their labels 19px right of the
command rows and their avatars 11px right of the command glyphs.
Give the family one leading column: drop the stacked gap, hide the
agent menu's unused native checkmark (selection lives in the trailing
details rail), widen command glyph boxes to the avatar's 24px, and pull
the section label, provenance note, and empty state onto the rows' 8px
inline edge. Command-row label positions are unchanged; the avatar rows
and the More menu's route rows move onto the shared gutter.
* test(ui): assert the sidebar menu's shared leading column
The alignment fix had no committed regression: the existing agent-menu
E2E opens and navigates the menu but never reads its geometry, so the
19px label drift could return silently.
Assert in the browser that an avatar row and a command row start their
leading slot and their label on the same x. Fails on pre-fix code, where
the avatar column sits 11px right of the command glyphs.
Pinned sessions shared the Pages navigation group's container and head,
so a pinned chat rendered as one more nav row directly under Automations
or Plugins with nothing separating elevated content from navigation.
Split the sidebar zone at render time: navigation entries stay in the
Pages list, session entries move into a second group with the sidebar's
inter-group spacing and a muted "Pinned" section label matching the
PAGES/SESSIONS treatment. Both groups keep the zone drop handlers, so
dragging a session into either one still pins it and records its slot,
and the group disappears entirely when nothing is pinned.
* fix(system-agent): repair setup-chat execution identity, harness fallback, and error surfacing
Always inject the reserved "openclaw" execution entry for off-roster system-agent runs.
Keep implicit harness runtime selection implicit so Codex refusal falls back to the OpenClaw harness, while explicit policy remains fail-closed.
Log openclaw.chat turn failures and surface their underlying causes in the UNAVAILABLE response.
* chore(plugin-sdk): refresh agent harness API baseline
* test(system-agent): align fixtures with optional harness routes
* fix(system-agent): keep execution roster out of TUI catalog lookup
* refactor(system-agent): keep verified state types internal
* fix(system-agent): separate setup policy and execution config