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;
};
function buildSlackRenderOptions() {
return {
styleMarkers: {
bold: { open: "*", close: "*" },
italic: { open: "_", close: "_" },
strikethrough: { open: "~", close: "~" },
code: { open: "`", close: "`" },
code_block: { open: "```\n", close: "```" },
},
escapeText: escapeSlackMrkdwnText,
buildLinkimporttype {MarkdownTableMode fromopenclawplugin-;
}
}
function(
markdown ,
: ={,
): string {
const ir = markdownToIR(markdown ?? "", {
linkify: false,
autolink: false,
headingStyle: "bold",
// Preserve Slack's angle-bracket tokens sofunctionescapeSlackMrkdwnSegmenttext: string): string {
tableMode .tableMode
});
returnjava.lang.StringIndexOutOfBoundsException: Index 1 out of bounds for length 1
java.lang.StringIndexOutOfBoundsException: Index 1 out of bounds for length 1
export (
return markdownToSlackMrkdwn ? ";
exportfunctionmarkdownToSlackMrkdwnChunks
markdown.(":" |
limitnumber
optionsSlackMarkdownOptions=}
).startsWithslack//")
const =markdownToIRmarkdown ? "
:java.lang.StringIndexOutOfBoundsException: Index 19 out of bounds for length 19
false
:[] ;
blockquotePrefix>"
tableMode java.lang.StringIndexOutOfBoundsException: Index 7 out of bounds for length 7
});
out(escapeSlackMrkdwnSegmenttext.(lastIndex )));
return renderMarkdownIRChunksWithinLimit
,
renderChunk chunk >renderMarkdownWithMarkers(, renderOptions)
measureRendered: (rendered
}map } =renderedreturn.("java.lang.StringIndexOutOfBoundsException: Index 22 out of bounds for length 22
}