Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions packages/junior/src/chat/slack/mrkdwn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,25 @@ export function ensureBlockSpacing(text: string): string {
export function renderSlackMrkdwn(text: string): string {
let normalized = text.replace(/\r\n?/g, "\n").replace(/[ \t]+$/gm, "");
normalized = ensureBlockSpacing(normalized);
// The body is sent as a Slack `markdown` block, which expects standard
// Markdown links ([label](url)), not Slack mrkdwn angle-bracket syntax
// (<URL> / <URL|label>). Convert to explicit Markdown links so the block
// renders them correctly and bold markers (**) can never collide with the
// angle brackets and corrupt the URL.
//
// Pass 1: repair malformed bold-wrapped angle autolinks produced by the
// model: **<https://url**> → [https://url](https://url)
normalized = normalized.replace(
/\*\*<(https?:\/\/[^*>\s]+)\*\*>/g,
"[$1]($1)",
);
// Pass 2: Slack labeled links → Markdown: <https://url|label> → [label](https://url)
normalized = normalized.replace(
/<(https?:\/\/[^|>\s]+)\|([^>]+)>/g,
"[$2]($1)",
);
// Pass 3: remaining angle autolinks → explicit Markdown links: <https://url> → [https://url](https://url)
normalized = normalized.replace(/<(https?:\/\/[^>\s]+)>/g, "[$1]($1)");
return normalized.replace(/\n{3,}/g, "\n\n").trim();
}

Expand Down
46 changes: 46 additions & 0 deletions packages/junior/tests/unit/misc/output.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,52 @@ describe("renderSlackMrkdwn", () => {
"one\n\n- item a\n- item b\n\ntwo",
);
});

it("converts Slack angle-bracket URL to explicit Markdown link", () => {
expect(
renderSlackMrkdwn("See <https://example.com> for details."),
).toBe("See [https://example.com](https://example.com) for details.");
});

it("converts Slack labeled links to Markdown format", () => {
expect(
renderSlackMrkdwn("<https://github.com/org/repo/pull/1|PR #1>"),
).toBe("[PR #1](https://github.com/org/repo/pull/1)");
});

it("resolves bold+angle-bracket collision that broke GitHub PR links", () => {
// The model generated **<https://url**> — closing ** absorbed inside the
// angle brackets. Pass 1 catches this and converts to [url](url).
expect(
renderSlackMrkdwn(
"**<https://github.com/getsentry/sentry/pull/116765**>",
),
).toBe(
"[https://github.com/getsentry/sentry/pull/116765](https://github.com/getsentry/sentry/pull/116765)",
);
});

it("converts multiple angle-bracket URLs in one message", () => {
expect(
renderSlackMrkdwn(
"PR1: <https://github.com/org/repo/pull/1> and PR2: <https://github.com/org/repo/pull/2>",
),
).toBe(
"PR1: [https://github.com/org/repo/pull/1](https://github.com/org/repo/pull/1) and PR2: [https://github.com/org/repo/pull/2](https://github.com/org/repo/pull/2)",
);
});

it("leaves Markdown links unchanged", () => {
expect(
renderSlackMrkdwn("**[PR #123](https://github.com/org/repo/pull/123)**"),
).toBe("**[PR #123](https://github.com/org/repo/pull/123)**");
});

it("leaves plain bold text unchanged", () => {
expect(renderSlackMrkdwn("**Root cause** is here.")).toBe(
"**Root cause** is here.",
);
});
});

describe("buildSlackOutputMessage", () => {
Expand Down
Loading