mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
fix(slack): Fix bad escaping in slack tool commentary (#119373)
* fix(slack): preserve Markdown in progress drafts * fix(slack): retain escaping for tool progress * fix(slack): scope progress line escaping * fix(slack): preserve progress formatting by source * test(slack): assert plan refresh rendering * test(slack): keep progress regression coverage scoped * update tests --------- Co-authored-by: Sarah Fortune <sarah.fortune@gmail.com>
This commit is contained in:
@@ -322,7 +322,7 @@ export function createSlackProgressRuntime(runtimeParams: {
|
||||
mode: slackStreaming.mode,
|
||||
active: progressDraftActive,
|
||||
seed: progressSeed,
|
||||
formatLine: escapeSlackMrkdwn,
|
||||
formatLine: formatSlackProgressDraftLine,
|
||||
reasoningLinePrefix: "🧠 ",
|
||||
commentaryLinePrefix: "💬 ",
|
||||
reasoningGate: previewToolProgressEnabled,
|
||||
@@ -442,7 +442,7 @@ export function createSlackProgressRuntime(runtimeParams: {
|
||||
entry: account.config,
|
||||
lines: [...progressDraft.getSnapshot().lines],
|
||||
seed: progressSeed,
|
||||
formatLine: escapeSlackMrkdwn,
|
||||
formatLine: formatSlackProgressDraftLine,
|
||||
narration: explanation,
|
||||
plan: steps,
|
||||
});
|
||||
@@ -656,3 +656,7 @@ export function createSlackProgressRuntime(runtimeParams: {
|
||||
shouldYieldDraftProgress: () => shouldYieldDraftProgress(),
|
||||
};
|
||||
}
|
||||
|
||||
function formatSlackProgressDraftLine(line: string): string {
|
||||
return /^(?:🧠|💬)\s/u.test(line) ? line : escapeSlackMrkdwn(line);
|
||||
}
|
||||
|
||||
@@ -1966,7 +1966,7 @@ describe("dispatchPreparedSlackMessage preview fallback", () => {
|
||||
);
|
||||
|
||||
expect(draftStream.update).toHaveBeenLastCalledWith(
|
||||
["Shelling", "", "• exec", "🧠 \\_Reading the Slack handler\\_"].join("\n"),
|
||||
["Shelling", "", "• exec", "🧠 _Reading the Slack handler_"].join("\n"),
|
||||
);
|
||||
const updates = draftStream.update.mock.calls.map((call) => String(call[0]));
|
||||
expect(updates.join("\n")).not.toContain("Reasoning");
|
||||
@@ -1995,7 +1995,7 @@ describe("dispatchPreparedSlackMessage preview fallback", () => {
|
||||
);
|
||||
|
||||
expect(draftStream.update).toHaveBeenLastCalledWith(
|
||||
["Shelling", "", "• exec", "🧠 \\_Reading Checking\\_"].join("\n"),
|
||||
["Shelling", "", "• exec", "🧠 _Reading Checking_"].join("\n"),
|
||||
);
|
||||
const updates = draftStream.update.mock.calls.map((call) => String(call[0]));
|
||||
expect(updates.join("\n")).not.toContain("Checking Reading");
|
||||
@@ -2022,7 +2022,7 @@ describe("dispatchPreparedSlackMessage preview fallback", () => {
|
||||
);
|
||||
|
||||
expect(draftStream.update).toHaveBeenLastCalledWith(
|
||||
["Shelling", "", "🧠 \\_Reading Checking\\_"].join("\n"),
|
||||
["Shelling", "", "🧠 _Reading Checking_"].join("\n"),
|
||||
);
|
||||
const updates = draftStream.update.mock.calls.map((call) => String(call[0]));
|
||||
expect(updates.join("\n")).toContain("Reading Checking");
|
||||
@@ -2049,7 +2049,7 @@ describe("dispatchPreparedSlackMessage preview fallback", () => {
|
||||
);
|
||||
|
||||
expect(draftStream.update).toHaveBeenLastCalledWith(
|
||||
["Shelling", "", "🧠 \\_Thinking about Slack preview state\\_"].join("\n"),
|
||||
["Shelling", "", "🧠 _Thinking about Slack preview state_"].join("\n"),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -3749,6 +3749,37 @@ describe("dispatchPreparedSlackMessage preview fallback", () => {
|
||||
expect(draftStream.update).toHaveBeenCalledTimes(updateCount);
|
||||
});
|
||||
|
||||
it("preserves Markdown in Slack commentary drafts for the outbound renderer", async () => {
|
||||
const draftStream = createDraftStreamStub();
|
||||
createSlackDraftStreamMock.mockReturnValueOnce(draftStream);
|
||||
mockedSlackStreamingMode = "progress";
|
||||
mockedSlackDraftMode = "status_final";
|
||||
mockedDispatchSequence = [];
|
||||
mockedReplyOptionEvents = [
|
||||
{
|
||||
kind: "item",
|
||||
itemKind: "preamble",
|
||||
itemId: "preamble-1",
|
||||
progressText: "I’m using the `monorepo` skill on Linux x86_64.",
|
||||
},
|
||||
];
|
||||
|
||||
await dispatchPreparedSlackMessage(
|
||||
createPreparedSlackMessage({
|
||||
accountConfig: {
|
||||
streaming: {
|
||||
mode: "progress",
|
||||
progress: { label: false, commentary: true, toolProgress: false },
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(draftStream.update).toHaveBeenLastCalledWith(
|
||||
"💬 I’m using the `monorepo` skill on Linux x86_64.",
|
||||
);
|
||||
});
|
||||
|
||||
it("uses the enterprise event client for Slack commentary drafts", async () => {
|
||||
const draftStream = createDraftStreamStub();
|
||||
createSlackDraftStreamMock.mockReturnValueOnce(draftStream);
|
||||
|
||||
@@ -200,6 +200,32 @@ describe("buildSlackProgressDraftBlocks", () => {
|
||||
).toEqual([legacyLineBlock("• *Preamble*", "—"), legacyLineBlock("🛠️ *Exec*", "run tests")]);
|
||||
});
|
||||
|
||||
it("renders authored commentary Markdown in legacy rich draft details", () => {
|
||||
expect(
|
||||
buildSlackProgressDraftBlocks({
|
||||
lines: [
|
||||
{
|
||||
id: "commentary:preamble-1",
|
||||
kind: "item",
|
||||
label: "Commentary",
|
||||
text: "💬 Rendering the `sample-widget` fixture on **example.test**.",
|
||||
prefix: false,
|
||||
},
|
||||
{
|
||||
id: "reasoning",
|
||||
kind: "item",
|
||||
label: "Reasoning",
|
||||
text: "_Reading the Slack handler_",
|
||||
prefix: false,
|
||||
},
|
||||
],
|
||||
}),
|
||||
).toEqual([
|
||||
legacyLineBlock("• *Commentary*", "Rendering the `sample-widget` fixture on *example.test*."),
|
||||
legacyLineBlock("• *Reasoning*", "_Reading the Slack handler_"),
|
||||
]);
|
||||
});
|
||||
|
||||
it("does not emit legacy rich draft blocks when there are no lines or heading", () => {
|
||||
expect(
|
||||
buildSlackProgressDraftBlocks({
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
formatPlanChecklistLines,
|
||||
} from "openclaw/plugin-sdk/channel-outbound";
|
||||
import { SLACK_MAX_BLOCKS } from "./blocks-input.js";
|
||||
import { normalizeSlackOutboundText } from "./format.js";
|
||||
import { escapeSlackMrkdwn } from "./monitor/mrkdwn.js";
|
||||
import { truncateSlackText } from "./truncate.js";
|
||||
|
||||
@@ -77,9 +78,20 @@ function legacyLineTitle(line: ChannelProgressDraftLine): string {
|
||||
return `${line.icon ?? "•"} *${escapeSlackMrkdwn(line.label)}*`;
|
||||
}
|
||||
|
||||
function isAuthoredProgressLine(line: ChannelProgressDraftLine): boolean {
|
||||
return line.id === "reasoning" || line.id?.startsWith("commentary:") === true;
|
||||
}
|
||||
|
||||
function legacyLineDetail(line: ChannelProgressDraftLine, maxChars: number): string {
|
||||
const detail = lineDetailParts(line).join(" · ");
|
||||
return detail ? escapeSlackMrkdwn(compactDetail(detail, maxChars)) : "—";
|
||||
if (detail) {
|
||||
return escapeSlackMrkdwn(compactDetail(detail, maxChars));
|
||||
}
|
||||
if (isAuthoredProgressLine(line)) {
|
||||
const text = line.text.replace(/^(?:🧠|💬)\s+/u, "");
|
||||
return normalizeSlackOutboundText(compactDetail(text, maxChars));
|
||||
}
|
||||
return "—";
|
||||
}
|
||||
|
||||
function lineTaskTitle(line: ChannelProgressDraftLine, maxLineChars: number): string {
|
||||
|
||||
Reference in New Issue
Block a user