Skip to content

Commit 5ba981a

Browse files
Pandah97steipete
andauthored
fix(qa-matrix): keep room scenario preview truncation UTF-16 safe (#102606)
* fix(qa-matrix): keep room scenario preview truncation UTF-16 safe Replace 7x `.slice(0, N)` with `truncateUtf16Safe(..., N)` in runMatrixStreamingPreviewScenario and runMatrixToolProgressScenario to prevent surrogate pair corruption in body/formattedBody previews. Ref. lsr911 pattern — mechanical substitution, no behavior change. * test(qa-matrix): prove UTF-16 artifact boundaries Co-authored-by: 黄剑雄0668001315 <[email protected]> --------- Co-authored-by: Peter Steinberger <[email protected]>
1 parent 6fef79a commit 5ba981a

2 files changed

Lines changed: 39 additions & 11 deletions

File tree

extensions/qa-matrix/src/runners/contract/scenario-runtime-room.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import { randomUUID } from "node:crypto";
33
import { writeFile } from "node:fs/promises";
44
import path from "node:path";
5+
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
56
import type { MatrixQaObservedEvent } from "../../substrate/events.js";
67
import {
78
MATRIX_QA_BLOCK_ROOM_KEY,
@@ -271,6 +272,10 @@ function buildMatrixStreamingPreviewFinalText(prefix: string) {
271272
].join(" ");
272273
}
273274

275+
function truncateMatrixQaArtifactPreview(value: string | undefined) {
276+
return value === undefined ? undefined : truncateUtf16Safe(value, 200);
277+
}
278+
274279
async function runMatrixStreamingPreviewScenario(
275280
context: MatrixQaScenarioContext,
276281
params: {
@@ -347,8 +352,8 @@ async function runMatrixStreamingPreviewScenario(
347352
return {
348353
artifacts: {
349354
driverEventId,
350-
previewFormattedBodyPreview: preview.event.formattedBody?.slice(0, 200),
351-
previewBodyPreview: preview.event.body?.slice(0, 200),
355+
previewFormattedBodyPreview: truncateMatrixQaArtifactPreview(preview.event.formattedBody),
356+
previewBodyPreview: truncateMatrixQaArtifactPreview(preview.event.body),
352357
previewEventId: preview.event.eventId,
353358
previewMentions: preview.event.mentions,
354359
reply: finalReply,
@@ -422,7 +427,7 @@ function truncateMatrixQaToolProgressBody(body: string | undefined) {
422427
if (!body) {
423428
return "<none>";
424429
}
425-
return body.length <= 240 ? body : `${body.slice(0, 237)}...`;
430+
return body.length <= 240 ? body : `${truncateUtf16Safe(body, 237)}...`;
426431
}
427432

428433
function describeMatrixQaToolProgressCandidate(event: MatrixQaObservedEvent) {
@@ -672,9 +677,11 @@ async function runMatrixToolProgressScenario(
672677
return {
673678
artifacts: {
674679
driverEventId,
675-
previewBodyPreview: progressAfterFinal.event.body?.slice(0, 200),
680+
previewBodyPreview: truncateMatrixQaArtifactPreview(progressAfterFinal.event.body),
676681
previewEventId: progressPreviewEventId,
677-
previewFormattedBodyPreview: progressAfterFinal.event.formattedBody?.slice(0, 200),
682+
previewFormattedBodyPreview: truncateMatrixQaArtifactPreview(
683+
progressAfterFinal.event.formattedBody,
684+
),
678685
previewMentions: progressAfterFinal.event.mentions,
679686
reply: finalReply,
680687
token: params.finalText,
@@ -836,9 +843,9 @@ async function runMatrixToolProgressScenario(
836843
return {
837844
artifacts: {
838845
driverEventId,
839-
previewBodyPreview: progress.event.body?.slice(0, 200),
846+
previewBodyPreview: truncateMatrixQaArtifactPreview(progress.event.body),
840847
previewEventId: previewRootEventId,
841-
previewFormattedBodyPreview: progress.event.formattedBody?.slice(0, 200),
848+
previewFormattedBodyPreview: truncateMatrixQaArtifactPreview(progress.event.formattedBody),
842849
previewMentions: progress.event.mentions,
843850
reply: finalReply,
844851
token: params.finalText,

extensions/qa-matrix/src/runners/contract/scenarios.test.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2288,6 +2288,8 @@ describe("matrix live qa scenarios", () => {
22882288
eventId: "$quiet-preview",
22892289
sender: "@sut:matrix-qa.test",
22902290
type: "m.room.message",
2291+
body: "",
2292+
formattedBody: "",
22912293
},
22922294
since: "driver-sync-preview",
22932295
}))
@@ -2337,11 +2339,15 @@ describe("matrix live qa scenarios", () => {
23372339
});
23382340
const artifacts = result.artifacts as {
23392341
driverEventId?: unknown;
2342+
previewBodyPreview?: unknown;
23402343
previewEventId?: unknown;
2344+
previewFormattedBodyPreview?: unknown;
23412345
reply?: { eventId?: unknown };
23422346
};
23432347
expect(artifacts.driverEventId).toBe("$quiet-stream-trigger");
2348+
expect(artifacts.previewBodyPreview).toBe("");
23442349
expect(artifacts.previewEventId).toBe("$quiet-preview");
2350+
expect(artifacts.previewFormattedBodyPreview).toBe("");
23452351
expect(artifacts.reply?.eventId).toBe("$quiet-final");
23462352

23472353
expectSentTextMessage(sendTextMessage, {
@@ -2357,6 +2363,8 @@ describe("matrix live qa scenarios", () => {
23572363

23582364
it("captures partial preview text messages before the finalized Matrix reply", async () => {
23592365
const previewEventId = "$partial-preview";
2366+
const previewBody = `${"b".repeat(199)}😀tail`;
2367+
const previewFormattedBody = `${"f".repeat(199)}😀tail`;
23602368
const fallbackFinalText = "MATRIX_QA_PARTIAL_STREAM_PREVIEW_COMPLETE";
23612369
const { sendTextMessage } = mockMatrixQaRoomClient({
23622370
driverEventId: "$partial-stream-trigger",
@@ -2365,7 +2373,8 @@ describe("matrix live qa scenarios", () => {
23652373
event: matrixQaMessageEvent({
23662374
kind: "message",
23672375
eventId: previewEventId,
2368-
body: "partial preview",
2376+
body: previewBody,
2377+
formattedBody: previewFormattedBody,
23692378
}),
23702379
since: "driver-sync-preview",
23712380
},
@@ -2393,11 +2402,15 @@ describe("matrix live qa scenarios", () => {
23932402
const result = await runMatrixQaScenario(scenario, matrixQaScenarioContext());
23942403
const artifacts = result.artifacts as {
23952404
driverEventId?: unknown;
2405+
previewBodyPreview?: unknown;
23962406
previewEventId?: unknown;
2407+
previewFormattedBodyPreview?: unknown;
23972408
reply?: { eventId?: unknown };
23982409
};
23992410
expect(artifacts.driverEventId).toBe("$partial-stream-trigger");
2411+
expect(artifacts.previewBodyPreview).toBe("b".repeat(199));
24002412
expect(artifacts.previewEventId).toBe("$partial-preview");
2413+
expect(artifacts.previewFormattedBodyPreview).toBe("f".repeat(199));
24012414
expect(artifacts.reply?.eventId).toBe("$partial-final");
24022415

24032416
expectSentTextMessage(sendTextMessage, {
@@ -2767,7 +2780,7 @@ describe("matrix live qa scenarios", () => {
27672780
const updateEvent = matrixQaMessageEvent({
27682781
kind: "notice",
27692782
eventId: "$tool-progress-timeout-update",
2770-
body: "Working...\nstill deciding",
2783+
body: `${"x".repeat(236)}😀tail`,
27712784
relatesTo: {
27722785
relType: "m.replace",
27732786
eventId: previewEvent.eventId,
@@ -2794,8 +2807,16 @@ describe("matrix live qa scenarios", () => {
27942807

27952808
const scenario = requireMatrixQaScenario("matrix-room-tool-progress-preview");
27962809

2797-
await expect(runMatrixQaScenario(scenario, context)).rejects.toThrow(
2798-
/observed preview candidates:[\s\S]*\$tool-progress-timeout-update/,
2810+
const error = await runMatrixQaScenario(scenario, context).then(
2811+
() => undefined,
2812+
(candidate: unknown) => candidate,
2813+
);
2814+
expect(error).toBeInstanceOf(Error);
2815+
const candidateLine = (error as Error).message
2816+
.split("\n")
2817+
.find((line) => line.startsWith("$tool-progress-timeout-update"));
2818+
expect(candidateLine).toBe(
2819+
`$tool-progress-timeout-update kind=notice relation=m.replace:$tool-progress-timeout-preview body=${JSON.stringify(`${"x".repeat(236)}...`)}`,
27992820
);
28002821
});
28012822

0 commit comments

Comments
 (0)