diff --git a/packages/provider-core/src/server/subagentProjection.test.ts b/packages/provider-core/src/server/subagentProjection.test.ts new file mode 100644 index 0000000000000..767764db0cae5 --- /dev/null +++ b/packages/provider-core/src/server/subagentProjection.test.ts @@ -0,0 +1,13 @@ +import { describe, expect, it } from "vite-plus/test"; + +import { subagentThreadTitle } from "./subagentProjection.ts"; + +describe("subagentThreadTitle", () => { + it("clips a long prompt without splitting a surrogate pair", () => { + const title = (prompt: string) => + subagentThreadTitle({ parentTitle: "Parent", prompt, ordinal: 1 }); + + expect(title(`${"a".repeat(68)}😀 and more work.`)).toBe(`${"a".repeat(68)}...`); + expect(title(`${"a".repeat(67)}😀 and more work.`)).toBe(`${"a".repeat(67)}😀...`); + }); +}); diff --git a/packages/provider-core/src/server/subagentProjection.ts b/packages/provider-core/src/server/subagentProjection.ts index 3775818892502..dbd929192e04d 100644 --- a/packages/provider-core/src/server/subagentProjection.ts +++ b/packages/provider-core/src/server/subagentProjection.ts @@ -39,8 +39,14 @@ export function subagentThreadTitle(input: { if (detail === undefined) { return `${input.parentTitle} subagent ${input.ordinal}`; } - const clipped = detail.length > 72 ? `${detail.slice(0, 69)}...` : detail; - return clipped; + if (detail.length <= 72) return detail; + let end = 69; + const previous = detail.charCodeAt(end - 1); + const next = detail.charCodeAt(end); + if (previous >= 0xd800 && previous <= 0xdbff && next >= 0xdc00 && next <= 0xdfff) { + end -= 1; + } + return `${detail.slice(0, end)}...`; } export function makeSubagentChildThread(input: {