fix: keep subagent truncation within max length (#100013)

This commit is contained in:
qingminlong
2026-07-04 18:48:23 -07:00
committed by GitHub
parent b550c829b2
commit 9b78ec8d6f
3 changed files with 30 additions and 13 deletions
+2 -4
View File
@@ -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.");
});
+19 -6
View File
@@ -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]/,
+9 -3
View File
@@ -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 = {