fix(release): preserve emoji in cross-OS response diagnostics (#108193)

* fix(release): keep cross-OS responses valid Unicode

* style(release): format cross-OS boundary test

* fix(release): resolve UTF-16 helper in direct Node runs

* fix(release): keep cross-OS summaries UTF-16 safe

Co-authored-by: Leon-SK668 <0668001470@xydigit.com>

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
Leon-SK668
2026-07-17 08:43:56 +08:00
committed by GitHub
parent dde6af2199
commit 56d0721eb7
3 changed files with 40 additions and 2 deletions
@@ -1,5 +1,6 @@
import { randomUUID } from "node:crypto";
import { join } from "node:path";
import { truncateUtf16Safe } from "../../../packages/normalization-core/src/utf16-slice.ts";
import type { GatewayHandle, LaneState } from "./config.ts";
import {
CROSS_OS_DASHBOARD_FETCH_TIMEOUT_MS,
@@ -157,7 +158,7 @@ export async function readBoundedCrossOsResponseText(
text += decoder.decode(value, { stream: true });
if (text.length > maxChars) {
text = text.slice(0, maxChars);
text = truncateUtf16Safe(text, maxChars);
truncated = true;
break;
}
@@ -1,5 +1,6 @@
import { existsSync } from "node:fs";
import { join } from "node:path";
import { truncateUtf16Safe } from "../../../packages/normalization-core/src/utf16-slice.ts";
export function resolveCommandPath(command: string) {
const pathValue = process.env.PATH ?? "";
@@ -28,7 +29,7 @@ export function trimForSummary(value: string) {
if (trimmed.length <= 600) {
return trimmed;
}
return `${trimmed.slice(0, 600)}...`;
return `${truncateUtf16Safe(trimmed, 600)}...`;
}
export function formatError(error: unknown) {
@@ -88,6 +88,7 @@ import {
resolveRunnerMatrix,
resolveStaticFileContentType,
startStaticFileServer,
trimForSummary,
shouldExerciseManagedGatewayLifecycleAfterInstall,
shouldRunPackagedUpgradeStatusProbe,
shouldRunWindowsInstalledBrowserOverrideImportSmoke,
@@ -210,6 +211,41 @@ describe("scripts/openclaw-cross-os-release-checks", () => {
expect(CROSS_OS_FETCH_BODY_MAX_CHARS).toBeGreaterThan(1024);
});
it.each([
{
caseName: "drops a split surrogate pair",
responseBody: `abc\u{1f600}tail`,
expectedText: "abc\n[truncated]",
},
{
caseName: "preserves a complete surrogate pair",
responseBody: `ab\u{1f600}tail`,
expectedText: `ab\u{1f600}\n[truncated]`,
},
])(
"keeps cross-OS response truncation UTF-16 safe: $caseName",
async ({ responseBody, expectedText }) => {
const response = new Response(responseBody);
await expect(readBoundedCrossOsResponseText(response, 4)).resolves.toBe(expectedText);
},
);
it.each([
{
caseName: "drops a split surrogate pair",
input: `${"x".repeat(599)}\u{1f600}tail`,
expected: `${"x".repeat(599)}...`,
},
{
caseName: "preserves a complete surrogate pair",
input: `${"x".repeat(598)}\u{1f600}tail`,
expected: `${"x".repeat(598)}\u{1f600}...`,
},
])("keeps cross-OS summaries UTF-16 safe: $caseName", ({ input, expected }) => {
expect(trimForSummary(input)).toBe(expected);
});
it("keeps cross-OS fetch timeouts active while reading response bodies", async () => {
let canceled = false;
const abortController = new AbortController();