diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ea2591cac62..9134f8cf65c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- **Web Readability relative links:** seed parsed documents with the request URL so article links resolve correctly while removing the plugin's duplicate lazy-loader facade. (#106860) - **Browser auto-routing:** fall back to the Gateway host when an implicitly selected browser node reports that its control host is unreachable, while preserving explicit node pins and ambiguous action failures. - **Discord voice participant context:** maintain the live Gateway voice-state roster and include current channel participants in authorized voice agent turns so agents can answer who is present. - **OC Path JSONC insertion:** patch object and array insertions through `jsonc-parser` so comments, trailing commas, and CRLF formatting survive. (#106847) diff --git a/extensions/web-readability/web-content-extractor.test.ts b/extensions/web-readability/web-content-extractor.test.ts index 9a3a0a41f3c8..2a8e32de55b0 100644 --- a/extensions/web-readability/web-content-extractor.test.ts +++ b/extensions/web-readability/web-content-extractor.test.ts @@ -20,6 +20,7 @@ const SAMPLE_HTML = `

Example Article

Main content starts here with enough words to satisfy readability.

Second paragraph for a bit more signal.

+

Continue reading

@@ -59,6 +60,7 @@ describe("web readability extractor", () => { }); const extracted = requireReadabilityResult(result); expect(extracted.text).toContain("Main content starts here"); + expect(extracted.text).toContain("[Continue reading](https://example.com/next)"); expect(extracted.title).toBe("Example Article"); }); diff --git a/extensions/web-readability/web-content-extractor.ts b/extensions/web-readability/web-content-extractor.ts index 09b257d4c5e6..7126ec7253b1 100644 --- a/extensions/web-readability/web-content-extractor.ts +++ b/extensions/web-readability/web-content-extractor.ts @@ -1,4 +1,5 @@ // Web Readability plugin module implements web content extractor behavior. +import { createLazyRuntimeSurface } from "openclaw/plugin-sdk/lazy-runtime"; import type { WebContentExtractionRequest, WebContentExtractionResult, @@ -30,65 +31,20 @@ const HTML_VOID_TAGS = new Set([ "wbr", ]); -type ParsedHtml = { - document: Document; -}; - -type ParseHtml = (html: string) => ParsedHtml; - -type ReadabilityResult = { - content?: string; - textContent?: string | null; - title?: string | null; -}; - -type ReadabilityInstance = { - parse(): ReadabilityResult | null; -}; - -type ReadabilityConstructor = new ( - document: Document, - options: { charThreshold: number }, -) => ReadabilityInstance; - -type ReadabilityModule = { - Readability: ReadabilityConstructor; -}; - -type LinkedomModule = { - parseHTML: ParseHtml; -}; - const READABILITY_MODULE = "@mozilla/readability"; const LINKEDOM_MODULE = "linkedom"; -let readabilityDepsPromise: - | Promise<{ - Readability: ReadabilityConstructor; - parseHTML: ParseHtml; - }> - | undefined; - -async function loadReadabilityDeps(): Promise<{ - Readability: ReadabilityConstructor; - parseHTML: ParseHtml; -}> { - if (!readabilityDepsPromise) { - readabilityDepsPromise = Promise.all([ - import(READABILITY_MODULE) as Promise, - import(LINKEDOM_MODULE) as Promise, - ]).then(([readability, linkedom]) => ({ - Readability: readability.Readability, - parseHTML: linkedom.parseHTML, - })); - } - try { - return await readabilityDepsPromise; - } catch (error) { - readabilityDepsPromise = undefined; - throw error; - } -} +const loadReadabilityDeps = createLazyRuntimeSurface( + () => + Promise.all([ + import(READABILITY_MODULE) as Promise, + import(LINKEDOM_MODULE) as Promise, + ]), + ([readability, linkedom]) => ({ + Readability: readability.Readability, + parseHTML: linkedom.parseHTML, + }), +); function exceedsEstimatedHtmlNestingDepth(html: string, maxDepth: number): boolean { let depth = 0; @@ -175,12 +131,7 @@ async function extractWithReadability( } try { const { Readability, parseHTML } = await loadReadabilityDeps(); - const { document } = parseHTML(cleanHtml); - try { - (document as { baseURI?: string }).baseURI = request.url; - } catch { - // Best-effort base URI for relative links. - } + const { document } = parseHTML(cleanHtml, { location: { href: request.url } }); const reader = new Readability(document, { charThreshold: 0 }); const parsed = reader.parse(); if (!parsed?.content) {