diff --git a/src/auto-reply/reply/commands-acp.test.ts b/src/auto-reply/reply/commands-acp.test.ts index cb6873a4ee76..e1ac574fcae4 100644 --- a/src/auto-reply/reply/commands-acp.test.ts +++ b/src/auto-reply/reply/commands-acp.test.ts @@ -1621,6 +1621,28 @@ describe("/acp command", () => { expect(result?.reply?.text).toContain("Applied steering."); }); + it("keeps bounded ACP steer output UTF-16 safe", async () => { + const prefix = "a".repeat(799); + hoisted.callGatewayMock.mockImplementation(async (request: { method?: string }) => { + if (request.method === "sessions.resolve") { + return { key: defaultAcpSessionKey }; + } + return { ok: true }; + }); + hoisted.readAcpSessionEntryMock.mockReturnValue(createAcpSessionEntry()); + hoisted.runTurnMock.mockImplementation(async function* () { + yield { type: "text_delta", text: `${prefix}😀tail` }; + yield { type: "done" }; + }); + + const result = await runDiscordAcpCommand( + `/acp steer --session ${defaultAcpSessionKey} tighten logging`, + ); + + expect(result?.reply?.text).toContain(`\n${prefix}…`); + expect(result?.reply?.text).not.toContain("😀"); + }); + it("resolves bound Telegram topic ACP sessions for /acp steer without explicit target", async () => { hoisted.sessionBindingResolveByConversationMock.mockImplementation( (ref: { channel?: string; accountId?: string; conversationId?: string }) => diff --git a/src/auto-reply/reply/commands-acp/lifecycle.ts b/src/auto-reply/reply/commands-acp/lifecycle.ts index 979ad78f791d..5e3ac82ef31f 100644 --- a/src/auto-reply/reply/commands-acp/lifecycle.ts +++ b/src/auto-reply/reply/commands-acp/lifecycle.ts @@ -5,6 +5,7 @@ import { resolveAcpThreadSessionDetailLines, } from "@openclaw/acp-core/runtime/session-identifiers"; import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; +import { truncateUtf16Safe } from "@openclaw/normalization-core/utf16-slice"; import { getAcpSessionManager } from "../../../acp/control-plane/manager.js"; import { resolveAcpSessionResolutionError } from "../../../acp/control-plane/manager.utils.js"; import { @@ -787,7 +788,7 @@ async function runAcpSteer(params: { if (event.text) { output += event.text; if (output.length > ACP_STEER_OUTPUT_LIMIT) { - output = `${output.slice(0, ACP_STEER_OUTPUT_LIMIT)}…`; + output = `${truncateUtf16Safe(output, ACP_STEER_OUTPUT_LIMIT)}…`; } } },