* fix(ui): unify tooltip arrows and rework sidebar footer hover cards
* fix(ui): keep rich tooltips open while trigger or card retains hover or focus
* fix(ui): type trigger slot query and drop unused build-card export
* test(ui): type usage hint button query for test-types lane
* feat(sessions): first-class archived-session handling
Archived sessions are now exempt from every automatic maintenance path
(age prune, entry cap, model-run prune, disk budget, cleanup repairs);
only explicit sessions.delete removes them. sessions.list gains an
additive tri-state archived filter (true | false | "all"). The sidebar
gets a persisted Active/Archived/All status filter with inline dimmed
archived rows, replacing the nav-away View-archived button; the Sessions
page toggle becomes the same tri-state with dimmed+badged rows in All
mode; the chat composer's archived notice becomes a full-width banner
with an Unarchive action.
* chore(protocol): regenerate Swift gateway models for tri-state archived filter
Zen (2%) breathes through a 6s cycle with one deliberate late snip,
drummer (1%) rocks a two-beat rhythm with jaw hits on the tilts, and
peekaboo (1%) ducks down small and pops back wide open. All three stay
in place (scale/rotate/vertical only); weights rebalance from the
default and southpaw stances.
The initials fallback span inside openclaw-viewer-avatar was hidden with a
single-class selector (.viewer-avatar__fallback, 0-1-0) that the later
.viewer-avatar > span display rule (0-1-1) outranks, so image avatars rendered
the photo and the colored initials side by side in the who's-online roster,
footer facepile, and session facepiles. Bump the hiding selector to
.viewer-avatar > .viewer-avatar__fallback so it wins again; the
.is-fallback swap (0-3-0) still shows initials when the image fails to load.
Regressed in #111421.
* feat(ui): show live run output tokens
* fix(ui): map live usage to active runs
* test(ui): split live usage coverage
* fix(agents): scope live usage cleanup
* fix(agents): reject stale live usage events
* fix(agents): keep live usage type local
* refactor(ui): keep chat view within line budget
* fix(agents): require lifecycle for live usage
* refactor(ui): dedupe chat reply target type
* feat(gateway,ui): ask-the-observer card input over sessions.observer.ask
* refactor(ui): single home for observer run-identity helper after restack
* test(ui): drop duplicated observer hud test after restack
* test(ui): give the observer ask flow its own colocated suite
* refactor(gateway): leaf observer contract and ask module split for ci gates
* refactor(gateway): drop observer contract re-export shims
* fix(ui): allow direct sessions in non-Git folders
* test: align New Session fixtures with place picker
* fix(ui): preserve worktree intent on Git probe failures
* fix(types): keep worktree status alias internal
* feat(ui): session observer HUD, subtitle integration, and settings
* test(ui): observer demo fixtures for the mock control-ui harness
* fix(ui): satisfy lint and deadcode gates for observer surfaces
* test(ui): adopt renamed pull-request summary api after rebase
* fix(ui): clean rebase artifacts in observer test files
* feat(cron): stream schedule sources (supervised command stdout)
Add gated argv stream schedules with bounded line batching and trigger.streamBatch composition.
Reuse the gateway ProcessSupervisor for source ownership, deterministic teardown, capped restart backoff, and schedule-key guarded batch execution. Expose additive protocol, CLI, tool, UI, docs, and generated snapshot surfaces without storage DDL.
Contract: stream schedules are event-driven, require cron.triggers.enabled, reject command payloads, and retain at most one bounded pending batch.
* fix(cron): reject retired stream source epochs at run admission
Thread an invalidatable per-owner source-generation token (ownerNonce.generation)
through cron.run admission alongside the schedule key. A batch handed to cron.run
under one source epoch can wait behind another run while its owner is stopped; a
disable→re-enable or A→B→A edit leaves the schedule key unchanged, so the key
check alone would admit the retired epoch's batch. The token is persisted in
job.state on every lifecycle write and compared at every admission site plus the
executeJobCore guard, so a stale epoch's batch is skipped.
Also fix direct stream-job mutations recording the wrong lifecycle status when
global cron is off but triggers are on: extract resolveStreamStopReason so the
direct path reports the remediable cron-disabled state like reconcile does.
* fix(cron): close stream admission windows from round-10 review
- Persist the retired source generation before draining stop teardown, so a
batch queued behind another cron run cannot gain admission during the up-to-10s
in-flight-batch wait (server-cron routed stop path).
- Add streamSourceGeneration to the closed gateway response schema (excluded from
the writable patch schema) so a running stream job passes strict result
validation without letting callers spoof source identity.
- Close the mutation-epoch ABA: track an eviction epoch so a snapshotted absent-0
is trusted as unchanged only when no LRU eviction happened during the await.
* refactor(cron): stream sources own a durable logical identity
Split the conflated restart-generation/admission token into two concepts:
a persisted streamSourceIdentity owned by cron store mutations (rotates on
enable/disable, source replacement, once-trigger auto-disable, and explicit
retirement; stable across supervised child restarts) and a watcher-local
process generation used only to fence stale child callbacks. Admission now
requires schedule key + identity together at every window, closing the
A-to-B-to-A and restart-flush races from review rounds 8-11.
Also: match-mode regexes now see raw source text (the [truncated] marker is
applied after matching), stop-timeout failures set the live restartExhausted
mirror so shutdown preserves the terminal diagnostic, and the watcher is
split into owner/output/registry modules under the max-lines budget.
* fix(cron): harden stream teardown and intake from round-4 review
An exit queued ahead of a requested stop no longer counts toward restart
exhaustion (the synchronous stop fence owns it), overlapping cron.stop and
stopAndDrain share one memoized shutdown drain instead of double-stopping
every owner, raw output intake is bounded at 4x the batch cap so normal
64 KiB pipe reads stop losing complete lines to OS chunk boundaries, and
persisted-shape quarantine coverage for unsafe match expressions is pinned.
* fix(cron): keep watcher-internal owner disposal from retiring live identity
Disposing an obsolete owner while a start replaces it is not a durable
removal; a retiring stop there rotated the live job's identity and stranded
the replacement behind the CAS ownership guard. Also align the docs with the
implemented match semantics: complete lines match on full text past the
batch cap, only intake-cut prefixes are unmatchable.
* fix(cron): make oversized-line matching independent of pipe chunking
Partial lines are retained up to the raw-intake bound rather than the
delivery cap, so a complete over-cap line matches identically whether it
arrives in one callback or several; only a line the intake bound itself cut
remains an unprovable prefix. Reconcile also contains schedule-replacement
stop failures per job, matching the other stop branches.
* fix(cron): bound assembled lines, drop stale payload override, barrier stopAll
- enforce the 4x raw-intake per-line cap while assembling split callbacks,
so an oversized line stays an unprovable prefix regardless of chunking
- stop passing the watcher-cached payload as a cron.run override; the run
snapshots the persisted payload under its admission lock
- stopAll waits for every owner stop to settle before surfacing failures
- split cron-stream-output interleaving tests into their own file (max-lines)
* fix(cron): address first full-CI round (lint, knip, schema test, unused param)
* chore(cron): refresh codex prompt snapshots for stream schedule schema
* fix(cron): keep the first clean line after an intake drop ending at a newline
* fix(cron): fence stream reconcile list snapshots against direct mutation routes
A cron.list snapshot captured across the reconcile await could be applied
after a direct add/update route already started the owner, stopping it as
removed and retiring its live identity. A mutation revision bumped at every
direct route start invalidates the stale snapshot; reconcile re-lists
(bounded) instead of applying it.
* style(cron): format stream owner imports
* fix(cron): discard severed stream prefixes at EOF
* fix(cron): retry failed stream shutdown drains
* fix(cron): honor stream stop fence after output drain
* chore(cron): refresh landing checks
* fix(control-ui): restore native context menu when text is selected (#106765)
* test(control-ui): prove selected-text context menus
* fix(control-ui): combine selection and message actions
Keep message actions on selected chat text and add an exact-selection Copy entry, resolving #106765 while retaining the contributor context-menu work.
Co-authored-by: Qiong <yang.ji2@xydigit.com>
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* feat(ui): calm the working claw and add turn status whimsy
The working indicator claw now claws in place (jaw snips, no lateral
shadowboxing) with rare seeded stances: mirrored southpaw, double-time
flurry, fake-3D turntable spin, the old punch combo as a rare shadowbox,
and a somersault backflip. After 30s of quiet the status line adds
rotating crab-gerund phrases (Clawing…), and a completed run leaves a
sticky 'Done in 51s · 485 tokens' recap row fed by the session row's
per-run runtime and output tokens. Recap attribution is guarded by a
watch/settle state machine (baseline terminal stamps, absorb, expiry);
the residual without gateway run identity is documented in code.
* fix(ui): unexport test-only working-phrase timing constants
knip's production scan forbids exports only consumed by tests; the test
mirrors the two literals with a pointer comment.
Places rows now carry destination icons: monitor for the Gateway and
Mac-family nodes, a phone glyph for phone-family devices, and a server
glyph for cloud workers. The platform prettifier moves from the devices
inventory page into ui/src/lib/platform-label.ts so place-picker
tooltips and Settings/Devices render identical platform names (macOS,
not darwin).
Apply the existing chat width setting to tool lanes, activity rows, tool shells, the composer, and its pull-request rows while preserving every default cap.
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(ui): format cron run token counts and durations consistently
The cron run entry rendered raw unformatted token counts ("1234567 tokens")
and raw millisecond durations ("90000ms"), inconsistent with every other usage
and duration surface in the app which uses formatTokens ("1.2M") and
formatDurationCompact ("1m 30s"). An unknown duration showed "0ms" which is
misleading — it implies an instantaneous run rather than missing data.
Use formatTokens for token counts and formatDurationCompact for durations,
showing n/a when durationMs is absent, matching the rest of the UI.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(ui): cover cron run metric rendering
Co-Authored-By: ZCode <noreply@zcode.ai>
* fix(ui): avoid raw duration fallback text
Co-Authored-By: ZCode <noreply@zcode.ai>
* chore(ui): refresh cron raw-copy baseline
Co-Authored-By: ZCode <noreply@zcode.ai>
* test(ui): localize invalid cron durations
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: ZCode <noreply@zcode.ai>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* feat(ui): expand lobster pet randomization
Shiny rolls (1/512) with Lobsterdex tracking, cotton candy and lumen
palettes, crusher/pincer claw asymmetry, freckles, seeded eye glints,
elder and old-friend rare loads, balloon/bubble entrances, snail/duck/
jellyfish ledge traffic with per-kind crossing times, passer reactions,
a message-in-a-bottle event, National Lobster Day monocles, and a golden
ledge trim for a completed Lobsterdex.
* style: apply oxfmt formatting to lobster pet files
* refactor(ui): split lobster pet art and ledge traffic into modules
lobster-pet-sprites.ts owns the static SVG art, lobster-pet-traffic.ts
owns passer/bottle scheduling as a ReactiveController, and load identity
(elder, old friend, dex completion) moves into lobster-pet-plans.ts;
keeps lobster-pet.ts and lobster-pet-look.ts under the max-lines cap and
fixes the passerBaseStyle consistent-return lint.
* fix(ui): clear ledge traffic on disconnect, hide bottle from AT
Review findings: a host reconnect no longer shows a frozen passer or an
unebbing bottle, and the bottle joins the rest of the decorative ledge
sprites behind aria-hidden (fortunes stay on the native-tooltip channel
by design - no i18n surface).
* style: oxfmt formatting for lobster plans/sprites/pet
* refactor(ui): narrow lobster plan/traffic exports to production consumers
isLobsterElderLoad and planLobsterOldFriend become module-local (tests
prove them through resolveLobsterLoadIdentity), and the traffic
controller's hook/scene types stay local; fixes the knip production
unused-export gate.
* fix(ui): request host update when disconnect clears ledge traffic
Lit does not re-render on reattach by itself; the queued update makes
sure a detached-then-reattached pet never shows a stale passer/bottle.
* improve(ui): show real machine identity in the place picker
The place picker labeled the gateway destination with a hardcoded
'Gateway · local' and let duplicate device names collide. The gateway
destination now shows the host's machine name via the existing
system.info method (falling back when unavailable), node rows carry
platform/model/IP facts as tooltips, and duplicate labels — same-named
devices and same-basename recent folders — get a deterministic muted
suffix chosen collision-only (model, then IP, then id; parent folder,
then path, then node facts for recents). IP addresses never render
inline by default. Phone-family nodes get a phone chip icon.
* fix(ui): appease prod-type and no-shadow gates in place identity helpers