fix(deps): promote markdown to a core dependency so rich delivery works out of the box (#32486) (#38649)
`markdown` was declared only in the `matrix` optional extra, and the official Docker image installs `--extra all --extra messaging --extra anthropic --extra bedrock --extra azure-identity --extra hindsight` — notably NOT `--extra matrix` (the matrix extra is deliberately routed to lazy-install because `mautrix[encryption]`/`python-olm` can't build on Windows/macOS — see the 2026-05-12 policy comment in `[all]`). Result: `markdown` never lands in the image venv, so the Markdown->HTML conversion on the DEFAULT delivery path silently falls back to plain text. Cron/agent deliveries render raw `##`/`**`/tables in clients like Element (no `formatted_body`). The conversion is now used by BOTH `gateway/platforms/matrix.py` and `tools/send_message_tool.py`, so it is no longer matrix-specific. `markdown` is a pure-Python `py3-none-any` wheel (~108KB, no compiled extensions, no platform constraints), so none of the reasons the matrix extra was lazy-routed apply to it. Promote it to a core dependency so it ships in the wheel, the Docker image, and every install; drop the now redundant copies from the `matrix` extra and the `platform.matrix` lazy-deps group; refresh the stale "installed with the matrix extra" docstring. Verified against a real build: ran the image's exact `uv sync` command (same extras, no `--extra matrix`) in a clean container off the new lockfile -> `import markdown` succeeds (3.10.2). On `origin/main` the same command leaves markdown absent. 223 targeted tests pass (test_matrix.py + test_lazy_deps.py). Closes #32486.
This commit is contained in:
@ -2799,11 +2799,11 @@ class MatrixAdapter(BasePlatformAdapter):
|
|||||||
def _markdown_to_html(self, text: str) -> str:
|
def _markdown_to_html(self, text: str) -> str:
|
||||||
"""Convert Markdown to Matrix-compatible HTML (org.matrix.custom.html).
|
"""Convert Markdown to Matrix-compatible HTML (org.matrix.custom.html).
|
||||||
|
|
||||||
Uses the ``markdown`` library when available (installed with the
|
Uses the ``markdown`` library (a core dependency) when available.
|
||||||
``matrix`` extra). Falls back to a comprehensive regex converter
|
Falls back to a comprehensive regex converter that handles fenced
|
||||||
that handles fenced code blocks, inline code, headers, bold,
|
code blocks, inline code, headers, bold, italic, strikethrough,
|
||||||
italic, strikethrough, links, blockquotes, lists, and horizontal
|
links, blockquotes, lists, and horizontal rules — everything the
|
||||||
rules — everything the Matrix HTML spec allows.
|
Matrix HTML spec allows.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
import markdown as _md
|
import markdown as _md
|
||||||
|
|||||||
@ -61,6 +61,16 @@ dependencies = [
|
|||||||
"prompt_toolkit==3.0.52",
|
"prompt_toolkit==3.0.52",
|
||||||
# Cron scheduler (built-in feature — scheduled cron/interval jobs use croniter).
|
# Cron scheduler (built-in feature — scheduled cron/interval jobs use croniter).
|
||||||
"croniter==6.0.0",
|
"croniter==6.0.0",
|
||||||
|
# Markdown -> HTML conversion for rich message delivery (Matrix
|
||||||
|
# `formatted_body`, and the `send_message` tool's HTML path). Now on the
|
||||||
|
# DEFAULT delivery path, not matrix-specific: without it both
|
||||||
|
# gateway/platforms/matrix.py and tools/send_message_tool.py silently fall
|
||||||
|
# back to plain text, so cron/agent deliveries render raw `##`/`**`/tables
|
||||||
|
# in clients like Element (see #32486). Pure-Python py3-none-any wheel
|
||||||
|
# (~108KB, no compiled extensions, no platform constraints), so unlike the
|
||||||
|
# matrix extra's `mautrix`/`python-olm` it's safe to ship everywhere — keeps
|
||||||
|
# it out of the lazy-install path that exists only for the heavy matrix deps.
|
||||||
|
"Markdown==3.10.2",
|
||||||
# Skills Hub (GitHub App JWT auth — optional, only needed for bot identity)
|
# Skills Hub (GitHub App JWT auth — optional, only needed for bot identity)
|
||||||
"PyJWT[crypto]==2.12.1", # CVE-2026-32597
|
"PyJWT[crypto]==2.12.1", # CVE-2026-32597
|
||||||
# Windows has no IANA tzdata shipped with the OS, so Python's ``zoneinfo``
|
# Windows has no IANA tzdata shipped with the OS, so Python's ``zoneinfo``
|
||||||
@ -104,7 +114,7 @@ dev = ["debugpy==1.8.20", "pytest==9.0.2", "pytest-asyncio==1.3.0", "pytest-time
|
|||||||
messaging = ["python-telegram-bot[webhooks]==22.6", "discord.py[voice]==2.7.1", "aiohttp==3.13.3", "brotlicffi==1.2.0.1", "slack-bolt==1.27.0", "slack-sdk==3.40.1", "qrcode==7.4.2"]
|
messaging = ["python-telegram-bot[webhooks]==22.6", "discord.py[voice]==2.7.1", "aiohttp==3.13.3", "brotlicffi==1.2.0.1", "slack-bolt==1.27.0", "slack-sdk==3.40.1", "qrcode==7.4.2"]
|
||||||
cron = [] # croniter is now a core dependency; this extra kept for back-compat
|
cron = [] # croniter is now a core dependency; this extra kept for back-compat
|
||||||
slack = ["slack-bolt==1.27.0", "slack-sdk==3.40.1", "aiohttp==3.13.3"]
|
slack = ["slack-bolt==1.27.0", "slack-sdk==3.40.1", "aiohttp==3.13.3"]
|
||||||
matrix = ["mautrix[encryption]==0.21.0", "Markdown==3.10.2", "aiosqlite==0.22.1", "asyncpg==0.31.0", "aiohttp-socks==0.11.0"]
|
matrix = ["mautrix[encryption]==0.21.0", "aiosqlite==0.22.1", "asyncpg==0.31.0", "aiohttp-socks==0.11.0"]
|
||||||
# WeCom callback-mode adapter — parses untrusted XML POST bodies from
|
# WeCom callback-mode adapter — parses untrusted XML POST bodies from
|
||||||
# WeCom-controlled callback endpoints, so we use defusedxml (drop-in
|
# WeCom-controlled callback endpoints, so we use defusedxml (drop-in
|
||||||
# replacement for stdlib xml.etree.ElementTree) to block billion-laughs
|
# replacement for stdlib xml.etree.ElementTree) to block billion-laughs
|
||||||
|
|||||||
@ -135,7 +135,6 @@ LAZY_DEPS: dict[str, tuple[str, ...]] = {
|
|||||||
),
|
),
|
||||||
"platform.matrix": (
|
"platform.matrix": (
|
||||||
"mautrix[encryption]==0.21.0",
|
"mautrix[encryption]==0.21.0",
|
||||||
"Markdown==3.10.2",
|
|
||||||
"aiosqlite==0.22.1",
|
"aiosqlite==0.22.1",
|
||||||
"asyncpg==0.31.0",
|
"asyncpg==0.31.0",
|
||||||
"aiohttp-socks==0.11.0",
|
"aiohttp-socks==0.11.0",
|
||||||
|
|||||||
4
uv.lock
generated
4
uv.lock
generated
@ -1398,6 +1398,7 @@ dependencies = [
|
|||||||
{ name = "fire" },
|
{ name = "fire" },
|
||||||
{ name = "httpx", extra = ["socks"] },
|
{ name = "httpx", extra = ["socks"] },
|
||||||
{ name = "jinja2" },
|
{ name = "jinja2" },
|
||||||
|
{ name = "markdown" },
|
||||||
{ name = "openai" },
|
{ name = "openai" },
|
||||||
{ name = "pathspec" },
|
{ name = "pathspec" },
|
||||||
{ name = "prompt-toolkit" },
|
{ name = "prompt-toolkit" },
|
||||||
@ -1502,7 +1503,6 @@ matrix = [
|
|||||||
{ name = "aiohttp-socks" },
|
{ name = "aiohttp-socks" },
|
||||||
{ name = "aiosqlite" },
|
{ name = "aiosqlite" },
|
||||||
{ name = "asyncpg" },
|
{ name = "asyncpg" },
|
||||||
{ name = "markdown" },
|
|
||||||
{ name = "mautrix", extra = ["encryption"] },
|
{ name = "mautrix", extra = ["encryption"] },
|
||||||
]
|
]
|
||||||
mcp = [
|
mcp = [
|
||||||
@ -1642,7 +1642,7 @@ requires-dist = [
|
|||||||
{ name = "httpx", extras = ["socks"], specifier = "==0.28.1" },
|
{ name = "httpx", extras = ["socks"], specifier = "==0.28.1" },
|
||||||
{ name = "jinja2", specifier = "==3.1.6" },
|
{ name = "jinja2", specifier = "==3.1.6" },
|
||||||
{ name = "lark-oapi", marker = "extra == 'feishu'", specifier = "==1.5.3" },
|
{ name = "lark-oapi", marker = "extra == 'feishu'", specifier = "==1.5.3" },
|
||||||
{ name = "markdown", marker = "extra == 'matrix'", specifier = "==3.10.2" },
|
{ name = "markdown", specifier = "==3.10.2" },
|
||||||
{ name = "mautrix", extras = ["encryption"], marker = "extra == 'matrix'", specifier = "==0.21.0" },
|
{ name = "mautrix", extras = ["encryption"], marker = "extra == 'matrix'", specifier = "==0.21.0" },
|
||||||
{ name = "mcp", marker = "extra == 'computer-use'", specifier = "==1.26.0" },
|
{ name = "mcp", marker = "extra == 'computer-use'", specifier = "==1.26.0" },
|
||||||
{ name = "mcp", marker = "extra == 'dev'", specifier = "==1.26.0" },
|
{ name = "mcp", marker = "extra == 'dev'", specifier = "==1.26.0" },
|
||||||
|
|||||||
Reference in New Issue
Block a user