From f0ce854be28d3fa788c795727d85b0d98d54e79c Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 25 Jul 2026 17:41:47 +0800 Subject: [PATCH] refactor(cron): share diagnostic summary normalization (#113595) --- src/cron/run-diagnostics-normalize.ts | 4 ++-- src/cron/run-diagnostics.test.ts | 15 +++++++++++++++ src/cron/run-diagnostics.ts | 20 +++++--------------- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/cron/run-diagnostics-normalize.ts b/src/cron/run-diagnostics-normalize.ts index 64af1bbf5ff9..61b20a2cf7c6 100644 --- a/src/cron/run-diagnostics-normalize.ts +++ b/src/cron/run-diagnostics-normalize.ts @@ -113,7 +113,7 @@ function normalizeDiagnosticMessage( return { message: `${truncateUtf16Safe(redacted, MAX_ENTRY_CHARS - 1)}…`, truncated: true }; } -function trimSummary(value: string | undefined): string | undefined { +export function normalizeCronRunDiagnosticSummary(value: string | undefined): string | undefined { const normalized = normalizeOptionalString(value); if (!normalized) { return undefined; @@ -167,7 +167,7 @@ export function normalizeCronRunDiagnostics( entries.shift(); } } - const summary = trimSummary( + const summary = normalizeCronRunDiagnosticSummary( typeof record.summary === "string" ? redactText(record.summary) : undefined, ); if (entries.length === 0 && !summary) { diff --git a/src/cron/run-diagnostics.test.ts b/src/cron/run-diagnostics.test.ts index 6853659ec40f..88722087ac25 100644 --- a/src/cron/run-diagnostics.test.ts +++ b/src/cron/run-diagnostics.test.ts @@ -90,6 +90,21 @@ describe("cron run diagnostics", () => { expect(summarizeCronRunDiagnostics(undefined)).toBeUndefined(); }); + it("bounds fallback summaries at valid UTF-16 boundaries", () => { + expect( + summarizeCronRunDiagnostics({ + entries: [ + { + ts: 1, + source: "exec", + severity: "error", + message: `${"s".repeat(1_998)}😀tail`, + }, + ], + }), + ).toBe(`${"s".repeat(1_998)}…`); + }); + it("creates diagnostics from errors and prefers the latest error summary", () => { const first = createCronRunDiagnosticsFromError("cron-preflight", "first failure", { nowMs: () => 100, diff --git a/src/cron/run-diagnostics.ts b/src/cron/run-diagnostics.ts index 0e403a2e786a..ff2e13f8ac8e 100644 --- a/src/cron/run-diagnostics.ts +++ b/src/cron/run-diagnostics.ts @@ -1,6 +1,5 @@ /** 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"; @@ -8,6 +7,7 @@ import { redactSensitiveText } from "../logging/redact.js"; import { formatUnknownError, isRecord, + normalizeCronRunDiagnosticSummary, normalizeCronRunDiagnostics as normalizeCronRunDiagnosticsValue, normalizeExitCode, normalizeToolName, @@ -20,7 +20,6 @@ import type { CronRunDiagnosticSource, } from "./types.js"; -const MAX_SUMMARY_CHARS = 2_000; const EXEC_DIAGNOSTIC_TAIL_CHARS = 2_000; const WEB_SEARCH_TOOL_NAME = "web_search"; @@ -37,17 +36,6 @@ export function toolsAllowRequestsWebSearch(toolsAllow?: string[]): boolean { ); } -function trimSummary(value: string | undefined): string | undefined { - const normalized = normalizeOptionalString(value); - if (!normalized) { - return undefined; - } - if (normalized.length <= MAX_SUMMARY_CHARS) { - return normalized; - } - return `${truncateUtf16Safe(normalized, MAX_SUMMARY_CHARS - 1)}…`; -} - /** Returns the operator-facing summary for persisted cron diagnostics. */ export function summarizeCronRunDiagnostics( diagnostics: CronRunDiagnostics | undefined, @@ -55,7 +43,7 @@ export function summarizeCronRunDiagnostics( if (!diagnostics) { return undefined; } - return trimSummary(diagnostics.summary ?? diagnostics.entries[0]?.message); + return normalizeCronRunDiagnosticSummary(diagnostics.summary ?? diagnostics.entries[0]?.message); } /** Normalizes untrusted cron diagnostic payloads into bounded, redacted entries. */ @@ -84,7 +72,9 @@ export function mergeCronRunDiagnostics( normalized.entries.findLast((entry) => entry.severity === "error") ?? normalized.entries.findLast((entry) => entry.severity === "warn") ?? normalized.entries.findLast((entry) => entry.severity === "info"); - const summary = trimSummary(normalized.summary ?? entryCandidate?.message); + const summary = normalizeCronRunDiagnosticSummary( + normalized.summary ?? entryCandidate?.message, + ); if (summary) { const severity = entryCandidate?.severity === "error" ? 2 : entryCandidate?.severity === "warn" ? 1 : 0;