* feat(ui): Home page, identity row as agent menu, Pages header, Threads-hosted actions
* fix(ui): rename Home badge local shadowing the lit state import
The Control UI rounded any every-schedule interval that is not a whole
number of minutes up to the next minute when reading a job into the edit
form (parseEverySchedule ceil): 30s and 90s and 4m6s all became a whole
number of minutes. Because addCronJob always rebuilds the schedule into
the cron.update patch, any save — including metadata-only edits that
never touch the schedule — silently rewrote the cadence (e.g. 30s → 60s,
90s → 120s) and re-anchored the job. CLI, API, and docs accept these
intervals, and the UI itself can create them, so the editor corrupted
cadences supported everywhere else.
Add a Seconds interval unit and read everyMs back into the largest unit
that divides it exactly (days → hours → minutes → seconds), rendering
sub-second remainders as exact decimal seconds built with BigInt
quotient/remainder. Every integer millisecond up to
Number.MAX_SAFE_INTEGER now round-trips losslessly, so a resave with the
same everyMs preserves the gateway's anchor inheritance. Gateway,
protocol, storage, and cron runtime are unchanged.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(ui): add permanent OpenClaw navigation
* refactor(ui): drive Exit setup visibility with a typed onboarding property
The route view previously hid the button with an injected style tag (the page
file was owned by a parallel lane); render it conditionally instead.
* test(ui): use the navigation route-load cause literal
* fix(ui): request the caretaker greeting for permanent custodian visits
The onboarding welcome variant seeds the first-run setup proposal; the pinned
sidebar and Settings entries now omit it so post-setup visits get the normal
caretaker greeting, driven by the same route onboarding flag as the chrome.
* fix(ui): restart custodian session on mode change
* feat(ui): five-zone sidebar with agent identity card, threads/groups/coding zones, and footer bar
* fix(ui): promote main-session children via the gateway row's literal key
* fix(ui): filter and sort promoted main-session threads; hide empty Coding count
* fix(ui): collapsed zones no longer consume the visible session page budget
* fix(ui): a11y — offline card label and catalog lists outside the Coding list container
* fix(ui): zone headers use pre-pagination counts so paged-out zones stay visible
* chore(ui): document catalog-adoption dedup invariant on promoted threads
* chore(ui): seed five-zone sidebar fixtures in the mock-dev harness
* fix(ui): resolve the canonical global main key for the identity card under global scope
* fix(ui): global scope resolves the global sentinel as canonical main even pre-hello
* fix(ui): drop dead channelDisplayLabel, avoid map-spread, type zone test helper
* feat(gateway): typed structured questions on openclaw.chat
The chat result gains an additive optional question field (2-4 options, one
recommended max, per-option reply text). Producers: the two onboarding welcome
variants (apply-setup yes/ask, ready next-step) and hosted wizard select/confirm
steps, which mirror the awaited step so card clients render real wizard choices.
Prose replies always stand alone for text-only clients (macOS app, TUI). The
custodian page consumes the typed field and drops the PR1 string-marker parser
(never shipped in a release); cards send the option reply while the transcript
shows the label.
* chore: keep OnboardingWelcome type module-local
* chore(protocol): regenerate Swift bindings for the chat question field
* feat(ui): add custodian onboarding surface
* fix(ui): reset custodian session when credentials change on the same gateway
* fix(ui): include bootstrap token in custodian session ownership key
* fix(ui): no replay for ambiguous user turns; send sensitive input verbatim
* fix(ui): harden custodian session and route state
* fix(ui): keep device-token session scope stable across transient drops
* fix(ui): detect gateway scope errors structurally, not by class identity
Under isolate:false a custom element registered by an earlier test file keeps
its own module registry, so instanceof GatewayRequestError diverges while the
error shape stays stable; the branch's new custodian test files reshuffled
worker packing and exposed this in usage-page.test.ts.
The shared jsdom registry persists across test files under isolate:false while
modules re-evaluate per file, so any page imported by two test files threw
NotSupportedError at import time (ui/src/pages/usage/usage-page.test.ts started
failing when gateway-source-replacement.test.ts became a second importer). All
other components already guard with customElements.get; these nine pages were
the only bare defines.
* feat(chat): replay the active plan snapshot to reconnecting clients
Plan checklists were live-event-only: a client that connected or
refreshed mid-run saw nothing until the next update_plan call, and iOS
adoptRun actively wiped retained plan state on reconnect. The gateway
now records the latest normalized plan snapshot per run beside the
existing chat text buffers and replays it through the established
chat.history/chat.startup inFlightRun recovery contract (runId > plan >
text under the payload budget; a budget-dropped plan is sent as an
explicit empty snapshot so clients never preserve a stale checklist).
Web, iOS, and Android seed and reconcile the snapshot through one shared
contract riding each client's existing inFlightRun adoption gate: present
plan replaces, absent plan preserves (legacy gateway), a different or
terminated run clears, and stale history responses cannot clobber a newer
live plan. Verified live gap: openclaw/openclaw#108675 comment 5001475066.
* fix(ios): extract run-snapshot adoption below the file-length lint ceiling
ChatViewModel.swift crossed SwiftLint's 1500-line limit; the in-flight
snapshot adoption and plan reconciliation move to a focused extension.
Also refresh the native i18n inventory for shifted line anchors.
* fixup: regenerate i18n anchors and test formatting after rebase
* ci: retrigger after wedged queued runs
* fix(ui): use Object.hasOwn for the plan field presence probe
* fix(ios): annotate the plan-snapshot fixture init for periphery
Stream bounded browser camera frames directly to Gemini Live and keep camera media off the Gateway. Add lifecycle, function-calling, fake-camera E2E, and live-smoke coverage.
Co-authored-by: shushushu <1064076525@qq.com>
* feat(ui): unify sidebar scrolling, move new-thread to brand row, keep Mac titlebar search
- Sidebar body is now the single scroller (nav + sessions); brand row and
footer stay pinned, scroll fades move to the body, and the thumb sits
flush against the resize divider via a shared --sidebar-pad-x inset.
- New-thread button moves next to search in the brand row; the Sessions
header keeps only sort. Agent-chip plus stays (agent-scoped).
- macOS web-chrome hosts show search + new-thread in the titlebar
permanently; the brand-row copies hide only while that strip is visible
(compact drawers keep them).
- Update card slims to a single-line pill.
Ref: sidebar UX feedback (scroll inset, pinned nav, button placement).
* fix(ui): refresh i18n raw-copy baseline for slim update pill separator
* test: stabilize four flaky tests at root cause
- test/non-isolated-runner.ts: move shared-worker cleanup from onAfterRunSuite
to onAfterRunFiles. Vitest early-returns runSuite for files that fail during
collection, skipping onAfterRunSuite, so a crashed sibling left its evaluated
real modules cached and the next file's vi.mock factories silently never
applied — the "res.setHeader is not a function" failures in
http-utils.authorize-request.test.ts. onAfterRunFiles fires per file
regardless of collect/run outcome. Regression meta-test spawns a child
vitest run (collect-crash sibling + mock-dependent file) and fails on the
old runner with flavor:real vs flavor:mocked.
- src/gateway/http-utils.authorize-request.test.ts: export every http-common
binding http-auth-utils imports so the factory stays isolate:false-safe.
- src/gateway/session-message-events.test.ts: drop the beforeAll 60s override;
the suite harness cold-imports the full gateway server graph, which can
legitimately exceed 60s under contention. Sibling suites use the shared
project hookTimeout (120s/180s) for the same boot.
- src/gateway/server-methods/agent.sessions-and-models.test-utils.ts: the
finalize-throw test polled a 2s waitForAssertion for an off-turn background
run; signal finalize via a promise resolved from the spy (event-driven,
bounded by the test timeout) and keep the bounded poll for the follow-up
respond/warn observations.
- ui/src/pages/chat/chat-responsive.browser.test.ts: budget cold-app first
renders at 30s (real-app cases boot a cold Vite dev server whose first
transform can starve past 10s under 6-worker contention) and replace
one-shot isVisible reads with bounded locator waits for the state-driven
hover reveal.
Proof: focused runs green; 5x repeats of the three gateway files and 5x
repeats of chat-responsive with OPENCLAW_VITEST_MAX_WORKERS=6 all green;
runner regression test fails pre-fix, passes post-fix.
* test: isolate runner meta-test child env from GitHub Actions
The child vitest inherited GITHUB_ACTIONS/CI from the runner job, which
turned on ANSI colors (breaking the plain-substring assertions) and let
the child's github-actions reporter emit ::error annotations the parent
job rendered as its own failures. Drop GITHUB_ACTIONS/FORCE_COLOR and set
NO_COLOR, which overrides every tinyrainbow enable path.
* test: wait for parseable pid in tsdown-build pid-file polls
waitForFile existence polling can catch writeFileSync's open-truncate
0-byte window, yielding NaN pids and false isProcessAlive failures (the
same class as #109140). Poll until the file parses to a positive pid;
covers all three sibling pid-read sites in the suite.
Project group heads in the native session catalogs (Codex + Claude) are now
collapse toggles: chevron + aria-expanded, rows hidden while the head and
count stay visible. State persists per catalog+host+project through the
existing collapsed-sections localStorage set. Follow-up to #109575.
* feat(ui): fold the mobile navigation row into the chat title bar
On plain-browser mobile layouts the chat route drops the 58px shell topbar;
the pane title bar gains a leading nav-drawer toggle and a trailing
command-palette search button (~50px more chat). The shared mobile-nav
breakpoint moves into one module used by shell and chat; native shells and
non-chat routes keep today's chrome.
* improve(ui): anchor the workspace chip right after the session title
The chip now hugs the title text with the header's standard gap instead of
sitting at the far right; the action cluster claims the leftover width and
stays right-pinned. Where field-sizing is supported the rename input hugs
its text too, so the chip tracks the title while typing.
* chore(ui): keep hasNativeShellClass module-local
* feat(ui): group native session catalog rows by project in the sidebar
Codex and Claude session catalogs in the Control UI sidebar now group each
host's sessions by project folder, mirroring Claude Code desktop: any cwd at
or under <repo>/.claude/worktrees/<name> folds into the origin repo. A folder
toggle icon in the catalog header flattens the list; grouping defaults to on
and persists via localStorage (openclaw:sidebar:sessions:catalog-grouping).
* chore(ui): drop generated i18n fallback metadata from source PR
Post-rebase main forbids catalog fallback metadata in source PRs; the
post-merge locale-refresh workflow reconciles foreign catalog parity.
* chore(ui): keep CatalogProjectGroup type module-local
knip dead-export gate: no external importers; callers rely on the inferred
return shape of groupCatalogSessionsByProject.
* test(scripts): drop retired workspace-state.json expectation
Opportunistic CI fix: b6535fb8de5/905395a3ff5 retired workspace-state
seeding from scripts/e2e/parallels/agent-workspace.ts but the smoke-model
test still asserted the removed string, breaking checks-node-compact-small-2
on every branch.
* test(scripts): restore mainline retired-state regression guard
Rebase conflict resolution wrongly kept this branch's older removal; main's
2256387885 comment plus not.toContain assertion is the canonical fix.
* feat(gateway): add sessions.files.reveal to open a session workspace on the gateway host
Resolves the session's workspace root server-side (same resolution as
sessions.files.list), refuses exec-node and cloud-worker placements, and
opens the directory with the platform opener. The opener helpers move out
of config.ts into a shared open-path module that config.openFile now uses.
* feat(ui): always-on chat title bar with inline rename and workspace menu
Every chat pane now renders its header: click the session title to rename
it inline (Enter commits via sessions.patch, Escape cancels), a workspace
chip names the session's checkout with reveal/copy-path/copy-branch
actions, and cloud-worker sessions show a globe. The single-pane floating
toggle cluster and the transcript's reserved titlebar drag band fold into
the header, which keeps native macOS window dragging.
* fix(ui): resolve title-bar session aliases and clear the collapsed-nav overlay
Live verification found two gaps: route aliases like ?session=main showed
the generic key-derived title instead of the session's label (resolve via
hello defaults + key equivalence, matching the pane), and the floating
sidebar-expand pill overlapped the header title on plain web while the nav
was collapsed (52px inset, mirroring the native-shell clearances).
* fix(ui): keep the protocol schema layer out of the Control UI startup bundle
The title-bar change made session-row-badges runtime-import the
gateway-protocol barrel for one classifier, dragging typebox and every
schema into startup JS (395 KiB gzip vs the 370 KiB budget). The placement
vocabulary now lives in a dependency-free session-placement-state module:
the schema union derives from it, the barrel re-exports it, and the UI
deep-imports it (startup back to 361.8 KiB).
* fix(gateway-protocol): keep SessionPlacementStateSchema statically typed
Type.Union over a mapped array loses tuple inference and collapses Static
to never; the literal list stays explicit with a compile-time guard tying
it to the shared SESSION_PLACEMENT_STATES vocabulary.
* chore(protocol): regenerate Swift/Kotlin bindings for sessions.files.reveal
* chore: narrow new exports flagged by the deadcode gate
* chore(i18n): leave generated locale artifacts to the locale-refresh workflow
The generated-artifact isolation gate (067635cb51) landed mid-flight;
source PRs now carry en.ts only.
The run-scoped plan snapshot already fed the sticky composer bar; it now
also renders as an always-expanded card inside the active run's work
group, so plan progress is visible in scroll context. The bar and card
share one extracted renderer (variant: bar | card) instead of duplicating
the template, and the card clears with the run through existing plan
lifecycle.
* feat(ui): add verify-connection affordance and device-code wizard e2e to Model Setup
* fix(ui): drop unnecessary Boolean conversion in canVerify
* chore(ui): reconcile i18n baselines after rebase
Adds the sidebar approval attention chip and a dedicated approval history
page backed by the new approval.history gateway RPC (30-day retention
window). Extracts buildSidebarAttentionItems into its own module so the Lit
component consumes it as a real cross-module dependency, and wires the strict
i18n catalog for the new strings (fallbacks=0).
* feat(migrate): add Hermes memory-only import and a shared memory-import core
* feat(onboarding): offer detected memory imports during CLI setup and guided onboarding
* feat(ui): show a first-run memory-import offer in Control UI onboarding mode
* feat(linux-app): open the first-run dashboard in onboarding mode
* feat(macos): add the onboarding memory-import page
* docs: document the onboarding memory-import page across surfaces
* chore(i18n): translate onboarding memory-import strings for control-ui and native locales
* refactor: keep memory-import internals unexported for deadcode gates
* fix(ci): resolve lint findings in onboarding memory import
* chore(i18n): refresh native inventory after lint refactor
* fix(ci): skip updater artifacts in the unsigned Linux companion PR lane
* chore(i18n): reconcile locale artifacts after rebase
The gateway already broadcasts stream:"plan" agent events with typed
{step, status} snapshots; all three clients dropped them. Control UI now
renders an expandable checklist bar above the composer (run-scoped,
cleared on run lifecycle); the shared iOS/macOS chat UI mounts a
Liquid Glass pill (iOS 26 glassEffect, material fallback) between the
message list and composer; Android adds an expandable pill above the
composer bridged through ChatController/NodeRuntime/MainViewModel.
All parsers accept typed and legacy string steps, enforce the
single-in_progress invariant, and scope snapshots and clears to the
owning run. Locale artifacts, Android resources, and the iOS string
catalog carry generated translations for the pill strings.