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.
53 lines
2.0 KiB
JavaScript
53 lines
2.0 KiB
JavaScript
/**
|
|
* Deterministic loopback HTTP fixture for the web-fetch snapshot scenario: a
|
|
* small HTML page (headings, named entities, a GFM table, nested formatting)
|
|
* on a fixed port, so recording and keyless replay drive the REAL
|
|
* `dsh-web-fetch-local` transport and `dsh-tool-web` markdown rendering
|
|
* without external network. The port is fixed because the fetched URL is part
|
|
* of the recorded model transcript.
|
|
*/
|
|
import { createServer } from 'node:http'
|
|
|
|
/** Fixed loopback port the scenario prompt points `web_fetch` at. */
|
|
const PORT = 43117
|
|
|
|
const PAGE = `<!doctype html>
|
|
<html><head><title>Menu</title><style>.x{color:red}</style><script>ignored()</script></head>
|
|
<body>
|
|
<h1>Café menu</h1>
|
|
<p>Prices include <strong>service & <em>tax</em></strong> — updated daily.</p>
|
|
<ul><li>Espresso</li><li>Flat white</li></ul>
|
|
<table><thead><tr><th>Drink</th><th>Price</th></tr></thead><tbody><tr><td>Espresso</td><td>€2</td></tr><tr><td>Flat white</td><td>€3</td></tr></tbody></table>
|
|
<p>See <a href="https://fixture.invalid/specials">today’s specials</a>.</p>
|
|
</body></html>
|
|
`
|
|
|
|
/** Cordis plugin name. */
|
|
export const name = 'web-fetch-fixture-server'
|
|
|
|
/**
|
|
* Start the fixture server on 127.0.0.1 and register its shutdown.
|
|
* @param ctx - Cordis context; the effect disposes the server with the fiber.
|
|
*/
|
|
export async function apply(ctx) {
|
|
const server = createServer((req, res) => {
|
|
if (req.url === '/menu.html') {
|
|
res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' })
|
|
res.end(PAGE)
|
|
return
|
|
}
|
|
res.writeHead(404, { 'content-type': 'text/plain; charset=utf-8' })
|
|
res.end('not found')
|
|
})
|
|
await new Promise((resolve, reject) => {
|
|
server.once('error', reject)
|
|
server.listen(PORT, '127.0.0.1', () => resolve(undefined))
|
|
})
|
|
// The fixture must never hold the process open past protocol shutdown.
|
|
server.unref()
|
|
ctx.effect(() => () => {
|
|
server.close()
|
|
server.closeAllConnections()
|
|
}, 'web-fetch-fixture-server')
|
|
}
|