Accepted Nextcloud Talk webhook and Nostr relay messages now use the shared durable channel-ingress queue. Claims stay owned until reply-lane terminal completion; bounded append and delivery retries fail closed into dead-letter handling; restart recovery owns redelivery; and Nostr cursors advance only after relay EOSE and durable progress.
Related: #109657
* feat(ui): custom session icons with agent-drawable SVG and nav-parity pinned rows
* wip: slice-2 interleaved sidebar zone (pre-review)
* fix(ui): trim icon exports, bypass protocol barrel in startup bundle, regen Swift protocol client
* fix(ui): preserve unknown-agent zone entries on writes, prune entries on any unpin
* fix(ui): reset keeps unloaded session slots; enforce SVG byte cap on canonical form
* fix(ui): archiving a pinned session retires its sidebar zone slot
* fix(ui): persist drag-pinned zone slot only after the pin patch lands
* fix(ui): recompute drop insertion against freshest zone order on pin ack
* fix(ui): consume self-drop events before the zone bailout
* docs(ui): note the non-sidebar unpin pruning contract on pruneSidebarSessionEntry
* refactor(ui): extract pure session-tree projection; drop unused zone type export
* fix(agents): bound IDENTITY.md reads to prevent OOM
* fix(agents): reuse shared workspace bootstrap file size limit for IDENTITY.md
* chore: retrigger CI after cancelled gate run
* chore: retrigger CI after runner tooling flake
* chore: retrigger CI after main typecheck fix and runner brew flake
* fix(identity): bound explicit --identity-file imports through shared loader
* fix(agents): unexport parseIdentityMarkdown to satisfy production deadcode scan
* chore: retrigger CI after stuck previous run
* chore: retrigger CI after stuck checks-node-compact-large-4 run
* chore: retrigger CI after stuck checks-node-compact-large-4 and flaky small-9
* fix(agents): report oversized identity file size to the user
When `--identity-file` points at a file exceeding the size limit, the
`no identity data found` error is misleading. Re-throw the fs-safe
`exceeds` error with a human-readable message so the CLI surfaces the
actual cause.
* fix(agents): update test assertion for oversized identity diagnostic
* fix(agents): attach caught error as cause in oversized identity diagnostic
* fix(agents): preserve symlinked identity files
* fix(agents): classify identity size errors exactly
Co-authored-by: 陈宪彪0668000387 <chen.xianbiao@xydigit.com>
* fix(agents): preserve supported identity avatars
Co-authored-by: 陈宪彪0668000387 <chen.xianbiao@xydigit.com>
* fix(agents): keep identity limit private
Co-authored-by: 陈宪彪0668000387 <chen.xianbiao@xydigit.com>
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: Peter Steinberger <peter@steipete.me>
* 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(clients): gateway session groups, batch operations, session inspector, and new-session options
* chore(i18n): translate session management strings and regenerate catalogs
* chore(i18n): complete translation artifacts for session management strings
* fix(clients): fail closed when a transport cannot honor agent or base-ref session options
* fix(clients): authoritative agent identity for new-session parents and revision-keyed sidebar group refresh
* fix(clients): refetch session groups on remote catalog events and correct Swedish repository-root translation
* fix(clients): resilient new-session popover, revision-keyed group manager, and popover string translations
* fix(clients): scope reset fallback to plain new-session requests and revision-key the inspector groups
* fix(clients): reject empty agent catalogs and refresh sessions on remote group events
* refactor(clients): drop unused agents fetch wrapper and runtime memberwise init flagged by periphery
* chore(i18n): realign locale artifacts after rebase onto model-controls main
* chore(clients): split session creation into the session-actions extension and realign i18n after ask-user main rebase
* chore(i18n): resync derived artifacts after question-card main rebase
The entry stylesheet aggregated approval, config, config-quick, and
lobster-pet styles even though every consumer is a lazy route chunk or
lazily defined element, and two small UI landings tipped startup CSS to
exactly the 42.0 KiB budget, failing all QA Smoke profiles on main (run
29643357786). Move each stylesheet to its owning lazy module — pages
already own their CSS this way (chat, agents, cron) — and keep only the
shell's pre-hydration approval booting subset in the entry via the new
approval-boot.css. Startup CSS drops 42.0 -> 36.0 KiB gzip; tighten the
budget 42 -> 38 KiB to lock in the recovery, matching the startup-JS
precedent from #110528.
* 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
* feat(ui): session unarchive flows with undo toast
Archiving from the web UI was silent and one-way. Adds a shared single-slot
toast with Undo (restores archive state, pin, and active selection), a sidebar
'View archived' entry into Settings -> Sessions, an Active|Archived segment
replacing the buried archived-only chip, an inline Restore button on archived
chats' disabled composer, and a guarded 'Delete all archived' bulk action that
fully re-enumerates archived sessions (paginated, aborts on any abnormal page)
and passes the protocol archivedOnly guard. No gateway/protocol changes.
* test(ui): adapt archive undo sidebar harness
* fix(ui): unexport ToastOptions and widen test mock casts for CI gates
* perf(ui): drop zod and lazy-load json5 out of Control UI startup
zod's only UI consumer was the custom-theme shape layer, whose deep CSS
validators already re-check every token; replace the two shallow schemas
with a plain record reader and delete the zod jitless CSP shim + test.
Startup keeps one schema library total (typebox, protocol-owned, already
lazy via the approval page).
json5 now loads through a lazy runtime boundary: strict JSON.parse is the
fast path, the parser warms with the config editor and whenever a config
snapshot or raw draft actually needs JSON5 (comments, trailing commas).
The redaction sanitize path parses the authoritative raw once at snapshot
ingestion (async-safe) and submits against the carried parsed fact, so
secret-placeholder handling never races the lazy parser.
Startup JS: 321.8 -> 295.8 KiB gzip, 13 -> 12 requests; budgets ratchet
to 310 KiB / 18 requests.
* fix(ui): gate config submits on pending JSON5 original parse
A JSON5 config racing the first parser load could reach the sanitize step
with a null parsed original and pass redaction placeholders through.
setConfigRawOriginal now parses synchronously whenever the parser is warm
and tracks a pending promise otherwise; submit, auto-save, and teardown
flush paths defer to that promise (teardown keeps its synchronous prefix
when no parse is pending).
* fix(ui): keep teardown config flush synchronous and JSON5 diff cache non-sticky
Teardown flush must dispatch before unload destroys the context; the
gateway's restore-or-reject sentinel contract backs the rare unsanitized
window. Raw-diff parse failures no longer cache while the lazy JSON5
parser is still loading, so a transient cold-parser miss retries on the
next render instead of pinning an empty diff.
* fix(ui): harden lazy JSON5 boundary against double-submit and failed loads
Claim the config busy flag before awaiting a pending JSON5 original parse
so a second click cannot slip past the busy state (autosave overlap is
already serialized by the in-flight registry and drain discipline). A
rejected json5 chunk import now resets the loader for retry, the per-state
pending promise is never-rejecting and self-clearing, and fire-and-forget
warms swallow rejections.
* docs(ui): note autosave entry serialization at the JSON5 parse await
* fix(ui): fill raw pending-changes diff once the lazy JSON5 parser lands
First diff open could race the parser chunk and render an empty list with
nothing scheduling a retry; renderConfig now re-renders when the warm
completes, and the browser test warms the parser in setup to assert the
steady state the view module guarantees in prod.
* perf(ui): lazy-load the lobster pet out of the Control UI startup graph
Keep the lightweight lobster contract on the sidebar's static path while idle-loading the heavy pet implementation. Move the logo stand-in custom element into the pet module so both decorative elements upgrade together after the lazy import.
* style(ui): format lobster-pet lazy boundary
* fix(ui): clear the lobster-pet chunk cache on load failure
A rejected import stayed cached forever and surfaced as an unhandled
rejection; clearing it lets a later scheduling call retry.
* fix(ui): retry the lobster-pet chunk once connectivity returns