Skip to content

Commit d30fff1

Browse files
authored
fix: preserve Mattermost mention indentation
1 parent 64f015a commit d30fff1

File tree

3 files changed

+37
-12
lines changed

3 files changed

+37
-12
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ Docs: https://docs.openclaw.ai
5858
- Sandbox/subagents: pass the real configured workspace through `sessions_spawn` inheritance when a parent agent runs in a copied-workspace sandbox, so child `/agent` mounts point at the configured workspace instead of the parent sandbox copy. (#40757) Thanks @dsantoreis.
5959
- Mattermost/plugin send actions: normalize direct `replyTo` fallback handling so threaded plugin sends trim blank IDs and reuse the correct reply target again. (#41176) Thanks @hnykda.
6060
- MS Teams/allowlist resolution: use the General channel conversation ID as the resolved team key (with Graph GUID fallback) so Bot Framework runtime `channelData.team.id` matching works for team and team/channel allowlist entries. (#41838) Thanks @BradGroux.
61+
- Mattermost/Markdown formatting: preserve first-line indentation when stripping bot mentions so nested list items and indented code blocks keep their structure, and render Mattermost tables natively by default instead of fenced-code fallback. (#18655) thanks @echo931.
6162

6263
## 2026.3.8
6364

extensions/mattermost/src/mattermost/monitor-helpers.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,22 @@ describe("normalizeMention", () => {
6161
expect(result).toContain(" - deep");
6262
});
6363

64+
it("preserves first-line indentation for nested list items", () => {
65+
const input = "@echobot\n - nested\n - deep";
66+
const result = normalizeMention(input, "echobot");
67+
expect(result).toBe(" - nested\n - deep");
68+
});
69+
6470
it("preserves indented code blocks", () => {
6571
const input = "@echobot\ntext\n code line 1\n code line 2";
6672
const result = normalizeMention(input, "echobot");
6773
expect(result).toContain(" code line 1");
6874
expect(result).toContain(" code line 2");
6975
});
76+
77+
it("preserves first-line indentation for indented code blocks", () => {
78+
const input = "@echobot\n code line 1\n code line 2";
79+
const result = normalizeMention(input, "echobot");
80+
expect(result).toBe(" code line 1\n code line 2");
81+
});
7082
});

extensions/mattermost/src/mattermost/monitor-helpers.ts

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -80,16 +80,28 @@ export function normalizeMention(text: string, mention: string | undefined): str
8080
return text.trim();
8181
}
8282
const escaped = mention.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
83-
const re = new RegExp(`@${escaped}\\b`, "gi");
84-
// Replace the mention itself, then clean up without destroying Markdown structure.
85-
// 1. Remove the mention (replace with empty to avoid injecting spaces into indentation)
86-
// 2. Collapse only runs of multiple spaces/tabs within a line (preserving leading indent)
87-
// 3. Trim blank lines left by mention removal
88-
return text
89-
.replace(re, "")
90-
.split("\n")
91-
.map((line) => line.replace(/(\S) {2,}/g, "$1 "))
92-
.join("\n")
93-
.replace(/^\s*\n/, "")
94-
.trim();
83+
const hasMentionRe = new RegExp(`@${escaped}\\b`, "i");
84+
const leadingMentionRe = new RegExp(`^([\\t ]*)@${escaped}\\b[\\t ]*`, "i");
85+
const trailingMentionRe = new RegExp(`[\\t ]*@${escaped}\\b[\\t ]*$`, "i");
86+
const normalizedLines = text.split("\n").map((line) => {
87+
const hadMention = hasMentionRe.test(line);
88+
const normalizedLine = line
89+
.replace(leadingMentionRe, "$1")
90+
.replace(trailingMentionRe, "")
91+
.replace(new RegExp(`@${escaped}\\b`, "gi"), "")
92+
.replace(/(\S)[ \t]{2,}/g, "$1 ");
93+
return {
94+
text: normalizedLine,
95+
mentionOnlyBlank: hadMention && normalizedLine.trim() === "",
96+
};
97+
});
98+
99+
while (normalizedLines[0]?.mentionOnlyBlank) {
100+
normalizedLines.shift();
101+
}
102+
while (normalizedLines.at(-1)?.text.trim() === "") {
103+
normalizedLines.pop();
104+
}
105+
106+
return normalizedLines.map((line) => line.text).join("\n");
95107
}

0 commit comments

Comments
 (0)