fix(skills): bound evaluator text without splitting surrogate pairs (#117673)

This commit is contained in:
zengLingbiao
2026-08-02 09:37:58 +08:00
committed by GitHub
parent ff770f9a48
commit 34692256be
2 changed files with 60 additions and 3 deletions
@@ -759,6 +759,61 @@ describe("Skill Workshop proposal evaluation", () => {
});
});
it("does not split surrogate pairs when bounding evaluator text", async () => {
const workspaceDir = await tempDirs.make("openclaw-skill-evaluation-surrogate-");
const proposal = await proposeCreateSkill({
workspaceDir,
agentId: "main",
name: "Surrogate Bounds",
description: "Bound evaluator text without splitting surrogates",
content: "# Surrogate Bounds\n",
});
hookMocks.evaluate.mockResolvedValue([
{
evaluatorId: "emoji-bounds",
pluginId: "evaluation-tests",
status: "completed",
result: {
summary: `${"s".repeat(7_999)}🙂`,
decisionReason: `${"r".repeat(1_999)}🙂`,
metrics: { note: `${"m".repeat(3_999)}🙂` },
},
},
{
evaluatorId: "emoji-error",
pluginId: "evaluation-tests",
status: "error",
error: `${"e".repeat(1_999)}🙂`,
},
]);
const evaluated = await evaluateSkillProposal({
workspaceDir,
agentId: "main",
proposalId: proposal.record.id,
expectedRevisionHash: proposal.revisionHash,
});
expect(evaluated.evaluation.outcomes).toEqual([
{
evaluatorId: "emoji-bounds",
pluginId: "evaluation-tests",
status: "completed",
result: {
summary: "s".repeat(7_999),
decisionReason: "r".repeat(1_999),
metrics: { note: "m".repeat(3_999) },
},
},
{
evaluatorId: "emoji-error",
pluginId: "evaluation-tests",
status: "error",
error: "e".repeat(1_999),
},
]);
});
it("rejects evaluator results that exceed the aggregate persistence budget", async () => {
const workspaceDir = await tempDirs.make("openclaw-skill-evaluation-size-budget-");
const proposal = await proposeCreateSkill({
+5 -3
View File
@@ -1,5 +1,6 @@
import { randomUUID } from "node:crypto";
import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce";
import { truncateUtf16Safe } from "@openclaw/normalization-core/utf16-slice";
import type {
PluginHookSkillEvaluationFinding,
PluginHookSkillProposalEvaluateResult,
@@ -346,18 +347,19 @@ function normalizeMetrics(
) {
return undefined;
}
normalized[key] = typeof value === "string" ? value.slice(0, 4_000) : value;
normalized[key] = typeof value === "string" ? truncateUtf16Safe(value, 4_000) : value;
}
return normalized;
}
function boundedRequired(value: string, maxLength: number, fallback: string): string {
const normalized = normalizeOptionalString(value) ?? fallback;
return normalized.slice(0, maxLength);
return truncateUtf16Safe(normalized, maxLength);
}
function boundedOptional(value: string | undefined, maxLength: number): string | undefined {
return normalizeOptionalString(value)?.slice(0, maxLength);
const normalized = normalizeOptionalString(value);
return normalized === undefined ? undefined : truncateUtf16Safe(normalized, maxLength);
}
function storeOptions(env?: NodeJS.ProcessEnv) {