Respawn one-shot macOS commands with Node's OpenSSL-backed CA store when system CA loading is inherited. This avoids an indefinite shutdown join on the Keychain loader worker while preserving system CA for Gateway, interactive, and explicit --use-system-ca processes.
* feat(clients): copy visible markdown, full-message reader for truncated messages, and independent reasoning/tool toggles
* chore(i18n): translate new reader and trace strings and regenerate catalogs
* test(ios): adopt reader display-options API in render smoke tests
* 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
* test(imessage): mock core turn dispatch for media policy coverage
PR #110095 missed this iMessage media-policy test file.
* test(imessage): mock core turn dispatch for last-route coverage
* test(whatsapp): mock core turn dispatch for broadcast coverage
* test(imessage): drop type-dead terminal-admission branch in media harness
* feat: normalize web search output contract
* refactor(agents): promote web_search contract from a dedicated output module
* fix(agents): wrap unwrapped web_search text and pass unknown provider payloads through as raw
* fix(agents): gate web_search results branch on conforming rows and cover metadata fields
* fix(agents): gate every provider text path at the web_search boundary
* test(agents): align web_search fixtures with boundary-owned wrapping
* fix(agents): make the web_search boundary own the untrusted-content envelope
* fix(agents): report declared web_search errors first and align the documented contract
* fix(agents): bind envelope stripping to real markers and densify result rows
* fix(agents): emit canonical urls and a closed error code from web_search
* fix(agents): keep structured provider error diagnostics in the wrapped message
* fix(agents): satisfy production export and lint gates for the web_search contract
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
* fix(qa-lab): close transport before gateway teardown
* test(qa-lab): bind teardown order to production plan
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(mcp): reject sandbox CSP metadata that normalizes to no policy
A present ?csp= value that decodes to valid JSON but is not a usable CSP
(e.g. null or a wrong-shaped object) normalized to undefined and was
indistinguishable from an absent parameter, so the gateway served proxy
HTML under the default policy. encodeCsp omits the query param entirely
for such values, so a present-but-empty policy is never legitimate;
throw so the sandbox endpoint fails closed with 400. Found by review on
the revert of 73685b4e7c946; the gap predates that commit.
* fix(mcp): treat empty csp query value as malformed, not absent
?csp= with an empty value passed the falsy absent-guard and served proxy
HTML under the default policy. Only a truly absent parameter (null) may
skip validation; an empty string now falls through to JSON.parse and
fails closed with 400.
* refactor(gateway): drop redundant sandbox CSP handler guard
decodeMcpAppSandboxCsp now throws for every present-but-unusable value
(1ca26c508d added the same fail-closed behavior at the handler seam),
so the handler-level 'present but falsy' check is unreachable. Keep the
invariant in the decoder, which owns policy decode semantics.
* fix(test): reset diagnostic listener-presence mirror between non-isolated files
resetOpenClawGlobalDiagnosticState clears the listener sets and deletes
the diagnostic-events state key, but the listener-presence counts live
under a separate globalThis record and survived, so
hasInternalDiagnosticEventListeners() stayed true for every later file
in the worker once any file leaked a registration (e.g. the import-time
listener in src/logging/diagnostic-run-activity.ts whose stop handle is
lost to the module-registry reset). Zero the counts to match the cleared
sets. Root cause of the model-call-diagnostics flake in CI run
29624203224; #110288 added the victim-side reset, this fixes the class.
* fix(tools-manager): bound GitHub releases response body to prevent unbounded read
The agent helper-tool fallback for fd and ripgrep read GitHub's
latest-release response with Response.json(). That consumes the
complete remote body before parsing it, so a faulty or compromised
response could grow agent memory without a bound.
This change makes the release-metadata fetch use the shared
readResponseWithLimit helper with a 1 MiB cap.
Ref. https://github.com/openclaw/openclaw/pull/98480
* fix(tools-manager): use bounded release reader
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>