* fix(ui): center onboarding dashboard and quiet its chrome
The custodian onboarding route carried the settings takeover class, so the
shell reserved a hidden 288px sidebar column and the whole page rendered
off-center. Onboarding now opts out of the takeover, drops the duplicate
header identity (the thread already introduces the mascot), and option-card
hover no longer lifts or re-tints cards.
* fix(ui): stop next-step cards overlaying the message meta row
The option-card and wizard-step thread attachments pulled themselves up with
a -12px top margin, covering the preceding message's sender/timestamp line.
* test(ui): align nudge e2e with the minimal onboarding header
* fix(ui): soften default dark-mode body text to ~12:1 contrast
Drop --text/--chat-text from #d4d4d8 (12.87:1 on --bg) to #cdcdd1
(12.01:1 on --bg, 11.10:1 on --card) so dark body text sits inside
the 10-12 comfort band instead of above it; brighter body text
halates on dark backgrounds. Light mode, openknot, dash, --text-strong,
and --muted are unchanged.
* fix(ui): dim dark body text a further step to ~11.4:1
#cdcdd1 -> #c8c8cc (11.41:1 on --bg, 10.54:1 on --card), mid-band
instead of top-of-band.
* fix(ui): settle dark body text at ~10:1 on --bg
#c8c8cc -> #bcbcc0 (10.05:1 on --bg, 9.29:1 on --card) per visual
preview pick; low end of the dark comfort band on the page, slightly
under it on cards, still well above AAA.
* docs(ui): sync design-system guides with new dark --text token
ClawSweeper P3: color-tokens.md and accessibility.md still published
#d4d4d8 (~12.9:1); both now name #bcbcc0 (~10.1:1).
The collapsed session rail and the background-tasks toggle both rendered
icons.activity in the chat pane header cluster, so two unrelated actions
were indistinguishable without reading a tooltip. Background tasks keep
the activity metaphor; the rail now uses the companion sparkle, which is
already the product's assist glyph and does not collide with the
split-view panel icons at the end of the same row.
Closes#121418
* refactor(channels): flatten channel turn dispatch naming
* docs(plugin-sdk): narrow inbound reply compat guidance
* docs(channels): point stale references at turn defining modules
* fix(channels): preserve dispatch contracts after flattening
* chore(plugin-sdk): ratchet surface budgets after flattening
* chore(channels): ratchet removed export collisions
* fix(plugin-sdk): restore inbound reply compat exports
Restore eight still-existing legacy callable re-exports from canonical SDK seams and cover the deprecated package subpath with a table-driven compatibility test.
Raise the public export, callable export, and deprecated export budgets by exactly eight; the three maintainer-authorized zero-consumer symbols remain removed.
* test(channels): split channel turn kernel coverage
Replace the oversized kernel test with independently mocked delivery, pipeline, and finalize suites, preserving all 51 tests while removing the max-lines suppression and stale ratchet entry.
* chore(plugin-sdk): refresh inbound reply API hash
* fix(ci): align channel turn review fixes
Restore the test-local DeliveryResult type removed during the split.
Ratchet the public export, callable export, and deprecated export budgets by exactly seven: six channel-inbound plus one channel-outbound legacy re-export.
* fix(plugin-sdk): back every workspace facade export with a real src facade
packages/plugin-sdk declared 59 export subpaths but only 24 had src facade
files; the other 35 defaults pointed at nonexistent ./src/<name>.ts targets
that typecheck (types resolve via boundary dist) but fail runtime resolution.
Create the 35 missing facades (each mirrors the sibling 3-line re-export of
the core src/plugin-sdk entrypoint; all verified live, none retired), and
extend scripts/sync-plugin-sdk-exports.mts to own the facade package too:
exports are regenerated from the src directory, every facade must name a
canonical SDK entrypoint, and --check fails on dangling keys or orphan
facades. The check-changed Plugin SDK surface lane now also triggers on
packages/plugin-sdk/** so the guard actually runs when the map changes.
* fix(plugin-sdk): validate facades before any exports write
ClawSweeper P2: write mode reported a stale facade but still exited 0,
after syncRootPackageExports had already rewritten the root manifest.
Hoist facade validation ahead of all writes and exit nonzero in both
modes; an invalid facade now fails closed with no partial manifest
update (proven: hash-identical manifests plus exit 1 in write mode).
Use the canonical topic → group → account → default policy for native commands. Disabled scopes now stop before routing and dispatch; sender-specific authorization remains explicit.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Co-authored-by: JuniperTheDev <juniperthedev@users.noreply.github.com>
* fix(ui): give the microphone picker one truthful state
Discovery now reports why it stopped as a code instead of prose, so the
composer popover can pick a single coherent rendering: with no selectable
input it shows one empty state rather than a checked System default row
stacked on two different ways of saying nothing was found. Rows drop
wa-dropdown-item type="checkbox", whose own leading check toggles on
click and disagrees with the trailing check bound to the stored device;
the chat pane gateway picker had the same mix and is fixed with it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(ui): keep the microphone picker current while it is open
The empty state now promises the list keeps up, so both media surfaces
subscribe to navigator.mediaDevices devicechange for as long as they are
on screen and drop the listener when they close. The composer popover
re-runs permission-requesting discovery, since a microphone that just
appeared has hidden labels until the probe runs; the settings rows
refresh passively so hardware changes never turn into an unasked-for
browser dialog. Also drops the newly unused RealtimeTalkDeviceKind
export that failed the deadcode gate.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* fix(openai): describe personality as the canonical GPT-5 style toggle
The shipped config-schema description told operators and models to prefer
agents.defaults.promptOverlays.gpt5.personality, a retired key that config
validation rejects and doctor deletes. plugins.entries.openai.config.personality
is the only live reader (src/agents/gpt5-prompt-overlay.ts).
* docs: align prompt-overlay, truncation-notice, and pruning docs with shipped behavior
- teach plugins.entries.openai.config.personality as canonical; retired
agents.defaults.promptOverlays noted as rejected/migrated
- replace nonexistent agents.defaults.bootstrapPromptTruncationWarning with
prose describing the built-in always-on notice
- reword session-pruning internal constants as built-in behavior, name the
real contextPruning config surface
- delete stale/orphan pages (path3 harness for a never-committed script,
superseded swarms plan, landed path3 artifact-family scoping note)
- fix dead paths in reference/test.md and concepts/typebox.md
* docs: describe the embedded truncation notice as compact
The embedded runtime injects buildBootstrapPromptWarningNotice, which
deliberately omits file names and sizes; per-file diagnostics stay in
/context, status, and logs. Addresses ClawSweeper P2 on #121324.
* docs: doctor migrates the retired personality key instead of removing it
Main landed #121346 mid-flight: doctor --fix now moves
agents.defaults.promptOverlays.gpt5.personality into
plugins.entries.openai.config.personality when unset.
Rotating a device token showed the freshly issued secret through native
window.prompt from the shared Nodes operations module. A WebView without a
dialog bridge returns null for that call without rendering anything, so the
rotation completed on the Gateway and the only copy of the new credential was
discarded. Where the prompt did render it was an editable, unlabelled,
uncopyable text input that Escape threw away.
rotateDeviceToken now returns the token and DevicesPage, the visual owner,
presents it through a dedicated read-only reveal built on the canonical modal
dialog and copy button: selectable monospace value, copy control, show-once
copy, and dismissal gestures refused so a stray Escape or backdrop click cannot
destroy the secret. The shared operations module still presents no UI.
The epoch guard keeps owning the local-storage write and the list refresh, but
no longer swallows the response itself: the previous credential is already dead
on the Gateway, so a reconnect mid-rotate must not be what loses the
replacement.
The session-catalog project/group aggregator header rendered at the same
13px/500 as the session rows nested inside it, so a group read as just
another session. Bring the label into the sidebar's existing structural-label
family at one reduced step (smaller, heavier, lightly tracked) and keep the
case as authored, since these labels carry project, custom-group, and person
names. The head's color is deliberately left alone: #115646 raised it for
dark-theme readability, so type carries the whole separation.
Closes#121371
Telegram now treats disabled tool progress as authoritative even when /verbose is enabled. Enabled progress and durable/final messages retain existing behavior.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Co-authored-by: Jason O'Neal <jason.allen.oneal@gmail.com>
Co-authored-by: 徐金城0668000897 <xu.jincheng@xydigit.com>
Menu rows stacked Web Awesome's shadow icon margin on top of our flex gap
(~17px gutter), let the icon slot baseline-align ~2px above the label centre,
drew shortcut hints at 550 weight beside 400-weight labels, and left the submenu
chevron 4px inside the hint column at a heavier size.
Rows now use a single icon column, optically centred icons, quiet mono keycap
hints on one rail, a chevron sharing that rail, and a divider inset to the same
columns. The control UI mock harness also advertises the session mutation
methods so the menu renders enabled instead of fully disabled.
Every dark palette defines --secondary with the same hex as --card, so the
markdown code chip painted with --secondary collapsed into the surface it sat
on (1.00:1 against a user bubble, 1.06-1.08:1 against the flat assistant
column) and --border was too close to the background to draw an edge. Light
mode never showed the bug because it overrode both properties.
Promote the light override into the single canonical rule: code chips and code
blocks paint --bg-muted with a --border-strong edge in every theme, and the
mode-specific overrides (including the raw rgba border) are deleted.
Also fixes the theme-contrast guard, which merged repeated `:root` blocks by
overwriting instead of merging, so the default `dark` theme resolved to an
empty token map and skipped every assertion. A new case reads the chip tokens
out of the shipped rule and asserts surface and border separation from --card
and --bg on all six themes.
* fix(ui): pin sidebar sessions optimistically in the mutation owner
Pin/unpin had no visible outcome until `sessions.patch` and the canonical
`sessions.list` refresh both landed: the row, its icon, and its section kept
the pre-click value for the whole round trip.
`createSessionMutations.patch` already owns an optimistic/rollback lifecycle
for model overrides, and the capability already exposes `patchRowLocal`, but
no pin path used it. Give the pin its own pending intent in the same owner:
flip the published row up front, chain `previous` through overlapping patches
so a rollback lands on the last Gateway-confirmed value, and let the newest
intent win when an older completion's list refresh republishes stale truth.
Retiring the connection or disposing the capability drops pending intents.
Every pin surface routes through this one operation, so the sidebar button,
the row menu, sidebar drag/drop, and the Sessions page all get the same
feedback and the same rollback. A rejected zone drop still writes no sidebar
slot: `handleSidebarZoneDrop` persists only on `completed`.
Closes#121247
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* test(ui): return only the pin harness fields the cases use
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(ui): re-assert pending pin intents on canonical session rows
A sessions.changed event during a pin/unpin round trip queues a canonical
list replacement, and every Gateway session payload carries the server's
pre-patch pinned/pinnedAt. The optimistic row was therefore reverted to the
pre-click value until the patch resolved, restoring the no-feedback bug.
The session capability already has one seam where canonical rows become
published rows, so the pending pin joins swarm notes there instead of being
a write the next refresh can lose. Settling no longer republishes a newer
intent by hand; it only hands that intent the baseline the Gateway confirmed.
The optimistic write now moves pinned and pinnedAt together, matching the
Gateway (which derives pinned from pinnedAt) so an in-flight row cannot sort
into a slot the server would never produce.
* fix(ui): publish pin intents to every session list
The optimistic write only reached the primary session snapshot. The archived
and all sidebar filters keep their own published lists, so pinning from
either one still waited on the Gateway, and a canonical refresh landing
inside the patch window could leave them on a value the rollback never
corrected.
Both the intent and its rollback now republish through the roster's
decoration seam, so every held list lands on the same value in one pass and
a failed pin needs no extra canonical list to settle.
* fix(ui): take the pin rollback baseline from the published row
The rollback baseline came from the primary session snapshot alone. A
sidebar on archived or all renders its own subscribed list, so a row shown
only there recorded an unpinned baseline; a rejected unpin then rolled it
back to that guess and left a session the Gateway still has pinned looking
unpinned, with no refresh to correct it.
The roster owns every published list, so it answers where a row currently
is, and the mutation owner captures the baseline from that row.
* fix(ui): move a superseded pin's baseline to confirmation time
An overlapping intent's rollback baseline was rebased only after the older
patch's list refresh returned. A rejected unpin landing in that gap rolled
back to the pre-pin value, and if the refresh then failed the row stayed
unpinned while the Gateway held the pin.
The Gateway has committed once the patch resolves, so the baseline moves
there instead.
* docs(ui): record why pin intents need no rollback on connection retirement
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Gate reuse on the watcher-owned snapshot version and persisted entry manifest, while keeping safe paths process-owned and reconciling only changed skills.
Cache config fingerprints by object identity and reset them at the applied-config lifecycle owner.
Control UI declares its typography tokens once, in ui/src/styles/base.css:
--mono and --font-body. Fifteen rules asked for --font-mono / --font-sans
instead, names that only exist in other documents (canvas widget host,
provider OAuth page, MCP Apps spec keys). Those references are invalid at
computed-value time, so the declarations were dropped: nine had no fallback
and silently inherited the body font, and the four `font:` shorthand sites
lost their size, weight, and line height along with the family.
Point every reference at the canonical token and drop the now-dead generic
fallbacks so ui/src has one spelling per font token.
ui/src/styles/base-theme-tokens.node.test.ts already guards this bug class
for color tokens (#113726, #113776) but never listed the font aliases; add
them so a re-introduction fails the existing lane instead of shipping.
* perf(sessions): avoid full-store single patches
Single-session non-label patches no longer materialize the whole session store. Label uniqueness remains store-wide, and stale aliases created while queued are still rejected.
* fix(build): refresh plugin SDK API baseline
Current main's generated manifest lagged merged Plugin SDK surface changes; canonical refresh restores the baseline check.
* fix(agents): exclude private shell output from context compaction
## What Problem This Solves
Shell executions explicitly excluded from model context could still contribute their private output to session replay, token estimates, compaction boundaries, or branch summaries.
## Why This Change Was Made
The session projection owner now omits context-excluded shell messages, while compaction assigns those messages zero tokens and prevents private persisted entries from becoming retained-history cut boundaries.
## User Impact
Private shell output remains durably recorded for its intended owner without leaking into model-visible conversation context or wasting compaction budget; legitimate retained context remains intact.
## Context
Actual production code executed in an isolated VM reduced more than 20,000 characters of private output to zero model-visible characters while preserving eligible context. New owner and sibling regressions cover replay, compaction, branch summaries, and token accounting. Existing oxfmt formatting and scoped whitespace checks passed.
* test(agents): cover private Codex history projection
Use a manifest-first inventory with independent coverage for manifest-only bundled capabilities.
Retire the undocumented thread-ownership plugin while Doctor removes stale references.
Document Talk voice and persist only provider-scoped voice selection.
Closes#121353
Symlink .claude/skills -> ../.agents/skills so Claude Code sessions can load
repo skills (test-audit authoring gate, openclaw-testing, pr-maintainer)
that were previously Codex-only.