fix(cron): keep bounded diagnostics UTF-16 safe (#102624)

* fix(cron): use truncateUtf16Safe for diagnostic entry and summary truncation

* test(cron): cover UTF-16 diagnostic boundaries

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
lzyyzznl 2026-07-09 20:05:51 +08:00 committed by GitHub
parent 5b2c316950
commit f786efddcd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 26 additions and 2 deletions

View file

@ -31,6 +31,29 @@ describe("cron run diagnostics", () => {
expect(diagnostics?.summary).toHaveLength(2_000);
});
it("keeps bounded diagnostic text valid at UTF-16 boundaries", () => {
const diagnostics = normalizeCronRunDiagnostics({
summary: `${"s".repeat(1_998)}😀tail`,
entries: [
{
ts: 1,
source: "exec",
severity: "error",
message: `${"m".repeat(998)}😀tail`,
},
],
});
expect(diagnostics?.summary).toBe(`${"s".repeat(1_998)}`);
expect(diagnostics?.entries[0]).toEqual({
ts: 1,
source: "exec",
severity: "error",
message: `${"m".repeat(998)}`,
truncated: true,
});
});
it("preserves later terminal diagnostics when capping entries", () => {
const diagnostics = normalizeCronRunDiagnostics({
entries: [

View file

@ -1,5 +1,6 @@
/** Builds bounded, redacted diagnostics for cron run logs and UI surfaces. */
import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce";
import { truncateUtf16Safe } from "@openclaw/normalization-core/utf16-slice";
import { isToolAllowedByPolicyName } from "../agents/tool-policy-match.js";
import { normalizeToolName as normalizePolicyToolName } from "../agents/tool-policy.js";
import { getReplyPayloadMetadata } from "../auto-reply/reply-payload.js";
@ -101,7 +102,7 @@ function normalizeDiagnosticMessage(value: unknown): { message?: string; truncat
if (redacted.length <= MAX_ENTRY_CHARS) {
return { message: redacted };
}
return { message: `${redacted.slice(0, MAX_ENTRY_CHARS - 1)}`, truncated: true };
return { message: `${truncateUtf16Safe(redacted, MAX_ENTRY_CHARS - 1)}`, truncated: true };
}
function trimSummary(value: string | undefined): string | undefined {
@ -112,7 +113,7 @@ function trimSummary(value: string | undefined): string | undefined {
if (normalized.length <= MAX_SUMMARY_CHARS) {
return normalized;
}
return `${normalized.slice(0, MAX_SUMMARY_CHARS - 1)}`;
return `${truncateUtf16Safe(normalized, MAX_SUMMARY_CHARS - 1)}`;
}
/** Returns the operator-facing summary for persisted cron diagnostics. */