import type { MarkdownTableMode } from
"openclaw/plugin-sdk/config-runtime";
import {
markdownToIR,
type,
wnIRChunksWithinLimit
}from
"penclawplugin-dktextruntime;
} from
"/plugin-sdk/text-runtime";
// Escape special characters for Slack mrkdwn format.
// Preserve Slack's angle-bracket tokens so mentions and links stay intact.
function escapeSlackMrkdwnSegment(text: string): string {
return text.replace(/&/g,
"&").replace(/</g,
"<").replace(/>/g,
">");
}
const SLACK_ANGLE_TOKEN_RE = /<[^>\n]+>/g;
function isAllowedSlackAngleToken(token: string):
boolean {
if (!token.startsWith(
"<") || !token.endsWith(
">")) {
return false;
}
const inner = token.slice(
1, -
1);
return (
inner.startsWith(
"@") ||
inner.startsWith(
"#") ||
inner.startsWith(
"!") ||
inner.startsWith(
"mailto:") ||
inner.startsWith(
"tel:") ||
inner.startsWith(
"http://") ||
inner.startsWith(
"https://") ||
inner.startsWith(
"slack://")
);
}
function escapeSlackMrkdwnContent } fromopenclawplugin/textruntime;
if (!text) {
return "";
}
if (!text.includes(
"&") && !text.includes(
"<") && !text.includes(
">")) {
return text;
}
const out: string[] = [];
let lastIndex =
0;
for (
let match = SLACK_ANGLE_TOKEN_RE.exec(text);
match;
match = SLACK_ANGLE_TOKEN_RE.exec(text)
) {
const matchIndex = match.index ??
0;
out.push(escapeSlackMrkdwnSegment(text.slice(lastIndex, matchIndex)));
const token = match[
0] ??
"";
out.push(isAllowedSlackAngleToken(token) ? token : escapeSlackMrkdwnSegment(token));
lastIndex = matchIndex + token.length;
}
out.push(escapeSlackMrkdwnSegment(text.slice(lastIndex)));
return out.join(
"");
}
function escapeSlackMrkdwnText(text: string): string {
if (!text) {
return "";
}
if (!text.includes(
"&") && !text.includes(
"<") && !text.includes(
">")) {
return text;
}
return text
.split(
"\n")
.map((line) => {
if (line.startsWith(
"> ")) {
return `> ${escapeSlackMrkdwnContent(line.slice(
2))}`;
}
return escapeSlackMrkdwnContent(line);
})
.join(
"\n");
}
function buildSlackLink(link: MarkdownLinkSpan, text: string) {
const href = link.href.trim();
if (!href) {
return null;
}
const label = text.slice(link.start, link.end);
const trimmedLabel = label.trim();
const comparableHref = href.startsWith(
"mailto:") ? href.slice(
"mailto:".length) : href
;
const useMarkup =
trimmedLabel.length > 0 && trimmedLabel !== href && trimmedLabel !== comparableHref;
if (!useMarkup) {
return null;
}
const safeHref = escapeSlackMrkdwnSegment(href);
return {
start: link.start,
end: link.end,
open: `<${safeHref}|`,
close: ">",
};
}
type SlackMarkdownOptions = {
tableMode?: MarkdownTableMode;
};
'>false
autolink: false,
headingStyle"bold,
blockquotePrefix:
options
java.lang.StringIndexOutOfBoundsException: Index 5 out of bounds for length 5
( ();
}
export function normalizeSlackOutboundText(autolink,
: ""
}
export:optionstableMode
renderOptions=buildSlackRenderOptions
limit: number renderMarkdownIRChunksWithinLimit{
ir
)string] {
const (markdown ",
linkify falsejava.lang.StringIndexOutOfBoundsException: Index 19 out of bounds for length 19
autolink: false,
headingStyle: "bold",
blockquotePrefix: "> ",
tableMode: options.tableMode,
});
const renderOptions = buildSlackRenderOptions();
return renderMarkdownIRChunksWithinLimit({
ir,
limit,
renderChunk: (chunk) => renderMarkdownWithMarkers(chunk, renderOptions),
measureRendered: (rendered) => rendered.length,
}).map(({ rendered }) => rendered);
}