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:
|
||||
"""Convert Markdown to Matrix-compatible HTML (org.matrix.custom.html).
|
||||
|
||||
Uses the ``markdown`` library when available (installed with the
|
||||
``matrix`` extra). Falls back to a comprehensive regex converter
|
||||
that handles fenced code blocks, inline code, headers, bold,
|
||||
italic, strikethrough, links, blockquotes, lists, and horizontal
|
||||
rules — everything the Matrix HTML spec allows.
|
||||
Uses the ``markdown`` library (a core dependency) when available.
|
||||
Falls back to a comprehensive regex converter that handles fenced
|
||||
code blocks, inline code, headers, bold, italic, strikethrough,
|
||||
links, blockquotes, lists, and horizontal rules — everything the
|
||||
Matrix HTML spec allows.
|
||||
"""
|
||||
try:
|
||||
import markdown as _md
|
||||
|
||||
@ -61,6 +61,16 @@ dependencies = [
|
||||
"prompt_toolkit==3.0.52",
|
||||
# Cron scheduler (built-in feature — scheduled cron/interval jobs use croniter).
|
||||
"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)
|
||||
"PyJWT[crypto]==2.12.1", # CVE-2026-32597
|
||||
# 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"]
|
||||
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"]
|
||||
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-controlled callback endpoints, so we use defusedxml (drop-in
|
||||
# replacement for stdlib xml.etree.ElementTree) to block billion-laughs
|
||||
|
||||
@ -135,7 +135,6 @@ LAZY_DEPS: dict[str, tuple[str, ...]] = {
|
||||
),
|
||||
"platform.matrix": (
|
||||
"mautrix[encryption]==0.21.0",
|
||||
"Markdown==3.10.2",
|
||||
"aiosqlite==0.22.1",
|
||||
"asyncpg==0.31.0",
|
||||
"aiohttp-socks==0.11.0",
|
||||
|
||||
4
uv.lock
generated
4
uv.lock
generated
@ -1398,6 +1398,7 @@ dependencies = [
|
||||
{ name = "fire" },
|
||||
{ name = "httpx", extra = ["socks"] },
|
||||
{ name = "jinja2" },
|
||||
{ name = "markdown" },
|
||||
{ name = "openai" },
|
||||
{ name = "pathspec" },
|
||||
{ name = "prompt-toolkit" },
|
||||
@ -1502,7 +1503,6 @@ matrix = [
|
||||
{ name = "aiohttp-socks" },
|
||||
{ name = "aiosqlite" },
|
||||
{ name = "asyncpg" },
|
||||
{ name = "markdown" },
|
||||
{ name = "mautrix", extra = ["encryption"] },
|
||||
]
|
||||
mcp = [
|
||||
@ -1642,7 +1642,7 @@ requires-dist = [
|
||||
{ name = "httpx", extras = ["socks"], specifier = "==0.28.1" },
|
||||
{ name = "jinja2", specifier = "==3.1.6" },
|
||||
{ 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 = "mcp", marker = "extra == 'computer-use'", specifier = "==1.26.0" },
|
||||
{ name = "mcp", marker = "extra == 'dev'", specifier = "==1.26.0" },
|
||||
|
||||
Reference in New Issue
Block a user