* feat(ui): durable session board face and dashboards index
Board face lived only in client-side boardSessionViews, capped at 50 entries,
so the preference never followed the user to another device, evicted as
sessions accumulated, and could not be seen as a set.
Persist it as SessionEntry.boardFace, which rides the existing entry_json blob
and so needs no SQLite schema change or version bump. Expose it on the session
list row and add it to the sessions.patch write-scope allowlist alongside label,
pinned, and archived: setting your own view preference is user-level chat
organization, not policy. Unknown patch fields still fail closed to
operator.admin.
Generic navigation now reads the stored face, so the sidebar and session list
open a thread on the face you left it on. boardSessionViews keeps only
activeTabId and reopenDockByTab, which are genuinely per-device.
Add /dashboards listing threads whose preferred face is dashboard. Filtering
runs server-side in filterSessionEntries before pagination, because the client
holds only a capped page and a client-side filter would silently omit
dashboards.
* test(protocol): assert the pre-rename face param is rejected
The gateway-protocol validator test still passed the pre-rename 'face' key,
which the closed schema rejects. Use boardFace, and pin the old name as a
negative case so it cannot silently return.
* chore(protocol): regenerate Swift bindings and docs map for boardFace
Adding boardFace to the sessions schema changes two committed generated
artifacts: the Swift gateway models (pnpm protocol:gen:swift) and the docs map
(pnpm docs:map:gen), which now lists the dashboards index section.
* feat(ui): path-based session and dashboard URLs
* docs(ui): document control UI URLs
* fix(ui): finalize session path routing
* feat(ui): anchor session URLs on stable keys
* docs(ui): clarify stable session URL identity
* fix(ui): resolve session prefixes with full prefix and pagination
Query sessions.list with the full supplied prefix instead of an eight-char
truncation, and paginate up to a bounded page count so longer disambiguation
links resolve instead of being reported ambiguous. Zero strict-prefix matches
now fall through to literal-key resolution rather than rendering an empty
ambiguity view.
Also document the ~dot/~dotdot segment escape: peer ids reach session keys
trimmed and lowercased only (src/routing/session-key.ts), so a literal '.' or
'..' segment is reachable and browsers would normalize it away.
* fix(ui): synchronize committed session routes
* test(ui): split native shell host coverage
* fix(ui): thread configured mainKey through session URL builders
Reserved-set disambiguation needs the operator-configured mainKey at runtime, so
thread it from agentsList through every session path builder and the ClickClack
control URL. Unambiguous non-hex single-segment rests now resolve literally while
short-id-shaped rests still fail closed, which restores ClickClack channel
compatibility detection and control-link reconciliation.
Also bound prefix-resolution retries, preserve catalog thread identity, and keep
draft state on ambiguous candidate links.
* fix(ui): repair session URL CI integration
* perf(ui): lazy-load session route resolution
* perf(ui): isolate session prefix resolution
* perf(ui): defer session path parsing
* perf(ui): defer session navigation startup
* fix(ui): preserve first-run and literal session navigation
* fix(ui): satisfy session routing type and export gates
* fix(clickclack): preserve unscoped control link agent
* style(ui): satisfy chat page line limit
* refactor(ui): move chat page helpers to owning modules
* fix(ui): preserve destination session route identity
* fix(ui): preserve agent identity in session routes
* fix(ui): escape dots in literal session path segments
encodeURIComponent leaves periods intact, so a literal key segment like
channel:release.js produced /chat/main/channel/release.js. In-app navigation is
intercepted by the SPA, but a refresh, an external link, or a ClickClack link
would be served as a static asset request and never reach the app.
pathForWorkboardBoard already escapes dots for this reason; mirror it in both the
session URL contract and the ClickClack encoder, route the agent id through the
same segment encoder, and pin the case in both shared vector tables.
* fix(ui): stop bootstrap after teardown race
* refactor(ui): centralize session navigation targets
* fix(ui): consume bootstrap teardown abort
* fix(ui): canonicalize configured main session routes
* fix(ui): preserve distinct session references
* fix(ui): redirect released session query links
* fix(ui): make bootstrap teardown abort-safe
* fix(control-ui): drop stale Create PR row after merge and surface PR chips mid-turn
* test(gateway): split session-PR branch tests to satisfy max-lines
* test(gateway): drop unused GitContext export from test support
* feat(ui): unify sidebar footer into full-width identity card
Footer becomes one card (avatar + name + chevron) mirroring the top agent
card; identity menu gains email header, Settings (with platform shortcut
hint), and Usage; Usage leaves default sidebar pins; settings shortcut now
also binds Ctrl+Shift+Comma; offline state lives in the card subtitle and
menu retry action and the card persists while disconnected.
* test(ui): align persisted-sidebar fallback default with usage removal
* refactor(ui): split agent menu by scope — account items move to footer identity menu; fix facepile tooltip anchors
* test(ui): match footer identity button by its name-bearing aria label
* refactor(ui): move sidebar menu rendering out of the menus controller
* perf(ui): lazy-load sidebar menu rendering outside the startup chunk
* fix(ui): inherit knownSessionGroups signature from organizer host
* feat(gateway): add web-only incognito sessions held in process memory
* feat(ui): add incognito toggle and badges to the web new-session flow
* fix(sessions): classify incognito by key shape, fail closed on stale keys, and gate memory writes
* fix(codex): start harness threads ephemeral for incognito sessions
* fix(sessions): reshape internal-effects incognito keys and add doctor repair for reserved key collisions
* refactor(plugin-sdk): export canonical incognito key classifier and guard the sentinel path
* fix(state): classify incognito DB handles from the recorded open-time set
* fix(gateway): isolate incognito sessions from durable lineage and allocation on read-only misses
* docs(sessions): pin the reserved incognito namespace ownership decision
* feat(gateway): admin-scope incognito visibility and incognito-blind cross-session surfaces
* fix(ci): repair kysely guardrails, dead export, docs map, protocol bindings, and ACP reset rotation
* fix(gateway): remove non-admin observability side channels for incognito sessions
* fix(gateway): enforce admin-scope incognito access and cover all parent-reference creation paths
* feat(ui): restructure chat transcript for multi-user sessions
Viewer-relative alignment: attributed messages from other participants
(senderId != viewer) render left-aligned as peers with their avatar,
name, and identity tint; only the viewer's own messages stay right-aligned.
System-role transcript entries (e.g. local command output) now render as
centered notice rows instead of a pseudo-participant with a question-mark
avatar. In threads with 2+ attributed senders, assistant replies carry a
'Replying to <name>' attribution chip derived from the preceding attributed
user turn; unattributed turns clear the attribution rather than mislabeling.
Also drops redundant role lowercasing on already-normalized roles.
* docs(web): describe multi-user chat transcript layout
* docs(web): refresh chat transcript docs map
* 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
* 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(gateway): session observer digests over the utility model
* fix(gateway): split session-observer modules and satisfy ci gates
* fix(gateway): observer reads session entries without materializing agent state
* feat(anthropic): import Claude Desktop custom groups
* fix(anthropic): resolve Claude Desktop groups from LevelDB entries
Group discovery regexed whole decompressed blocks, so matches were not
attributable to a Local Storage value and byte order decided the winner.
On a real 5.4MB store that mislabelled 59 of 159 sessions, surfacing a
mojibake label spliced out of Snappy copy-record bytes.
Parse SSTable entries properly instead: prefix-delta keys bounded by the
restart array, newest internal sequence per user key (honoring deletions),
and record scanning confined to a single value. Values are normalized so
Chromium's UTF-16 form scans like Latin-1, and unflushed WAL writes keep
precedence over SSTables.
Prod LOC grows ~77; it buys structural correctness in place of ordering
luck, and folds the old index-only walk into one shared entry decoder.
Verified against the live store: 159 assignments, 0 mislabelled versus an
independent entry-level ground truth, 0 control-character labels.
* fix(ui): sort custom session groups ahead of project groups
Custom groups were pushed into the section list as encountered, so their
position depended on roster order rather than the documented behavior;
the existing test only passed because its fixture happened to be ordered
that way. Collect custom and project groups separately and concatenate,
and assert the guarantee with a reversed-input case.
* fix(anthropic): widen LevelDB fixture key type for test typecheck
The prefix-delta helper assigned a Buffer into a Buffer-typed accumulator
whose generic argument differed, which tsgo rejects in the test lane.
* chore(anthropic): drop release-owned changelog edit from the PR
CHANGELOG.md is generated at release time, so a normal PR must not carry
an entry. The release-note context lives in the feature commit message and
the PR body instead.
* feat(ui): finish the chat session → thread rename across the Control UI
Completes the #110933 creation-surface rename mechanically: Threads page
(title, filters, archive/delete/rename/pin menus, transcript search),
sidebar labels and menus, welcome suggestion, palette category, tools
availability copy, workboard lifecycle/card strings, dreaming history,
board/diff/workspace-rail chrome, and the untitled main/channel fallback
names. Technical identifiers stay: Session ID, session keys, terminal
sessions, protocol wording. Living docs updated (control-ui.md);
historical release notes left as shipped.
* style(ui): format session-management e2e
* fix(ui): finish thread rename in navigation labels and stale test literals
Codex review caught the palette Threads entry and the Worktrees owner
column still saying Session; CI caught test literals asserting the old
copy (Threads page overview tile, filter tooltips, group counts, catalog
terminal notice, create-rejection error, workboard lifecycle hint). Also
completes the usage-stat cluster (Threads + hints) and the tool-catalog
group label. Native app strings (iOS/Android/macOS) keep session wording
and reconcile through their own i18n pipelines as a follow-up.
* feat(webchat): reply-to a message with hydrated reply context
Control UI replies now carry the target transcript id as replyToId on
chat.send. The Gateway resolves the replied-to message from session
history and hydrates the channel-agnostic ReplyToId/ReplyToBody/
ReplyToSender envelope fields, so agents receive reply_to_id,
has_reply_context, and the untrusted reply-target block exactly like
Discord replies (mirrors #90263). Reply targets without a persisted
transcript id keep the inline-quote fallback.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(webchat): keep non-reply chat.send dispatch ordering and satisfy CI gates
* docs(webchat): match reply-context doc to webchat conversation-info policy
* fix(webchat): hydrate reply bodies from display-visible content only
---------
Co-authored-by: openclaw-clawsweeper[bot] <openclaw-clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com>
* docs: add The main session concept page and cross-links
* docs: precise memory-priming wording on the main-session page
* docs: update Control UI sidebar description to the Home and zones model
* feat(chat): rewind and fork a session from a message bubble
* docs(web): document chat rewind and fork bubble actions
* fix(chat): lint cleanups, protocol regen, and test splits for rewind/fork CI
* fix(ui): fit chat rewind disabled styles inside the startup CSS budget
* feat(mac): show native notification permission in dashboard Notifications settings
The Mac app's embedded Control UI showed the web-push section as
Unsupported/Not subscribed even though the app delivers notifications
natively. The dashboard now installs an openclawNotifications WebKit
bridge (status / request-permission / send-test) and the Notifications
settings page renders native permission state with Enable, Open System
Settings, and Send test actions when hosted in the Mac app. Browsers
keep the existing web-push UI. The notifications section renderer moved
out of the oversized config view module.
* fix(ui): break config view import cycle and satisfy lint/deadcode/i18n gates
Move the notifications section props to a leaf contract (no view.ts
type import), unexport the status-event constant, use bracket access
for the injected dunder global, default-case the status switch, and
refresh the native i18n inventory line offset.
* feat(ui): manage MCP servers directly on the Settings MCP page
* refactor(ui): mcp config section owned by the MCP settings page only
* chore(ui): drop test-only McpServersCard export for knip
* fix(gateway): emit config.changed from the reload committer for every accepted write
Agent config_set, CLI, and doctor writes reach the gateway through the file
watcher's reload path, which never broadcast config.changed - only direct RPC
writes did. Move the broadcast to a single onConfigCandidateCommitted hook in
the reload committer so all writers notify connected UIs, including
runtime-skipped commits (writer-intent echo suppression, reload mode off),
and delete the duplicate RPC-side broadcasts.
* feat(ui): sync chat follow-up and commentary prefs across devices
Add chatPersistCommentary and chatFollowUpMode to the ui.prefs sync surface
(schema, types, docs) with clearable null removal for the follow-up override.
Rewrite server-prefs around a descriptor table so a synced pref is one entry
instead of five hand-written code sites. Caption synced rows/sections
(chat prefs, theme, language) and refresh the stale Settings subtitle.
* refactor(ui): dedupe settings constants, drop dead composer branch, refresh copy and docs
Derive the Security tool-profile options from PROFILE_OPTIONS and General's
thinking levels from BASE_THINKING_LEVELS instead of parallel literals.
Remove the unused composer mode:"model" branch, the orphaned
.config-view-toggle CSS family, and stale quick-settings wording. Add static
settings-search entries for the Sessions and Managed Worktrees pages plus a
provenance-row test, and update docs/cli/openclaw.md to describe the current
system-agent config-write policy (denylist roots, route-backing plugin guard,
per-agent routing escalations).
* chore: refresh plugin SDK API baseline after rebase onto current main