Skip to content

feat(pulse/telegram): render markdown in messages via HTML parse mode#1285

Open
catchingknives wants to merge 1 commit into
danielmiessler:mainfrom
catchingknives:feat/telegram-markdown-html-render
Open

feat(pulse/telegram): render markdown in messages via HTML parse mode#1285
catchingknives wants to merge 1 commit into
danielmiessler:mainfrom
catchingknives:feat/telegram-markdown-html-render

Conversation

@catchingknives
Copy link
Copy Markdown

@catchingknives catchingknives commented May 18, 2026

Why

The Telegram module sends ctx.reply(text) with no parse_mode, so the DA's **bold**, *italic*, `code`, code fences, and links arrive as literal characters on the phone. The existing TELEGRAM MODE OVERRIDE prompt worked around this by steering the DA toward plain prose — a render-side problem masquerading as a content-side restriction.

Change

  • New mdToHtml helper in modules/markdown-html.ts converts a useful subset of markdown to Telegram HTML: bold, italic, strikethrough, inline code, fenced code, links, ATX headers (→ bold lines), bullets (→ ).
  • Telegram module sends with parse_mode: "HTML" by default; plain-text fallback on any send error via .catch().
  • New markdown_mode: "html" | "plain" field in TelegramConfig, defaults to "html" — opt-out preserved for anyone who wants raw text.
  • TELEGRAM MODE OVERRIDE prompt flipped from "no formatting" to an inline cheatsheet of what renders, so the DA writes naturally-formatted prose.

Robustness choices

  • Code blocks are stash-and-restored before other transforms — bold/italic cannot leak into them.
  • Italic boundaries admit punctuation, whitespace, and HTML tag chars so **a _b_** nests correctly while snake_case and Array<T> survive untouched.
  • Unbalanced markers degrade to literal characters, so chunk-splitting at the 4096-char boundary cannot produce broken HTML.

Tests

Releases/v5.0.0/.claude/PAI/PULSE/modules/telegram.test.ts — 21 cases covering snake_case safety, nested formatting, HTML escaping, code-block isolation, unbalanced markers, URLs with underscores, multiple inline spans.

$ bun test ./Releases/v5.0.0/.claude/PAI/PULSE/modules/telegram.test.ts
21 pass, 0 fail

End-to-end validation

Showcase message exercising every transformation sent through a real Telegram bot; renders correctly on iOS.

Relationship to other open PRs

The Telegram module sends ctx.reply(text) with no parse_mode, so the
DA's **bold**, *italic*, `code`, code fences, and links arrive as
literal characters on the phone. The existing TELEGRAM MODE OVERRIDE
prompt worked around this by steering the DA toward plain prose — a
render-side problem masquerading as a content-side restriction.

Changes:

- New `mdToHtml` helper in modules/markdown-html.ts converts a useful
  subset of markdown to Telegram HTML: bold, italic, strikethrough,
  inline code, fenced code, links, ATX headers (→ bold lines),
  bullets (→ '•').

- Telegram module sends with parse_mode: 'HTML' by default;
  plain-text fallback on any send error via .catch().

- New `markdown_mode: "html" | "plain"` field in TelegramConfig,
  defaults to 'html' — opt-out preserved for users who want raw text.

- TELEGRAM MODE OVERRIDE prompt flipped from 'no formatting' to an
  inline cheatsheet of what renders, so the DA writes naturally-
  formatted prose.

Robustness:

- Code blocks are stash-and-restored before other transforms run —
  bold/italic cannot leak into them.

- Italic boundaries admit punctuation, whitespace, and HTML tag chars
  so '**a _b_**' nests correctly while 'snake_case' and 'Array<T>'
  survive untouched.

- Unbalanced markers degrade to literal characters, so chunk-splitting
  at the 4096-char boundary cannot produce broken HTML.

Tests:

  $ bun test ./Releases/v5.0.0/.claude/PAI/PULSE/modules/telegram.test.ts
  21 pass, 0 fail

End-to-end validation: showcase message exercising every transformation
sent through @nairyo_bot; renders correctly on iOS.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant