Files
openclaw/extensions/slack/src/truncate.ts
MatthewSynthia 7df5834511 fix(slack): cap chat.update edit text at the 4000-char limit, not the 8000 send limit (#115027)
* fix(slack): cap chat.update edit text at the 4000-char limit, not the 8000 send limit

updateMessageSlack truncated the edit text to SLACK_TEXT_LIMIT (8000), but Slack chat.update
rejects text longer than 4000 characters with msg_too_long (documented in limits.ts). Every
other edit path (actions.ts, edit-text.ts, message-action-dispatch.ts, preview-finalize.ts)
uses SLACK_EDIT_TEXT_LIMIT (4000); updateMessageSlack was the lone outlier, so a long
question-delivery status edit failed instead of landing. Use the edit limit.

* fix(slack): enforce edit text byte limits

Co-authored-by: MatthewSynthia <matthewsynthia@users.noreply.github.com>

* fix(slack): preserve prepared edit text within limits

Co-authored-by: MatthewSynthia <matthewsynthia@users.noreply.github.com>

---------

Co-authored-by: MatthewSynthia <matthewsynthia@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-28 12:41:37 -04:00

47 lines
1.6 KiB
TypeScript

// Slack plugin module implements truncate behavior.
import { sliceUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
export function truncateSlackText(value: string, max: number): string {
const trimmed = value.trim();
if (trimmed.length <= max) {
return trimmed;
}
// Slice on a code-point boundary so a surrogate pair (emoji / astral char)
// straddling the limit is dropped whole, instead of leaving a lone surrogate
// half that serializes to an invalid `\uD83D` in the Slack payload.
if (max <= 1) {
return sliceUtf16Safe(trimmed, 0, max);
}
return `${sliceUtf16Safe(trimmed, 0, max - 1)}…`;
}
export function countSlackTextUtf8Bytes(value: string): number {
return Buffer.byteLength(value, "utf8");
}
/** Truncate Slack text without splitting a code point or exceeding a UTF-8 byte limit. */
export function truncateSlackTextByUtf8Bytes(value: string, maxBytes: number): string {
const trimmed = value.trim();
if (maxBytes <= 0) {
return "";
}
if (countSlackTextUtf8Bytes(trimmed) <= maxBytes) {
return trimmed;
}
const suffix = "…";
const suffixBytes = countSlackTextUtf8Bytes(suffix);
const prefixBudget = maxBytes >= suffixBytes ? maxBytes - suffixBytes : maxBytes;
let prefix = "";
let prefixBytes = 0;
for (const character of trimmed) {
const characterBytes = countSlackTextUtf8Bytes(character);
if (prefixBytes + characterBytes > prefixBudget) {
break;
}
prefix += character;
prefixBytes += characterBytes;
}
return maxBytes >= suffixBytes ? `${prefix}${suffix}` : prefix;
}