Skip to content

Commit acac359

Browse files
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 <[email protected]>
1 parent 28bbbe4 commit acac359

4 files changed

Lines changed: 38 additions & 3 deletions

File tree

extensions/matrix/src/matrix/monitor/handler.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3185,6 +3185,31 @@ describe("matrix monitor handler draft streaming", () => {
31853185
await finish();
31863186
});
31873187

3188+
it("keeps truncated Matrix tool progress UTF-16 safe", async () => {
3189+
const { dispatch } = createStreamingHarness({
3190+
streaming: "progress",
3191+
previewToolProgressEnabled: true,
3192+
accountConfig: {
3193+
streaming: {
3194+
mode: "progress",
3195+
progress: { label: false, maxLineChars: 500 },
3196+
},
3197+
} as never,
3198+
});
3199+
const { opts, finish } = await dispatch();
3200+
const progressPrefix = "x".repeat(298);
3201+
3202+
const progressText = `${progressPrefix}🎉tail`;
3203+
await opts.onItemEvent?.({ progressText });
3204+
await opts.onItemEvent?.({ progressText });
3205+
3206+
await vi.waitFor(() => {
3207+
expect(sendSingleTextMessageMatrixMock).toHaveBeenCalledTimes(1);
3208+
});
3209+
expect(singleTextMessageBody()).toBe(`- \`${progressPrefix}...\``);
3210+
await finish();
3211+
});
3212+
31883213
it("replaces recovered Matrix command progress instead of leaving stale failed text", async () => {
31893214
const { dispatch } = createStreamingHarness({
31903215
streaming: "progress",

extensions/matrix/src/matrix/monitor/handler.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import { resolveInboundLastRouteSessionKey } from "openclaw/plugin-sdk/routing";
4444
import { resolvePinnedMainDmOwnerFromAllowlist } from "openclaw/plugin-sdk/security-runtime";
4545
import { getSessionEntry } from "openclaw/plugin-sdk/session-store-runtime";
4646
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
47+
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
4748
import type {
4849
CoreConfig,
4950
MatrixConfig,
@@ -434,7 +435,7 @@ function formatMatrixToolProgressMarkdownCode(text: string): string {
434435
const clipped =
435436
text.length <= MATRIX_TOOL_PROGRESS_MAX_CHARS
436437
? text
437-
: `${text.slice(0, MATRIX_TOOL_PROGRESS_MAX_CHARS - 1).trimEnd()}...`;
438+
: `${truncateUtf16Safe(text, MATRIX_TOOL_PROGRESS_MAX_CHARS - 1).trimEnd()}...`;
438439
const safe = clipped.replaceAll("`", "'");
439440
return `\`${safe}\``;
440441
}

extensions/matrix/src/matrix/sdk/event-helpers.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,14 @@ describe("event-helpers", () => {
4141
expect(fromText.statusCode).toBe(500);
4242
});
4343

44+
it("keeps truncated HTTP error bodies UTF-16 safe", () => {
45+
const parsedPrefix = `{"detail":"${"a".repeat(488)}`;
46+
const invalidPrefix = `not-json ${"b".repeat(490)}`;
47+
48+
expect(buildHttpError(500, `${parsedPrefix}😀"}`).message).toBe(parsedPrefix);
49+
expect(buildHttpError(502, `${invalidPrefix}🎉tail`).message).toBe(invalidPrefix);
50+
});
51+
4452
it("serializes Matrix events and resolves state key from available sources", () => {
4553
const viaGetter = {
4654
getId: () => "$1",

extensions/matrix/src/matrix/sdk/event-helpers.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// Matrix helper module supports event helpers behavior.
22
import type { MatrixEvent } from "matrix-js-sdk/lib/matrix.js";
3+
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
34
import type { MatrixRawEvent } from "./types.js";
45

56
type MatrixEventContentMode = "current" | "original";
@@ -90,10 +91,10 @@ export function buildHttpError(
9091
if (typeof parsed.error === "string" && parsed.error.trim()) {
9192
message = parsed.error.trim();
9293
} else {
93-
message = bodyText.slice(0, 500);
94+
message = truncateUtf16Safe(bodyText, 500);
9495
}
9596
} catch {
96-
message = bodyText.slice(0, 500);
97+
message = truncateUtf16Safe(bodyText, 500);
9798
}
9899
}
99100
return Object.assign(new Error(message), { statusCode });

0 commit comments

Comments
 (0)