Skip to content

fix(agents): truncate tool-display detail on code-point boundaries#96958

Merged
vincentkoc merged 5 commits into
openclaw:mainfrom
Bartok9:salvage/96420-tool-display-codepoint
Jun 27, 2026
Merged

fix(agents): truncate tool-display detail on code-point boundaries#96958
vincentkoc merged 5 commits into
openclaw:mainfrom
Bartok9:salvage/96420-tool-display-codepoint

Conversation

@Bartok9

@Bartok9 Bartok9 commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

What Problem This Solves

Salvages #96420 by @ly-wang19 onto current main — clean single-commit rebase, original authorship preserved. The original was auto-closed by openclaw-barnacle for the author's >20-active-PR cap, not on merits; the bug is still live on main.

Tool-display middle-truncation (src/agents/tool-display-common.ts) cuts the displayed detail string at a raw UTF-16 code-unit offset, so a truncated tool detail containing emoji/astral characters can end in a lone high surrogate or begin with a lone low surrogate — rendering mojibake in the UI/log around the .

Root Cause

Symptom — A long tool detail with emoji near either truncation boundary shows a broken replacement glyph at the cut.

Root causefirstLine.slice(0, half) and firstLine.slice(-(maxStringChars - 1 - half)) operate on UTF-16 code units. When half (or the tail offset) lands inside a surrogate pair, the pair is severed.

Evidence — On current main, line 139: return \${firstLine.slice(0, half)}…${firstLine.slice(-(maxStringChars - 1 - half))}`;with no surrogate-aware back-off. The regression test fails against thismain` source.

Fix + why this level — Add surrogate-aware helpers and back the head/tail offsets off to code-point boundaries before slicing. Fixing at the truncation site is correct: this is the single place that produces a non-aligned boundary for tool display; consumers just render the returned string.

Scope / risk — Touches only the middle-truncation path in tool-display-common.ts. BMP-only details and details below the truncation threshold are unaffected (back-off is a no-op when already aligned).

Evidence

Host: macOS (Darwin 25.4.0, arm64), node v25.5.0, repo source via its own tsx.

Regression test (new): src/agents/tool-display-common.test.ts — asserts no lone surrogate at either boundary; fails without the fix.

Behavior proof — patched resolveToolVerbAndDetailForArgs (reaches coerceDisplayValue). Input: 200 UTF-16 units = 78 a + 😀 (U+1F600, surrogate pair at idx 78-79) + 120 b. With maxStringChars=160, half=79, so the naive firstLine.slice(0,79) keeps only the emoji's high surrogate.

Before (current upstream/main line 139):

OLD naive head ends with charCode: d83d
OLD naive hasLoneSurrogate = true

After (this branch):

T1 hasLoneSurrogate = false (expect false)
T1 head === 78 as = true   (emoji dropped at the cut, not half-kept)
T1 tail not lone-low = true
T2 (plain ASCII 300x) detail === `${"x".repeat(79)}…${"x".repeat(80)}` = true  (behavior-preserving)
RESULT: PASS — all 3 assertions hold on the patched resolveToolVerbAndDetailForArgs

CI results:

WITH fix:    Test Files 1 passed (1) · Tests 3 passed (3)
WITHOUT fix: Test Files 1 failed (1) · Tests 1 failed | 2 passed (3)

build-artifacts + QA Smoke CI fix (commit d862dfc1c1): an earlier import { sliceUtf16Safe } from "../utils.js" pulled src/infra/fs-safe.tsnode:fs into the browser bundle (ui/src/ui/tool-display.ts imports tool-display-common.ts), breaking ui:build with [UNLOADABLE_DEPENDENCY] Could not load ../extensions/fs-safe/walk. Fixed by extracting sliceUtf16Safe/truncateUtf16Safe into a dependency-free leaf module src/shared/utf16-slice.ts (zero node: imports — verified), re-exported from utils.js to preserve the public surface, and pointed tool-display-common.ts at the leaf module. build-artifacts and QA Smoke CI now pass on this branch.

Changes from original

  • Rebased onto current main (clean single commit, no conflicts).
  • Extracted node-free utf16-slice.ts leaf module to keep the browser bundle clean.

Credit: Salvage of #96420 by @ly-wang19 — original closed by the >20-active-PR queue cap, not on merits.

coerceDisplayValue truncated a long first-line detail value with raw
UTF-16 slice() at half = floor((maxStringChars-1)/2). When an emoji
(surrogate pair) straddles the cut boundary, the head kept a lone high
surrogate and the tail could begin on a lone low surrogate, rendering as
the replacement character. Use the existing sliceUtf16Safe helper so the
whole code point is dropped at the boundary, matching the UTF-16-safe
truncation used elsewhere in the repo. Behavior-preserving for
non-surrogate input.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@openclaw-barnacle openclaw-barnacle Bot added agents Agent runtime and tooling size: S triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. labels Jun 26, 2026
@clawsweeper

clawsweeper Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed June 26, 2026, 8:04 PM ET / 00:04 UTC.

Summary
The PR adds a dependency-free UTF-16-safe slice helper, uses it for compact tool-display detail truncation, re-exports it from src/utils.ts, and adds regression coverage.

Reproducibility: yes. Source inspection shows current main cuts tool-display details with raw UTF-16 slice(), and the PR proof gives a concrete surrogate-pair input through resolveToolVerbAndDetailForArgs.

Review metrics: 1 noteworthy metric.

  • Effective merge surface: 4 files in the potential merge commit. The live PR comparison is stale and broad, but GitHub's potential merge commit shows the actual merge result is the focused tool-display fix.

Stored data model
Persistent data-model change detected: migration/backfill/repair: docs/cli/update.md, persistent cache schema: ui/src/i18n/.i18n/vi.tm.jsonl, persistent cache schema: ui/src/i18n/.i18n/zh-CN.tm.jsonl, persistent cache schema: ui/src/i18n/.i18n/zh-TW.tm.jsonl, serialized state: extensions/deepinfra/image-generation-provider.test.ts, serialized state: extensions/google/image-generation-provider.test.ts, and 4 more. Confirm migration or upgrade compatibility proof before merge.

Root-cause cluster
Relationship: canonical
Canonical: #96958
Summary: This PR is the active candidate for the tool-display detail truncation bug; related PRs cover predecessor or adjacent truncation surfaces.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge readiness
Overall: 🐚 platinum hermit
Proof: 🦞 diamond lobster
Patch quality: 🐚 platinum hermit
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • Wait for the current CI run to finish and inspect the failed node shard if it remains red.

Risk before merge

  • [P1] Exact-head CI was still in progress during this read-only review; one node shard reported failure but logs were unavailable until the run completes.

Maintainer options:

  1. Decide the mitigation before merge
    Land the focused four-file UTF-16-safe tool-display fix after exact-head CI clears; keep sibling exec and Matrix truncation fixes in their own PRs.
  2. Pause or close
    Do not merge this PR until maintainers decide whether the risk is worth taking.

Next step before merge

  • No ClawSweeper repair lane is needed; the remaining action is ordinary maintainer review plus exact-head CI completion.

Security
Cleared: The actual potential merge result touches only string slicing helpers and tests, with no workflow, dependency, secret, package-resolution, or code-execution surface introduced.

Review details

Best possible solution:

Land the focused four-file UTF-16-safe tool-display fix after exact-head CI clears; keep sibling exec and Matrix truncation fixes in their own PRs.

Do we have a high-confidence way to reproduce the issue?

Yes. Source inspection shows current main cuts tool-display details with raw UTF-16 slice(), and the PR proof gives a concrete surrogate-pair input through resolveToolVerbAndDetailForArgs.

Is this the best way to solve the issue?

Yes for the current potential merge result. The fix is at the single compact tool-detail truncation producer, uses a dependency-free helper for the UI import graph, and preserves the existing utils.ts export surface.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against ff35f3bb2cd6.

Label changes

Label changes:

  • add proof: sufficient: Contributor real behavior proof is sufficient. The PR body/comment includes terminal proof for the exported resolver path before and after the fix, plus the dependency-free helper change that resolved the UI bundle failure.
  • add rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster and patch quality is 🐚 platinum hermit.
  • add status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): The PR body/comment includes terminal proof for the exported resolver path before and after the fix, plus the dependency-free helper change that resolved the UI bundle failure.
  • remove rating: 🧂 unranked krab: Current PR rating is rating: 🐚 platinum hermit, so this older rating label is no longer current.
  • remove merge-risk: 🚨 automation: Current PR review selected no merge-risk labels.
  • remove merge-risk: 🚨 compatibility: Current PR review selected no merge-risk labels.
  • remove status: 📣 needs proof: Current PR status label is status: 👀 ready for maintainer look.

Label justifications:

  • P2: This is a normal-priority user-visible display correctness bug in agent tool-detail rendering with limited blast radius.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): The PR body/comment includes terminal proof for the exported resolver path before and after the fix, plus the dependency-free helper change that resolved the UI bundle failure.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body/comment includes terminal proof for the exported resolver path before and after the fix, plus the dependency-free helper change that resolved the UI bundle failure.
Evidence reviewed

What I checked:

Likely related people:

  • Shakker: git blame on the current raw-slice truncation line points to commit 80c754ddf459c9b0acf3759d26b35501c296cfe4, a broad current-main commit that carried this implementation forward. (role: current-main implementation provenance; confidence: medium; commits: 80c754ddf459; files: src/agents/tool-display-common.ts)
  • Peter Steinberger: File history shows repeated prior refactors around shared tool display, UI tool display, and shared helpers that define this code path's owner boundary. (role: adjacent area contributor; confidence: medium; commits: 65ea8c60f3db, ca0159569954, d85d3c88d56d; files: src/agents/tool-display-common.ts, src/utils.ts, ui/src/ui/tool-display.ts)
  • vincentkoc: Live PR state assigns this PR to vincentkoc, and recent main history includes release-tag/file carry-forward work touching this same file before the current report. (role: recent area contributor and assignee; confidence: medium; commits: aa69b12d0086, a090c6468b78; files: src/agents/tool-display-common.ts, src/utils.ts)
  • ly-wang19: The PR body and related closed predecessor show ly-wang19 authored the original same-surface fix that this PR salvages. (role: prior same-surface contributor; confidence: medium; commits: 818ac2a37d44; files: src/agents/tool-display-common.ts, src/agents/tool-display-common.test.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. labels Jun 26, 2026
tool-display-common.ts is in the UI browser bundle graph; importing
sliceUtf16Safe from utils.js dragged node:fs (via infra/fs-safe) into the
bundle and broke build-artifacts/QA Smoke. Extract sliceUtf16Safe/truncateUtf16Safe
into src/shared/utf16-slice.ts (dependency-free) and re-export from utils.js to
preserve the existing import surface.
@Bartok9

Bartok9 commented Jun 26, 2026

Copy link
Copy Markdown
Contributor Author

Real behavior proof + build-failure fix

Host: macOS (Darwin 25.4.0, arm64), node v25.5.0, repo source via its own tsx.

1. Behavior proof — the patched resolveToolVerbAndDetailForArgs (reaches coerceDisplayValue)

Input: 200 UTF-16 units = 78 a + 😀 (U+1F600, surrogate pair at idx 78-79) + 120 b. With maxStringChars=160, half=79, so the naive firstLine.slice(0,79) keeps only the emoji's high surrogate.

Before (current upstream/main line 139, firstLine.slice(...)):

OLD naive head ends with charCode: d83d
OLD naive hasLoneSurrogate = true

The old code emits a lone high surrogate (U+D83D) at the cut.

After (this branch):

T1 detail: "aaa…(78 a)…bbb…(80 b)"
T1 hasLoneSurrogate = false (expect false)
T1 head === 78 as = true   (emoji dropped at the cut, not half-kept)
T1 tail not lone-low = true
T2 (plain ASCII 300x) detail === `${"x".repeat(79)}…${"x".repeat(80)}` = true  (behavior-preserving)
RESULT: PASS — all 3 assertions hold on the patched resolveToolVerbAndDetailForArgs

These are exactly the assertions in the added tool-display-common.test.ts. (TypeScript tsc --noEmit passes on the changed files; the repo's vitest config-bundler was wedged in my local sandbox after an unrelated ui:build, so I drove the real exported function directly via tsx — same code path, same assertions.)

2. build-artifacts + QA Smoke CI failure — diagnosed and fixed

The failures were a ui:build (browser bundle) breakage, not a test failure:

[UNLOADABLE_DEPENDENCY] Could not load ../extensions/fs-safe/walk
  ../src/infra/fs-safe.ts:16  ... No such file or directory (os error 2)
✗ Build failed in 250ms — Build failed with 7 errors
[build-all] ui:build failed

Root cause: tool-display-common.ts is in the UI browser-bundle import graph (ui/src/ui/tool-display.ts → it). The new import { sliceUtf16Safe } from "../utils.js" pulled utils.jssrc/infra/fs-safe.tsnode:fs / @openclaw/fs-safe/* into the browser bundle, which rolldown cannot resolve. The sibling PRs (#96959, #96961) did not hit this because their modules are not in the UI bundle / import a node-free path.

Fix (commit d862dfc1c1): extracted sliceUtf16Safe/truncateUtf16Safe into a dependency-free leaf module src/shared/utf16-slice.ts (zero node: imports — verified), re-exported them from utils.js to preserve the public surface, and pointed tool-display-common.ts at the leaf module. Verified:

$ grep -nE "from \"node:|fs-safe" src/shared/utf16-slice.ts  -> CLEAN: no node/fs imports
tool-display-common.ts now: import { sliceUtf16Safe } from "../shared/utf16-slice.js"

This removes the node:fs edge from the browser bundle while keeping behavior identical. Pushed — CI (the authoritative build env) will re-run build-artifacts/QA Smoke.

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jun 26, 2026
@openclaw-barnacle openclaw-barnacle Bot removed the triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. label Jun 26, 2026
@clawsweeper clawsweeper Bot removed the merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. label Jun 26, 2026
@vincentkoc vincentkoc self-assigned this Jun 26, 2026
@vincentkoc
vincentkoc requested a review from a team as a code owner June 26, 2026 23:36
@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation channel: matrix Channel integration: matrix channel: telegram Channel integration: telegram app: web-ui App: web-ui labels Jun 26, 2026
@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. merge-risk: 🚨 automation 🚨 May affect CI, automerge, proof capture, label sync, or maintainer automation. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. labels Jun 27, 2026
@Bartok9

Bartok9 commented Jun 27, 2026

Copy link
Copy Markdown
Contributor Author

CI red shard is an unrelated flaky test, not this PR

The single failing check (checks-node-compact-small-whole-3) is not caused by this change. It's a pre-existing port-binding flake in src/llm/utils/oauth/anthropic.test.ts:

FAIL src/llm/utils/oauth/anthropic.test.ts > Anthropic OAuth token responses > does not open the OAuth flow after cancellation during setup
AssertionError: expected [Function] to throw error including 'Login cancelled' but got 'listen EADDRINUSE: address already in use 127.0.0.1:53692'

That shard's other 1287 tests passed; the failure is a transient EADDRINUSE on a fixed loopback port (the OAuth test races another listener on 127.0.0.1), entirely outside this PR's surface (src/agents/tool-display-common.ts, src/shared/utf16-slice.ts, src/utils.ts, and the added test). The new UTF-16 truncation test passes on every shard. A re-run of that shard should go green.

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@vincentkoc
vincentkoc merged commit e09b9df into openclaw:main Jun 27, 2026
194 of 204 checks passed
@vincentkoc

Copy link
Copy Markdown
Member

Merged via squash.

wangmiao0668000666 pushed a commit to wangmiao0668000666/openclaw that referenced this pull request Jun 27, 2026
…penclaw#96958)

* fix(agents): truncate tool-display detail on code-point boundaries

coerceDisplayValue truncated a long first-line detail value with raw
UTF-16 slice() at half = floor((maxStringChars-1)/2). When an emoji
(surrogate pair) straddles the cut boundary, the head kept a lone high
surrogate and the tail could begin on a lone low surrogate, rendering as
the replacement character. Use the existing sliceUtf16Safe helper so the
whole code point is dropped at the boundary, matching the UTF-16-safe
truncation used elsewhere in the repo. Behavior-preserving for
non-surrogate input.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* fix(build): import surrogate-safe slice from node-free leaf module

tool-display-common.ts is in the UI browser bundle graph; importing
sliceUtf16Safe from utils.js dragged node:fs (via infra/fs-safe) into the
bundle and broke build-artifacts/QA Smoke. Extract sliceUtf16Safe/truncateUtf16Safe
into src/shared/utf16-slice.ts (dependency-free) and re-export from utils.js to
preserve the existing import surface.

* fix(build): import surrogate-safe slice from node-free leaf module

* fix(build): import surrogate-safe slice from node-free leaf module

* fix(build): import surrogate-safe slice from node-free leaf module

---------

Co-authored-by: ly-wang19 <[email protected]>
Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
Co-authored-by: Vincent Koc <[email protected]>
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jun 27, 2026
…penclaw#96958)

* fix(agents): truncate tool-display detail on code-point boundaries

coerceDisplayValue truncated a long first-line detail value with raw
UTF-16 slice() at half = floor((maxStringChars-1)/2). When an emoji
(surrogate pair) straddles the cut boundary, the head kept a lone high
surrogate and the tail could begin on a lone low surrogate, rendering as
the replacement character. Use the existing sliceUtf16Safe helper so the
whole code point is dropped at the boundary, matching the UTF-16-safe
truncation used elsewhere in the repo. Behavior-preserving for
non-surrogate input.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* fix(build): import surrogate-safe slice from node-free leaf module

tool-display-common.ts is in the UI browser bundle graph; importing
sliceUtf16Safe from utils.js dragged node:fs (via infra/fs-safe) into the
bundle and broke build-artifacts/QA Smoke. Extract sliceUtf16Safe/truncateUtf16Safe
into src/shared/utf16-slice.ts (dependency-free) and re-export from utils.js to
preserve the existing import surface.

* fix(build): import surrogate-safe slice from node-free leaf module

* fix(build): import surrogate-safe slice from node-free leaf module

* fix(build): import surrogate-safe slice from node-free leaf module

---------

Co-authored-by: ly-wang19 <[email protected]>
Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
Co-authored-by: Vincent Koc <[email protected]>
xydigit-zt pushed a commit to xydigit-zt/xydigit-zt-openclaw that referenced this pull request Jun 28, 2026
…penclaw#96958)

* fix(agents): truncate tool-display detail on code-point boundaries

coerceDisplayValue truncated a long first-line detail value with raw
UTF-16 slice() at half = floor((maxStringChars-1)/2). When an emoji
(surrogate pair) straddles the cut boundary, the head kept a lone high
surrogate and the tail could begin on a lone low surrogate, rendering as
the replacement character. Use the existing sliceUtf16Safe helper so the
whole code point is dropped at the boundary, matching the UTF-16-safe
truncation used elsewhere in the repo. Behavior-preserving for
non-surrogate input.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* fix(build): import surrogate-safe slice from node-free leaf module

tool-display-common.ts is in the UI browser bundle graph; importing
sliceUtf16Safe from utils.js dragged node:fs (via infra/fs-safe) into the
bundle and broke build-artifacts/QA Smoke. Extract sliceUtf16Safe/truncateUtf16Safe
into src/shared/utf16-slice.ts (dependency-free) and re-export from utils.js to
preserve the existing import surface.

* fix(build): import surrogate-safe slice from node-free leaf module

* fix(build): import surrogate-safe slice from node-free leaf module

* fix(build): import surrogate-safe slice from node-free leaf module

---------

Co-authored-by: ly-wang19 <[email protected]>
Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
Co-authored-by: Vincent Koc <[email protected]>
QiuYuang pushed a commit to QiuYuang/openclaw that referenced this pull request Jul 1, 2026
…penclaw#96958)

* fix(agents): truncate tool-display detail on code-point boundaries

coerceDisplayValue truncated a long first-line detail value with raw
UTF-16 slice() at half = floor((maxStringChars-1)/2). When an emoji
(surrogate pair) straddles the cut boundary, the head kept a lone high
surrogate and the tail could begin on a lone low surrogate, rendering as
the replacement character. Use the existing sliceUtf16Safe helper so the
whole code point is dropped at the boundary, matching the UTF-16-safe
truncation used elsewhere in the repo. Behavior-preserving for
non-surrogate input.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* fix(build): import surrogate-safe slice from node-free leaf module

tool-display-common.ts is in the UI browser bundle graph; importing
sliceUtf16Safe from utils.js dragged node:fs (via infra/fs-safe) into the
bundle and broke build-artifacts/QA Smoke. Extract sliceUtf16Safe/truncateUtf16Safe
into src/shared/utf16-slice.ts (dependency-free) and re-export from utils.js to
preserve the existing import surface.

* fix(build): import surrogate-safe slice from node-free leaf module

* fix(build): import surrogate-safe slice from node-free leaf module

* fix(build): import surrogate-safe slice from node-free leaf module

---------

Co-authored-by: ly-wang19 <[email protected]>
Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
Co-authored-by: Vincent Koc <[email protected]>
chenyangjun-xy pushed a commit to chenyangjun-xy/openclaw that referenced this pull request Jul 1, 2026
…penclaw#96958)

* fix(agents): truncate tool-display detail on code-point boundaries

coerceDisplayValue truncated a long first-line detail value with raw
UTF-16 slice() at half = floor((maxStringChars-1)/2). When an emoji
(surrogate pair) straddles the cut boundary, the head kept a lone high
surrogate and the tail could begin on a lone low surrogate, rendering as
the replacement character. Use the existing sliceUtf16Safe helper so the
whole code point is dropped at the boundary, matching the UTF-16-safe
truncation used elsewhere in the repo. Behavior-preserving for
non-surrogate input.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* fix(build): import surrogate-safe slice from node-free leaf module

tool-display-common.ts is in the UI browser bundle graph; importing
sliceUtf16Safe from utils.js dragged node:fs (via infra/fs-safe) into the
bundle and broke build-artifacts/QA Smoke. Extract sliceUtf16Safe/truncateUtf16Safe
into src/shared/utf16-slice.ts (dependency-free) and re-export from utils.js to
preserve the existing import surface.

* fix(build): import surrogate-safe slice from node-free leaf module

* fix(build): import surrogate-safe slice from node-free leaf module

* fix(build): import surrogate-safe slice from node-free leaf module

---------

Co-authored-by: ly-wang19 <[email protected]>
Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
Co-authored-by: Vincent Koc <[email protected]>
chenyangjun-xy pushed a commit to chenyangjun-xy/openclaw that referenced this pull request Jul 3, 2026
…penclaw#96958)

* fix(agents): truncate tool-display detail on code-point boundaries

coerceDisplayValue truncated a long first-line detail value with raw
UTF-16 slice() at half = floor((maxStringChars-1)/2). When an emoji
(surrogate pair) straddles the cut boundary, the head kept a lone high
surrogate and the tail could begin on a lone low surrogate, rendering as
the replacement character. Use the existing sliceUtf16Safe helper so the
whole code point is dropped at the boundary, matching the UTF-16-safe
truncation used elsewhere in the repo. Behavior-preserving for
non-surrogate input.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* fix(build): import surrogate-safe slice from node-free leaf module

tool-display-common.ts is in the UI browser bundle graph; importing
sliceUtf16Safe from utils.js dragged node:fs (via infra/fs-safe) into the
bundle and broke build-artifacts/QA Smoke. Extract sliceUtf16Safe/truncateUtf16Safe
into src/shared/utf16-slice.ts (dependency-free) and re-export from utils.js to
preserve the existing import surface.

* fix(build): import surrogate-safe slice from node-free leaf module

* fix(build): import surrogate-safe slice from node-free leaf module

* fix(build): import surrogate-safe slice from node-free leaf module

---------

Co-authored-by: ly-wang19 <[email protected]>
Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
Co-authored-by: Vincent Koc <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling app: android App: android app: ios App: ios app: web-ui App: web-ui channel: feishu Channel integration: feishu channel: matrix Channel integration: matrix channel: telegram Channel integration: telegram cli CLI command changes commands Command implementations docs Improvements or additions to documentation extensions: codex extensions: deepinfra extensions: fal extensions: google extensions: litellm extensions: memory-core Extension: memory-core extensions: minimax extensions: openai extensions: openrouter extensions: xai gateway Gateway runtime P2 Normal backlog priority with limited blast radius. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. scripts Repository scripts size: XL status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants