* 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.
Two geometry defects found while stress-testing the companion.
Positioning mixed coordinate spaces. `work_area()` is physical pixels of the
monitor Quick Chat is moving to, but `inner_size()` is physical pixels at the
scale of the monitor it is on now. A 640pt window on a 2x display reports
1280px, so invoking it on a 1x 1920px display centred using 1280 and landed it
320px left of centre. Re-express the window size in the target monitor's scale
first; equal scales give a ratio of 1, so single-monitor setups are untouched
to the pixel.
Widget growth resized without re-anchoring. `quickchat_set_expanded` resizes
and then repositions, but the widget path only resized, keeping the old top
edge. Growing from 360pt to 440pt in a 440pt work area left the bottom 80pt
off-screen and unreachable. `resize_window_if_needed` now reports whether it
actually changed the height so the caller re-anchors only on a real resize,
rather than moving the window when nothing changed.
Three defects that compounded into a test suite nobody could run and nobody
was running.
The suite has been red on main since 2026-07-20. `connect_frame_matches_gateway_schema`
asserts a TLS-pinned connection advertises no capabilities, but #111933 wrote
that assertion while caps held only inline-widgets, and #111920 made
`agent-kind` unconditional the same day. No textual conflict, so both landed
and the assertion has been wrong ever since. Pinning only withdraws inline
widgets, so assert exactly that.
`cargo test` could not run on macOS at all: tauri-plugin-notifications links a
Swift static library, nothing adds an rpath for the Swift runtime, and every
test binary aborted at load with `Library not loaded:
@rpath/libswift_Concurrency.dylib`. Emit the rpath from build.rs.
Neither surfaced because linux-app.yml never ran `cargo test` - it only checked
formatting and built bundles. Run the suite on Linux, and upgrade the macOS job
from `check` to `test` so link-time breakage like the rpath is caught at all;
`check` never links, so it cannot see this class of failure.
* fix(linux): restore the macOS build of the Tauri companion
Tauri gates `WebviewWindowBuilder::transparent` behind its `macos-private-api`
feature on macOS, so the companion stopped compiling for macOS when Quick Chat
landed in #109947: `no method named 'transparent' found for struct
'WebviewWindowBuilder'`. Enable that feature together with the matching
`app.macOSPrivateApi` config flag, which Tauri requires to agree with it or
tauri-build fails the allowlist check.
Quick Chat is built for a transparent window - quickchat.css makes html and
body transparent behind a 16px-radius translucent card with a drop shadow - so
compiling macOS by dropping `.transparent(true)` would ship an opaque rectangle
instead of the floating composer.
Also add a per-PR macOS `cargo check`. The only job that compiled the macOS
target sits behind a manual dispatch input in linux-app-release.yml, which is
why a broken macOS build survived for nine days.
* ci(linux): build the macOS companion on macos-15
tauri-plugin-notifications' Swift sources use typed throws
(`throws(FFIResult)`), which requires Swift 6. The macos-14 runner image still
ships Swift 5.x and cannot parse them, so the plugin's build script panics with
"Swift build failed for target: arm64-apple-macosx13.0".
This hit the new per-PR check immediately, and it means the release workflow's
`build_macos` job could not have produced a macOS bundle either - it compiles
the same crate on the same image, but only runs behind a manual dispatch input
so nothing surfaced it. Move both to macos-15.
* fix(linux): draw the macOS tray icon from a template silhouette
AppKit renders menu bar template images from the alpha channel alone, so the
opaque rounded-tile 32x32.png painted a solid white square instead of the
mascot. Add a dedicated tray-template.svg and its 36px render, a silhouette
with the eyes knocked back out, and select it only on macOS. Its geometry
mirrors the native macOS app's CritterIconRenderer at rest so both clients
wear the same face; other platforms keep the full-color 32x32.png.
* chore(linux): drop the root changelog entry from the tray icon fix
CHANGELOG.md is release-owned in this repo (AGENTS.md) and `scripts/pr
prepare-run` rejects normal PRs that touch it; release generation derives
entries from merged PRs. The release-note context lives in the PR body and
the preceding commit message instead.
* refactor(prompt): plain inbound context labels with a provenance marker
Replaces trust-worded inbound context labels ("(untrusted metadata)",
"(untrusted, for context)") with plain labels plus a fixed provenance
marker suffix appended to every OpenClaw-injected context header.
Detection keys on the marker, not label text, so strippers stay correct
across UI, TUI, replay, /trace segmentation, memory recall, and the Swift
chat preprocessor. Drops sanitizeInboundSystemTags in favor of the marker
boundary plus trusted system-prompt narration.
Renames the untrusted-named plugin SDK context identifiers to
channel-provenance names, keeping deprecated aliases registered for
removal after 2026-09-08.
Adds `openclaw doctor --fix` migrations that rewrite legacy inbound
labels in stored SQLite transcripts and purge legacy envelope-
contaminated LanceDB recall rows.
* fix(ci): resolve gate failures for plain inbound context labels
- doctor sqlite readers: open read-only connections via openNodeSqliteDatabase
so the Kysely connection-boundary guardrail holds; unexport the now-internal
transcript snapshot type (Knip unused-export gate).
- compat registry: split the record table into registry-records.ts and
plugin-sdk-subpath-records.ts. The new compat record pushed registry.ts past
the 700-line oxlint cap; suppressions are disallowed, so follow the existing
sibling record-module pattern. Public exports and PluginCompatCode literals
unchanged.
- acp-runtime test: assert current finalization behavior (newline normalization
only). The bracket de-fang and System: rewrite it expected were removed with
sanitizeInboundSystemTags; forged system lines are neutralized at the
system-event queue, the single chokepoint feeding the System:-per-line render.
- regenerate docs_map and the plugin SDK API baseline manifest.
* fix(prompt): harden inbound context label migration and drop in-band sanitizer
Review follow-ups on the plain-label + provenance-marker change:
- Remove src/security/system-tags.ts. Rewriting inbound text to neutralize
look-alike `System:`/`[System]` markers corrupted legitimate user text and is
not a real injection boundary; role separation plus external-content wrapping
is. Explicit product decision, recorded at the system-event queue.
- Narrow the LanceDB legacy-row purge so it cannot delete benign memories. It
now requires a complete known legacy sentinel line, a legacy label followed by
a fenced JSON body, or the complete legacy external-content header. The prior
predicates matched ordinary prose such as `Notes (untrusted metadata):`, and
deletion is irreversible.
- Make explicit-empty canonical ChannelStructuredContext win over the deprecated
alias via a present/absent result instead of collapsing `[]` to undefined.
- Keep `\r?` in the active-memory doctor rule. It is the only rule spanning the
header's line break, migrated assistant rows skip newline normalization, and
without it the marked-header replace wins and the body strips to empty. Added
a CRLF regression test.
- Fix stale comments that described removed behavior, and cover the Swift
prose-block strip path.
Claude-Session: https://claude.ai/code/session_01WNzsPddQmxy9Y7jKD4wAxH
* refactor(infra): move exec approvals into the shared SQLite state DB
Delete the file-runtime exec-approvals store (exec-approvals.json + .lock
sidecar machinery) on both runtimes and make the reserved
exec_approvals_config singleton row canonical. Doctor owns the one-time
import with claim/verify/receipt discipline; runtime fails closed with a
doctor instruction while un-migrated legacy state exists. The wire CAS
contract, socket semantics, and gateway auth-token derivations are
unchanged. Kills the #113929 lock-contention bug class structurally and
nets around -2.9k lines.
* fix(infra): green CI gates and retire file-era exec approvals tests
Break the migration-type import cycle with a leaf contract, regenerate the
plugin-SDK API and native i18n baselines for the intentional surface change,
drop unused exports, and replace the macOS file-era approvals test suite with
SQLite-backed behavior coverage per the obsolete-internals test policy.
* chore: green max-lines ratchet, native i18n baseline, and unused-export scan
* feat(ui): gateway-owned sidebar section order with hover-only drag grips
* fix(ui): repair sidebar section order CI gates
* fix(protocol): keep sectionOrder optional for older gateways
Native Swift clients ship separately from gateways, so new clients must decode older gateway responses that omit sectionOrder.
* fix(ui): reconcile gateway section ordering with main
* fix(state): allow lazy tables in v5 maintenance
* refactor(ui): retire prefs session section order in favor of gateway-owned order
Supersedes #113948 by deleting the unreleased ui.prefs.sessionSectionOrder key and its browser plumbing. Gateway SQLite sidebar_sections remains the single canonical store.
* refactor(gateway): remove dead sessions.observer.ask rpc
* docs: record btw and companion contract split
* fix(gateway): unexport observer model sanitizer after ask removal
* fix(wear): preserve active replies when older turns end
* fix(wear): isolate terminal events at the watch
---------
Co-authored-by: Peter Steinberger <steipete@golden-gate.local>