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
* 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
* fix(plugins): bound plugin manifest metadata file reads
* fix(plugins): use correct Error type check in oversized manifest catch
readRegularFileSync throws a plain Error (not RangeError) when the file
exceeds maxBytes. Change the catch-block type check from RangeError to
Error to properly detect oversized plugin manifests and emit the
subsystem warning.
* test(plugins): exercise valid oversized manifests
Co-authored-by: 陈宪彪0668000387 <chen.xianbiao@xydigit.com>
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(config): use Object.hasOwn in restoreEnvVarRefs to avoid prototype pollution
When key comes from Object.entries(incoming) on user config data,
'key in parsed' traverses the prototype chain. If a config key
collides with an Object.prototype property (e.g. 'toString'),
the check incorrectly returns true and calls restoreEnvVarRefs
with a prototype function instead of a config value.
Replace with Object.hasOwn(parsed, key) to check only own properties.
* test(config): prove inherited env refs are ignored
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: Peter Steinberger <peter@steipete.me>
* fix(shared): reject HTTP status codes outside the 100-599 range
extractHttpStatusMatch only checked Number.isFinite, so 3-digit
sequences such as "000", "099", "600", and "999" were accepted as
valid HTTP statuses. Downstream code then surfaced them as "HTTP 0"
in user-visible messages and fed them into retry/classification
branches (e.g. isCloudflareOrHtmlErrorPage checks `code < 500` and
`code < 600`). Restrict the extracted code to the IANA HTTP status
code range (100-599).
Co-authored-by: Patrick Erichsen <patrick.a.erichsen@gmail.com>
* fix(shared): validate all parsed HTTP status prefixes
---------
Co-authored-by: Patrick Erichsen <patrick.a.erichsen@gmail.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(config): restore process.env after invalid config rejection
When config validation fails, env vars from the rejected config
(env.vars in openclaw.json) leaked into process.env and were never
cleaned up. In long-running gateways this meant rejected config
values could persist until process restart.
Add restoreEnvChangesIfUnchanged calls in both sync (loadConfigLocal)
and async (readConfigFileSnapshotInternal) validation-failure paths,
matching the pattern already used by suspicious-config recovery paths.
* fix(config): restore env before observer and on duplicate-agent-dir rejection
- Add restoreEnvChangesIfUnchanged before DuplicateAgentDirError throw
in the sync loadConfigLocal path so env vars from rejected config
do not leak when duplicate agent directories are detected.
- Move restoreEnvChangesIfUnchanged before observeLoadConfigSnapshot
in the sync validation-failure path so config health/audit
observation does not see rejected env values.
- Add entry-point regression tests exercising sync loadConfig,
async readConfigFileSnapshot, and duplicate-agent-dir rejection
paths through createConfigIO with real config files.
* fix(config): restore env after invalid snapshot
Co-authored-by: xialonglee <li.xialong@xydigit.com>
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(gateway): bound usage.list days parameter to 100 years
parseDays accepted any finite number including 1e300, which downstream
resolveTrailingDays would feed into Date arithmetic as a day offset,
overflowing the ECMAScript Date range and surfacing as a misleading
"calendar day does not exist in requested time zone" error. Reject
day counts above 100 years (36600); callers wanting truly unbounded
history should use `range: "all"`.
Co-authored-by: Patrick Erichsen <patrick.a.erichsen@gmail.com>
* fix(gateway): clamp oversized usage day ranges
---------
Co-authored-by: Patrick Erichsen <patrick.a.erichsen@gmail.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>