* fix(e2e): keep phase log tails UTF-8 safe
The phase runner keeps a bounded in-memory log tail per phase and writes it
to stderr when a phase fails. The byte-based cut can land inside a multi-byte
UTF-8 character, and decoding from that offset emits U+FFFD replacement
characters at the start of the diagnostics tail. Reuse the decodeUtf8Tail
helper from scripts/e2e/lib/text-file-utils.mjs (#109669) so the retained
window starts on a character boundary.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(e2e): declare decodeUtf8Tail in text-file-utils types
phase-runner.ts imports decodeUtf8Tail from text-file-utils.mjs; the
adjacent declaration file must export it or check-test-types fails with
TS2305.
Co-Authored-By: Claude <noreply@anthropic.com>
* refactor(e2e): reuse tailText wrapper for phase log tails
Co-Authored-By: Claude <noreply@anthropic.com>
* test(e2e): focus phase tail regression
Co-authored-by: wangmiao0668000666 <wang.miao86@xydigit.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(scripts): use truncateUtf16Safe for LLM text truncation in bench dump
Three .slice(0, N) calls truncate assistant/tool result/final text for
logging output. Raw String.prototype.slice operates on UTF-16 code
units and can split surrogate pairs (emoji, CJK characters) straddling
the cut boundary, producing invalid Unicode in trail and result output.
Replace with truncateUtf16Safe from @openclaw/normalization-core/utf16-slice,
the same helper already used across the codebase for safe boundary truncation.
Co-Authored-By: nebulacoder-v8.0 <noreply@zte.com.cn>
* test(scripts): add behavioral proof for UTF-16 safe truncation in bench dump
Co-Authored-By: Pick-cat <huang.ting3@xydigit.com>
* test(scripts): replace root proof harness with repository-native bench dump truncation tests
Remove the 163-line root-level proof artifact and add focused vitest
tests in test/scripts/ that exercise the exact truncateUtf16Safe call
sites and format strings used by the bench script — without needing a
live LLM benchmark run.
Co-Authored-By: Pick-cat <huang.ting3@xydigit.com>
* test(scripts): remove redundant UTF-16 helper coverage
---------
Co-authored-by: nebulacoder-v8.0 <noreply@zte.com.cn>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Replace .slice(0, maxChars) with truncateUtf16Safe in two locations:
- readBoundedResponseText (error body truncation)
- truncateBody (issue body preview)
Without this fix, an emoji (or any astral character) straddling the
truncation boundary would leave a lone surrogate in the output, producing
invalid UTF-16 that serializes to a garbled replacement character.
Follows the same pattern as Leon-SK668's PRs #108184, #108193, #108208.
* build(protocol): make @openclaw/gateway-protocol publishable to npm
* chore(mxc): align package version with release train
* fix(release): support gateway-only core packages
The About page commit row now embeds the commit's committer timestamp
(commitAt, advisory like branch/dirty) and renders it as an always-relative
age with the exact localized date-time on hover. The copy button becomes a
compact borderless icon revealed on hover/focus only where hover exists;
touch keeps it visible.
* feat(channels): batch 1 producers drop media placeholder bodies
Media-placeholder program batch 1: Google Chat, Zalo, LINE, and
Mattermost stop minting <media:kind> placeholder bodies. Media-only
messages carry an empty caption plus one structured fact per native
attachment (type-only when a download fails or is rejected, so payload
positions and kind signals stay aligned). The shared
formatMediaPlaceholderText SDK formatter renders text-only carriers
(Mattermost pending-room lines) from structured facts; per-channel
placeholder builders and the expected-count side channel are deleted.
* fix(mattermost): satisfy type, deadcode, and SDK manifest gates
* feat: add session discussion panel seam
* fix: keep discussion iframe cookie-capable and changelog release-owned
* test: cover cookie-capable discussion iframe sandbox
* fix: stretch discussion panel host so the embed fills the rail
* fix: keep provider failures retryable and probe discussion availability before showing the action
* fix: block same-origin discussion embeds, show action on catalog sessions, close stale panel on reconnect
* fix: scope discussion probes and panel callbacks to the issuing connection
* fix: dedupe in-flight discussion probes per session
* fix: retry superseded discussion probes and key-scope panel results
* fix: regenerate Swift protocol models and extend advertised-method expectations
* style: format chat-pane-header
* fix: regenerate Kotlin protocol models and date discussion methods in the 2026.7 train
* chore: restore release-owned changelog to main state
* chore: keep changelog untouched relative to merge-base
Follow-up to #111393: run-tsgo, check-tsgo-core-boundary, profile-tsgo,
and the format-docs oxfmt shim still resolved node_modules/.bin from the
current checkout only, so dependency-less linked worktrees died with
ENOENT before oxlint's boundary prep could run. All four now use
resolveRepoToolBinPath, completing local lint/typecheck tooling for
linked worktrees.
* chore(pr): reuse green hosted CI from patch-identical pre-rebase heads (24h window)
* fix(pr): type-safe fixtures and lint cleanup for hosted-CI reuse tests
* feat(ui): live agent activity subtitles on running sidebar sessions
* refactor(ui): lazy-load narration controller off the startup chunk, unexport test-only symbols
* chore(ui): raise startup JS budget to 312 KiB for the lazy narration feature
* test(ui): restore real timers after narration controller cases
* feat(browser): send pages to the main session from the Chrome extension
One-click page share in the OpenClaw Chrome extension: toolbar popup with an
optional note, page/selection context menu, and Alt+Shift+S. Capture is
selection-first with a readability heuristic, X/Twitter thread extraction, and
Google Docs plain-text export via the user's session cookies. Payloads ride the
existing paired relay WebSocket as a new pageShare message; the gateway-only
page-share sink wraps page text in the external-content safety boundary, then
enqueues a main-session system event and requests an immediate heartbeat
(hooks/wake semantics). Node-hosted relays report a clear unsupported error.
Capture heuristics adapted from Nat Eliason's MIT-licensed send-to-openclaw.
Co-authored-by: Codex <codex@openai.com>
* fix(browser): keep page-controlled metadata inside the share safety boundary
Review findings: move title/URL inside wrapExternalContent (a hostile <title>
must not become trusted header text), prefer the user's selection over the
full Google Docs export, and pass the context-menu selectionText through so
iframe selections and selections cleared during relay reconnect still win.
* fix(browser): bind context-menu shares to the click-time document
Selection shares from the context menu now send the click snapshot directly
(no recapture), so navigations during relay reconnect cannot mislabel the
source and iframe selections are preserved. The Google Docs selection probe
scans all accessible frames before falling back to the full-document export.
* test(browser): expect the page-share handler in relay server args
* fix(browser): probe only the main frame for Google Docs selections
All-frame injection rejects wholesale when one frame is inaccessible and
returns child frames in nondeterministic order, so the probe now reads the
main frame only. Child-frame selections still share correctly through the
context menu's click-time selectionText; toolbar/shortcut entry sends the
full page for that case (named tradeoff in the code comment).
* fix(browser): satisfy page-share CI gates
---------
Co-authored-by: Codex <codex@openai.com>