fix(matrix): keep HTTP error and tool-progress truncations UTF-16 safe (#102395)

* fix(matrix): keep HTTP error and tool-progress truncations UTF-16 safe

* test(matrix): prove UTF-16 truncation boundaries

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
wangmiao0668000666 2026-07-09 13:15:46 +08:00 committed by GitHub
parent 28bbbe4f60
commit acac359de6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 38 additions and 3 deletions

View file

@ -3185,6 +3185,31 @@ describe("matrix monitor handler draft streaming", () => {
await finish();
});
it("keeps truncated Matrix tool progress UTF-16 safe", async () => {
const { dispatch } = createStreamingHarness({
streaming: "progress",
previewToolProgressEnabled: true,
accountConfig: {
streaming: {
mode: "progress",
progress: { label: false, maxLineChars: 500 },
},
} as never,
});
const { opts, finish } = await dispatch();
const progressPrefix = "x".repeat(298);
const progressText = `${progressPrefix}🎉tail`;
await opts.onItemEvent?.({ progressText });
await opts.onItemEvent?.({ progressText });
await vi.waitFor(() => {
expect(sendSingleTextMessageMatrixMock).toHaveBeenCalledTimes(1);
});
expect(singleTextMessageBody()).toBe(`- \`${progressPrefix}...\``);
await finish();
});
it("replaces recovered Matrix command progress instead of leaving stale failed text", async () => {
const { dispatch } = createStreamingHarness({
streaming: "progress",

View file

@ -44,6 +44,7 @@ import { resolveInboundLastRouteSessionKey } from "openclaw/plugin-sdk/routing";
import { resolvePinnedMainDmOwnerFromAllowlist } from "openclaw/plugin-sdk/security-runtime";
import { getSessionEntry } from "openclaw/plugin-sdk/session-store-runtime";
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
import type {
CoreConfig,
MatrixConfig,
@ -434,7 +435,7 @@ function formatMatrixToolProgressMarkdownCode(text: string): string {
const clipped =
text.length <= MATRIX_TOOL_PROGRESS_MAX_CHARS
? text
: `${text.slice(0, MATRIX_TOOL_PROGRESS_MAX_CHARS - 1).trimEnd()}...`;
: `${truncateUtf16Safe(text, MATRIX_TOOL_PROGRESS_MAX_CHARS - 1).trimEnd()}...`;
const safe = clipped.replaceAll("`", "'");
return `\`${safe}\``;
}

View file

@ -41,6 +41,14 @@ describe("event-helpers", () => {
expect(fromText.statusCode).toBe(500);
});
it("keeps truncated HTTP error bodies UTF-16 safe", () => {
const parsedPrefix = `{"detail":"${"a".repeat(488)}`;
const invalidPrefix = `not-json ${"b".repeat(490)}`;
expect(buildHttpError(500, `${parsedPrefix}😀"}`).message).toBe(parsedPrefix);
expect(buildHttpError(502, `${invalidPrefix}🎉tail`).message).toBe(invalidPrefix);
});
it("serializes Matrix events and resolves state key from available sources", () => {
const viaGetter = {
getId: () => "$1",

View file

@ -1,5 +1,6 @@
// Matrix helper module supports event helpers behavior.
import type { MatrixEvent } from "matrix-js-sdk/lib/matrix.js";
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
import type { MatrixRawEvent } from "./types.js";
type MatrixEventContentMode = "current" | "original";
@ -90,10 +91,10 @@ export function buildHttpError(
if (typeof parsed.error === "string" && parsed.error.trim()) {
message = parsed.error.trim();
} else {
message = bodyText.slice(0, 500);
message = truncateUtf16Safe(bodyText, 500);
}
} catch {
message = bodyText.slice(0, 500);
message = truncateUtf16Safe(bodyText, 500);
}
}
return Object.assign(new Error(message), { statusCode });