mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-21 10:16:44 +00:00
fix: keep subagent truncation within max length (#100013)
This commit is contained in:
@@ -74,10 +74,8 @@ describe("buildSubagentList", () => {
|
||||
recentMinutes: 30,
|
||||
taskMaxChars: 110,
|
||||
});
|
||||
expect(list.active[0]?.line).toContain(
|
||||
"This is a deliberately long task description used to verify that subagent list output keeps the full task text",
|
||||
);
|
||||
expect(list.active[0]?.line).toContain("...");
|
||||
expect(list.active[0]?.task).toHaveLength(110);
|
||||
expect(list.active[0]?.task).toMatch(/\.\.\.$/);
|
||||
expect(list.active[0]?.line).not.toContain("after a short hard cutoff.");
|
||||
});
|
||||
|
||||
|
||||
@@ -37,21 +37,34 @@ describe("shared/subagents-format", () => {
|
||||
|
||||
it("truncates lines only when needed", () => {
|
||||
expect(truncateLine("short", 10)).toBe("short");
|
||||
expect(truncateLine("trim me ", 7)).toBe("trim me...");
|
||||
expect(truncateLine("abc ", 5)).toBe("abc");
|
||||
expect(truncateLine("trim me ", 7)).toBe("trim me");
|
||||
expect(truncateLine("abcdefghij", 5)).toBe("ab...");
|
||||
});
|
||||
|
||||
it("keeps truncated lines within the requested max length", () => {
|
||||
for (const maxLength of [0, 1, 2, 3, 4, 5, 7, 12]) {
|
||||
const result = truncateLine("abcdefghij", maxLength);
|
||||
expect(result.length).toBeLessThanOrEqual(maxLength);
|
||||
}
|
||||
expect(truncateLine("abcdefghij", 0)).toBe("");
|
||||
expect(truncateLine("abcdefghij", 1)).toBe(".");
|
||||
expect(truncateLine("abcdefghij", 2)).toBe("..");
|
||||
expect(truncateLine("abcdefghij", 3)).toBe("...");
|
||||
});
|
||||
|
||||
it("truncates without breaking surrogate pairs", () => {
|
||||
// Emoji at the cut point: the surrogate pair must not be split.
|
||||
expect(truncateLine("AB🤖CD", 3)).toBe("AB...");
|
||||
expect(truncateLine("AB🤖CDEF", 6)).toBe("AB...");
|
||||
// Cut point in the middle of a 3-emoji string.
|
||||
expect(truncateLine("🤖🤖🤖", 5)).toBe("🤖🤖...");
|
||||
expect(truncateLine("🤖🤖🤖", 5)).toBe("🤖...");
|
||||
// CJK Extension B (surrogate pair) at boundary: character stays intact.
|
||||
expect(truncateLine("AB𠮷CD", 5)).toBe("AB𠮷C...");
|
||||
expect(truncateLine("AB𠮷CDEF", 7)).toBe("AB𠮷...");
|
||||
// No broken surrogates in output.
|
||||
for (const result of [
|
||||
truncateLine("AB🤖CD", 3),
|
||||
truncateLine("AB🤖CDEF", 6),
|
||||
truncateLine("🤖🤖🤖", 5),
|
||||
truncateLine("AB𠮷CD", 5),
|
||||
truncateLine("AB𠮷CDEF", 7),
|
||||
]) {
|
||||
expect(result).not.toMatch(
|
||||
/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/,
|
||||
|
||||
@@ -27,10 +27,16 @@ export function formatTokenShort(value?: number) {
|
||||
|
||||
/** Truncates a single-line display string without preserving trailing whitespace. */
|
||||
export function truncateLine(value: string, maxLength: number) {
|
||||
if (value.length <= maxLength) {
|
||||
return value;
|
||||
const limit = Math.max(0, Math.floor(maxLength));
|
||||
const trimmed = value.trimEnd();
|
||||
if (trimmed.length <= limit) {
|
||||
return trimmed;
|
||||
}
|
||||
return `${truncateUtf16Safe(value, maxLength).trimEnd()}...`;
|
||||
const marker = "...";
|
||||
if (limit <= marker.length) {
|
||||
return marker.slice(0, limit);
|
||||
}
|
||||
return `${truncateUtf16Safe(trimmed, limit - marker.length).trimEnd()}${marker}`;
|
||||
}
|
||||
|
||||
type TokenUsageLike = {
|
||||
|
||||
Reference in New Issue
Block a user