feat(tool-web): replace the regex HTML-to-markdown converter with turndown

Implements the turndown Agent Note from the NIH dependency audit (full
variant, not the minimal entities-only fallback): dsh-tool-web's fetch
rendering now converts HTML through turndown + @joplin/turndown-plugin-gfm
(atx headings, fenced code, dash bullets, GFM tables/strikethrough) over
the real domino DOM, with script/style/noscript removed wholesale. The
hand-rolled ~86-line regex converter html.ts and its entity tables are
deleted; renderBody wraps the conversion in try/catch falling back to
the raw HTML body, because turndown's recursive DOM walk overflows with
a RangeError on pathological nesting (measured: 4k levels on the main
thread, 8k in a worker) where the regex version could never throw.

Closure weight, measured: tool-web IS in the single-exe runtime closure,
and the exe asset globs would pack ~7.9 MB of the three new packages —
but ~6 MB of that is domino's test corpus, with runtime lib/ at ~550 KB
against a ~174 MB artifact (<0.5% either way), so the swap wins.

Per testing policy the previously-missing keyless web_fetch snapshot
ships in the same change: the acp-agent `web-fetch` scenario boots a new
web.cordis.yml overlay (web seam + real dsh-web-fetch-local provider +
tool-web fetch-only + a loopback HTTP fixture server on a fixed port
serving deterministic HTML with entities, a GFM table, and nesting), so
recording and keyless replay both drive the real HTTP fetch and real
conversion end to end; the scenario pins the new `web` header class.

The Agent Note moves proposed -> implemented and is rewritten per the
lifecycle contract (Decision/Consequences/Testing, closure verdict and
alternatives recorded); tool-web and acp-agent READMEs updated in both
languages and pairs re-recorded.
This commit is contained in:
Tianyi Cui
2026-07-27 00:22:13 +08:00
parent 05adf5da4a
commit 45a5175e44
29 changed files with 962 additions and 210 deletions

35
pnpm-lock.yaml generated
View File

@@ -523,6 +523,9 @@ importers:
'@deepseek-ai/dsh-tool-todo':
specifier: workspace:*
version: link:../packages/todo/tool-todo
'@deepseek-ai/dsh-tool-web':
specifier: workspace:*
version: link:../packages/web/tool-web
'@deepseek-ai/dsh-tool-workflow':
specifier: workspace:*
version: link:../packages/workflow/tool-workflow
@@ -4259,9 +4262,15 @@ importers:
packages/web/tool-web:
dependencies:
'@joplin/turndown-plugin-gfm':
specifier: ^1.0.67
version: 1.0.67
schemastery:
specifier: ^3.18.0
version: 3.18.0
turndown:
specifier: ^7.2.4
version: 7.2.4
devDependencies:
'@deepseek-ai/dsh-agent':
specifier: workspace:^
@@ -4299,6 +4308,9 @@ importers:
'@deepseek-ai/dsh-web-search-exa':
specifier: workspace:^
version: link:../web-search-exa
'@types/turndown':
specifier: ^5.0.6
version: 5.0.6
cordis:
specifier: ^4.0.0-rc.7
version: 4.0.0-rc.7(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.5)
@@ -5971,6 +5983,9 @@ packages:
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
engines: {node: '>=12'}
'@joplin/turndown-plugin-gfm@1.0.67':
resolution: {integrity: sha512-FZfW5EZfidhzd1IaY1uxHnIZPTVOxAdleMZ4/1U6Nt5b7+Qj5JThDnaIomuJtetnUBzuRNbe9FWMuqD4B3dlWA==}
'@jridgewell/gen-mapping@0.3.13':
resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
@@ -6076,6 +6091,9 @@ packages:
'@opentelemetry/api':
optional: true
'@mixmark-io/domino@2.2.0':
resolution: {integrity: sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==}
'@modelcontextprotocol/sdk@1.29.0':
resolution: {integrity: sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==}
engines: {node: '>=18'}
@@ -7005,6 +7023,9 @@ packages:
'@types/trusted-types@2.0.7':
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
'@types/turndown@5.0.6':
resolution: {integrity: sha512-ru00MoyeeouE5BX4gRL+6m/BsDfbRayOskWqUvh7CLGW+UXxHQItqALa38kKnOiZPqJrtzJUgAC2+F0rL1S4Pg==}
'@types/unist@2.0.11':
resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==}
@@ -9463,6 +9484,10 @@ packages:
engines: {node: '>=18.0.0'}
hasBin: true
turndown@7.2.4:
resolution: {integrity: sha512-I8yFsfRzmzK0WV1pNNOA4A7y4RDfFxPRxb3t+e3ui14qSGOxGtiSP6GjeX+Y6CHb7HYaFj7ECUD7VE5kQMZWGQ==}
engines: {node: '>=18', npm: '>=9'}
type-check@0.4.0:
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
engines: {node: '>= 0.8.0'}
@@ -10860,6 +10885,8 @@ snapshots:
wrap-ansi: 8.1.0
wrap-ansi-cjs: wrap-ansi@7.0.0
'@joplin/turndown-plugin-gfm@1.0.67': {}
'@jridgewell/gen-mapping@0.3.13':
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
@@ -10951,6 +10978,8 @@ snapshots:
- bufferutil
- utf-8-validate
'@mixmark-io/domino@2.2.0': {}
'@modelcontextprotocol/sdk@1.29.0(zod@4.4.3)':
dependencies:
'@hono/node-server': 1.19.14(hono@4.12.29)
@@ -11705,6 +11734,8 @@ snapshots:
'@types/trusted-types@2.0.7':
optional: true
'@types/turndown@5.0.6': {}
'@types/unist@2.0.11': {}
'@types/unist@3.0.3': {}
@@ -14645,6 +14676,10 @@ snapshots:
optionalDependencies:
fsevents: 2.3.3
turndown@7.2.4:
dependencies:
'@mixmark-io/domino': 2.2.0
type-check@0.4.0:
dependencies:
prelude-ls: 1.2.1