* feat(mac): hand off gateway-owned settings panes to the dashboard
Gateway-config Settings tabs (Channels, Skills, Cron Jobs, Threads,
Instances) now default to a native handoff page with an Open in Dashboard
deep link; a Debug toggle re-enables the legacy native panes. Adds an
openclaw:native-navigate host->shell event with generation-guarded URL
fallback, and lifts the dashboard route map + same-app path validation
into OpenClawKit for future iOS reuse. Exec Approvals stays native: the
Dashboard's host-native policy editor is read-only by contract.
* fix(mac): satisfy native-i18n inventory and type budget
* fix(mac): refresh native i18n source locations
* fix(chat): close disclosures in coalesced HTML
Keep model-authored details closers visible to the shared disclosure contract when CommonMark absorbs them into type-6 HTML blocks across the Control UI and native apps.
* refactor(apps): isolate disclosure unfolding
Keep the native disclosure segmenter below its lint complexity and type-size limits, and refresh the source-owned native i18n inventory after the move.
* fix(apps): align disclosure helper visibility
Keep the extracted same-file helper compatible with Swift access control while leaving its implementation types hidden outside the source file.
* fix(apps): align source replacement visibility
Keep the source replacement helper at the same file-private boundary as the extracted disclosure unfolding types.
* feat(gateway): manage audio and video attachments
* feat(gateway): preserve generated media metadata
* fix(gateway): align seeded managed media
* fix(gateway): scope pending media trust by URL
* fix(gateway): split managed media by trust
* fix(gateway): require terminal media trust unanimity
* fix(gateway): align managed media metadata types
* fix(gateway): refresh managed media contracts
* chore(ci): model managed media test seams
* test(sessions): expect canonical media kinds
* fix(protocol): make Swift drift checks non-mutating
* test(protocol): cover core Swift gateway frames
* docs(protocol): clarify Swift model update flow
* fix(mac): move device auth tokens to shared SQLite state
* fix(mac): fail identity migration when source auth vanishes mid-read
Missing-file tolerance now applies only to the first observation; a disappearance after the auth file was observed fails the migration so the claimed identity survives for retry instead of committing without its credentials.
* fix(ios): validate auth scope UTF-8 encoding
* fix(ios): isolate SQLite auth profiles
* chore(ios): refresh native source inventory
* fix(mac): keep device auth in the shared token table
* test(mac): pin shared-key token cache semantics
* feat(chat): show live run telemetry across clients
* fix(chat): satisfy native validation gates
* fix(chat): settle advertised terminal runs
* fix(android): clear stale run usage on gaps
* chore(i18n): align native source inventory
* fix(chat): reconcile telemetry with current projections
* refactor(chat): keep run state in owner extensions
* chore(i18n): refresh native source inventory
* test(ui): expect persistent working telemetry through streaming
* fix(mac): drop stale native identity claim when key material matches
* fix(mac): acquire stale identity claim before deletion
* fix(mac): park stale identity claim instead of deleting it
Rescue path now contains no destructive unlink: the acquired claim is parked as .stale-<uuid> and validated before any continue path, closing both replacement races review found.
* fix(mac): restore parked claim when the rescue source vanishes
Review round 4: accepted the vanished-source regression (restore before continue so claim-without-source stays importable); rejected serializing the source acquisition - rescue imports nothing and post-rescue source writes follow the standard atomic claim-then-import path (invariant documented inline).
* style(apple): satisfy device identity SwiftFormat
* fix(thinking): fall back instead of erroring on unsupported adaptive level (#109351)
The adaptive level is the auto sentinel (normalizeThinkLevel maps auto ->
adaptive) meaning 'pick an appropriate level', not 'force this exact level'.
Some clients (e.g. the Apple Watch client) send it as a transport default
while the iOS app sends none, so hard-erroring on an unsupported adaptive
override rejected otherwise-valid turns from those clients. Now the auto
sentinel always falls back to a supported level; explicit concrete levels
(e.g. an explicit /think xhigh) still hard-error as before.
Closes#109351
* docs(proof): add real-behavior-proof screenshot + run log for #111301
Captured run of the real get-reply-run.media-only test (101 passed)
showing the adaptive-thinking fallback fix verified.
Co-Authored-By: yuvrajlaptop2008-byte <284412773+yuvrajlaptop2008-byte@users.noreply.github.com>
* fix(ios): inherit Watch chat thinking defaults
Co-authored-by: yuvraj thakur <284412773+yuvrajlaptop2008-byte@users.noreply.github.com>
* chore(ios): sync native i18n inventory
---------
Co-authored-by: yuvrajlaptop2008-byte <284412773+yuvrajlaptop2008-byte@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(macos): bound websocket ping so a dropped pong cannot orphan its continuation
URLSession drops the pong handler entirely when a web socket task is cancelled or
closed mid-flight. The existing gate guarded a double resume but nothing guarded
zero resumes, so the checked continuation leaked, Swift logged CONTINUATION
MISUSE, and keepaliveLoop's await never returned - wedging the keepalive for the
lifetime of the process.
Race the ping against a deadline and reuse the gate so exactly one resume wins.
Cancellation of the deadline returns without resuming, otherwise a delivered pong
would race the gate and surface as a spurious timeout.
* fix(macos): reuse URLError(.timedOut) instead of a new public ping error
Periphery flags a public enum referenced only inside its own module as dead
code in the shared OpenClawKit intersection scan. URLError keeps the timeout
indistinguishable from a transport timeout for callers, which already handle
URLSession errors from every other path, and adds no public surface.
* fix(state): name the refusing install in schema and exec-approval refusals
The newer-schema refusal told operators not to downgrade and to upgrade
OpenClaw, neither of which is actionable when two builds share one release
version string. It now names the install root that refused, both schema
versions, and warns that a linked source checkout reports its git HEAD even
when its built dist is older.
The exec-approvals gate told operators to run `openclaw doctor --fix` without
naming the state directory, so a bare invocation repaired the default root
while the scoped install stayed blocked. Both the TypeScript gate and its
Swift sibling now scope the command to the blocked state directory.
Refs #115008
* fix(gateway): name the refusing install in the startup schema refusal
* fix(mac): keep the exec-approvals gate message buildable on iOS
* fix(exec-approvals): shell-quote the state directory in the repair command
* fix(exec-approvals): state the repair directory in prose so every shell can follow it
* perf(gateway): warm handler families and cache identity avatars
* chore: drop changelog edit (release-generation-owned)
* fix(ci): regenerate protocol models, satisfy knip, absorb sidecar drift
- commit regenerated Swift GatewayModels for the additive cron.list param
- split the avatar data-url cache into assistant-avatar-cache.ts so its
factory has a production consumer (knip production scan)
- de-export prewarm family names/type; test now drives fake families only
- postReadySidecarCount 2 -> 3: main added a post-ready sidecar in parallel
* feat(ui): offer normally-absent core files instead of flagging them missing
The Agents core-files editor rendered every unwritten workspace file as a
permanently-badged MISSING tab, so an ordinary workspace looked broken. Absence
is only a fault for required files; SOUL.md, USER.md, and MEMORY.md are normal
to be absent until written.
agents.files.list now marks those entries expectedAbsent, and the editor keeps
them out of the tab strip behind an add picker that creates the file on save.
A missing AGENTS.md still shows as a fault.
* chore(protocol): regenerate Swift gateway models for expectedAbsent
* fix(gateway): carry expectedAbsent through agents.files.get
Clients merge the get response over the listed entry, so a missing-file get
that dropped the flag made a file picked from the add picker re-render as a
MISSING fault. Caught by live Control UI proof, not by the unit tests.
Add agent ownership to observer digests, scope global observer delivery and reconciliation, and centralize application session selection across navigation and reconnects.
Co-authored-by: xbrxr03 <abrarhabib03@gmail.com>
* fix(ui): keep stable chat rows in insertion order and only sort live tool/stream items by timestamp
* fix(ui): keep live rows within current turn
* fix(ui): keep current work above queued turns
* fix(ui): keep streamed replies above queued turns
* fix(ui): preserve reconnecting run order
* fix(ui): preserve causal terminal ordering
* fix(ui): bound replay rows to owning turns
* fix(ui): keep question summaries in owning turns
* fix(ui): scope question run ownership to session
* fix(ui): restore reconnecting chat run identity
* fix(ui): remove unused chatItemTimestamp import in chat-thread-build.ts
* fix(ui): correlate question summaries with agent runs
Co-authored-by: Peter Lee <li.xialong@xydigit.com>
* chore(i18n): refresh native source baseline
Co-authored-by: Peter Lee <li.xialong@xydigit.com>
* fix(ui): remove unused chatItemTimestamp export and split tool-stream test file
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* 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.