Repository navigation
feat(plugins): support manifests and static asset directories - #5685
Conversation
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
A critical root-symlink validation issue and a moderate native-module timeout limitation remain unresolved.
Get a fresh assessment by requesting another Copilot review.
Review effort: Lite
Findings: 1
Open (1)
What changed in this PR
Adds manifest-backed static assets for plugins, including authenticated serving, native module loading, and a migrated bookmarks example.
Changes:
- Adds
BrowserAssetsmanifests, validation, snapshots, MIME handling, and asset routes. - Adds native credentialed loading for packaged modules with relative imports.
- Updates the bookmarks example, documentation, dependencies, and test coverage.
| File | Summary |
|---|---|
frontend/tests/unit/core/extensions/registry.test.ts |
Transport-aware loader unit tests |
frontend/tests/e2e/plugin-assets.spec.ts |
Packaged asset loading E2E tests |
frontend/tests/e2e/bookmark-plugin.spec.ts |
Updated bookmark plugin E2E coverage |
frontend/src/core/extensions/registry.ts |
Transport-aware plugin loading |
frontend/src/core/extensions/contracts.ts |
Frontend transport contracts |
frontend/src/core/extensions/asset-module.ts |
Native credentialed asset loader; timeout does not cancel late module evaluation |
frontend/src/core/extensions/api.ts |
Transport discovery parsing |
frontend/AGENTS.md |
Plugin development guidance |
examples/deerflow-extension-bookmarks/README.md |
Example usage documentation |
examples/deerflow-extension-bookmarks/pyproject.toml |
Example version and dependency updates |
examples/deerflow-extension-bookmarks/deerflow_extension_bookmarks/ui_manifest.json |
Packaged asset manifest |
examples/deerflow-extension-bookmarks/deerflow_extension_bookmarks/static/dist/styles.css |
Packaged stylesheet |
examples/deerflow-extension-bookmarks/deerflow_extension_bookmarks/static/dist/index.mjs |
Plugin entry module |
examples/deerflow-extension-bookmarks/deerflow_extension_bookmarks/static/dist/chunks/bookmarks.mjs |
Lazy bookmark page module |
examples/deerflow-extension-bookmarks/deerflow_extension_bookmarks/static/dist/bookmark.svg |
Packaged bookmark icon |
examples/deerflow-extension-bookmarks/deerflow_extension_bookmarks/__init__.py |
Example asset registration |
docs/full-stack-plugins.md |
Packaged asset documentation |
backend/uv.lock |
Dependency lock update |
backend/tests/test_extension_api_contracts.py |
Extension API contract tests |
backend/tests/test_browser_assets.py |
Asset security and boundary tests |
backend/packages/harness/pyproject.toml |
Extension API pin update |
backend/packages/harness/deerflow/extensions/registry.py |
Browser asset registration |
backend/packages/harness/deerflow/extensions/browser_assets.py |
Manifest validation and snapshots; root symlinks can escape package confinement |
backend/packages/extension-api/pyproject.toml |
Extension API package version |
backend/packages/extension-api/deerflow_extension_api/plugins.py |
BrowserAssets public API |
backend/packages/extension-api/deerflow_extension_api/__init__.py |
Public export and version update |
backend/app/gateway/routers/plugins.py |
Authenticated static asset serving |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
willem-bd
left a comment
There was a problem hiding this comment.
Reviewed at 50d8214. No blocking findings. The boundary work checks out: manifest allowlist with strict typing (bool-as-int rejected), per-component symlink rejection plus package-confined resolve(strict=True), deterministic revision digest over manifest+paths+content, namespace+revision scoping on the asset route (the cross-namespace 404 is covered), auth enforced before lookup, and per-plugin transport isolation on the loader side. The root-symlink thread from the Copilot review is fixed at this head with regression coverage, and the untouched reference example keeps loading because the loader's api check is a range, not an exact pin. Two non-blocking suggestions below.
| script.onload = () => { | ||
| // The credentialed script populated the document's module map. Reuse that | ||
| // module instance to obtain its exports; do not fetch it as a Blob. | ||
| void import(/* webpackIgnore: true */ absoluteURL).then( |
There was a problem hiding this comment.
Non-blocking: the repo also documents DEER_FLOW_DEV_BUNDLER=turbo as a supported dev path, and Turbopack's handling of webpackIgnore on dynamic imports has been inconsistent across versions (turbopackIgnore is its native marker). The new e2e transpiles this file standalone and the unit tests mock the importer, so only the webpack production build exercises this line. Consider adding /* turbopackIgnore: true */ next to webpackIgnore so import(absoluteURL) stays a native dynamic import under the turbo dev bundler too.
There was a problem hiding this comment.
Addressed in ccfb567.
Added turbopackIgnore: true alongside webpackIgnore and documented how to run the bookmark E2E against the actual Turbo development server.
I also checked the existing head before this change: the default bookmark flow already passed with this pinned Next.js version. With both markers, all 9 bookmark browser tests passed against DEER_FLOW_DEV_BUNDLER=turbo pnpm dev, exercising the real compiled loader, packaged entry/lazy chunk, styling and actions. This is explicit bundler compatibility plus a verified dev path, rather than a claim that the previous head was broken on the current version.
The Webpack production build, 30 related production-browser tests, frontend lint/typecheck and all 1,939 frontend unit tests also passed.
| return Response( | ||
| asset.content, | ||
| media_type=asset.media_type, | ||
| headers={ |
There was a problem hiding this comment.
Non-blocking hardening: image/svg+xml is served from the authenticated Gateway origin, and an SVG loaded by direct navigation runs as a document in that origin (scripts inside it execute there, with cookies available), while the docs present "HTML ... not served" as the serving boundary. For a trusted operator-installed package the marginal risk is small, but Content-Security-Policy: sandbox on these responses would de-fang direct navigation without affecting subresource use (<img>, CSS, fonts, module-script fetches ignore a response's sandbox directive) — or at least add the SVG caveat next to that docs line.
There was a problem hiding this comment.
Addressed in ccfb567.
Added Content-Security-Policy: sandbox to packaged asset responses and documented the direct-navigation boundary. The restriction applies when an asset becomes a document; it does not isolate plugin JavaScript intentionally loaded into the host page.
The new Chromium regression uses the real Python registration and Gateway asset route with an SVG containing a script. Before the fix, direct navigation executed it. After the fix, the SVG still renders as an image and direct navigation cannot execute the script. A positive control removes only the response's sandbox header and confirms that the same SVG executes again. All 5 native asset browser tests passed, including same/split-origin authenticated static/lazy module imports, CSS and images; backend route tests check the header across served asset types.
…5769) * docs(extensions): add an eleven-page extension developer manual Add a harness/extensions/ section in English and Chinese documenting the deerflow-extension-api 0.2.3 contract: index when to write an extension vs a tool, MCP server or skill; contribution kinds; loading; failure and trust model; versioning quick-start build, test, install and remove a working extension runtime load sequence, API version rules, scopes and ExtensionData, fail-open and cancellation, diagnostics middleware placements and where each lands, scope, ordering, observe-only wrap hooks, failure isolation observers task lifecycle, system model calls, agent assembly and fingerprint, context compaction services-and-routes service lifecycle, router rejection rules, auth, principals run-evidence service and request-scoped readers (#5727), cursor semantics, deletions, redaction, store differences plugins experimental full-stack plugins, inline modules and manifest-based browser assets (#5685) operations extension manager CLI, sources, upgrade, rollback, Docker, Helm, recovery troubleshooting indexed by the exact log and error strings reference every public name and the contract version history Also fix stale descriptions: the contribution kinds listed in AGENTS.md and deerflow/extensions/AGENTS.md (now eight, including plugins), the run-evidence redaction claim (only auth_token is removed), and the plugin mount return value in docs/full-stack-plugins.md ({ dispose }, not a function). Changelog entries added in both languages. * docs(agents): trim root AGENTS.md to keep inherited chains under the size limit * docs(extensions): scope the manual to the 0.2.1 contract Plugins and request-scoped run evidence are not in 2.1.x-dev yet; they move to a stacked follow-up so this change can be cherry-picked. * docs(extensions): clarify PAT rejection on contributed routes
…5769) * docs(extensions): add an eleven-page extension developer manual Add a harness/extensions/ section in English and Chinese documenting the deerflow-extension-api 0.2.3 contract: index when to write an extension vs a tool, MCP server or skill; contribution kinds; loading; failure and trust model; versioning quick-start build, test, install and remove a working extension runtime load sequence, API version rules, scopes and ExtensionData, fail-open and cancellation, diagnostics middleware placements and where each lands, scope, ordering, observe-only wrap hooks, failure isolation observers task lifecycle, system model calls, agent assembly and fingerprint, context compaction services-and-routes service lifecycle, router rejection rules, auth, principals run-evidence service and request-scoped readers (#5727), cursor semantics, deletions, redaction, store differences plugins experimental full-stack plugins, inline modules and manifest-based browser assets (#5685) operations extension manager CLI, sources, upgrade, rollback, Docker, Helm, recovery troubleshooting indexed by the exact log and error strings reference every public name and the contract version history Also fix stale descriptions: the contribution kinds listed in AGENTS.md and deerflow/extensions/AGENTS.md (now eight, including plugins), the run-evidence redaction claim (only auth_token is removed), and the plugin mount return value in docs/full-stack-plugins.md ({ dispose }, not a function). Changelog entries added in both languages. * docs(agents): trim root AGENTS.md to keep inherited chains under the size limit * docs(extensions): scope the manual to the 0.2.1 contract Plugins and request-scoped run evidence are not in 2.1.x-dev yet; they move to a stacked follow-up so this change can be cherry-picked. * docs(extensions): clarify PAT rejection on contributed routes
…run evidence (#5775) * docs(extensions): add an eleven-page extension developer manual Add a harness/extensions/ section in English and Chinese documenting the deerflow-extension-api 0.2.3 contract: index when to write an extension vs a tool, MCP server or skill; contribution kinds; loading; failure and trust model; versioning quick-start build, test, install and remove a working extension runtime load sequence, API version rules, scopes and ExtensionData, fail-open and cancellation, diagnostics middleware placements and where each lands, scope, ordering, observe-only wrap hooks, failure isolation observers task lifecycle, system model calls, agent assembly and fingerprint, context compaction services-and-routes service lifecycle, router rejection rules, auth, principals run-evidence service and request-scoped readers (#5727), cursor semantics, deletions, redaction, store differences plugins experimental full-stack plugins, inline modules and manifest-based browser assets (#5685) operations extension manager CLI, sources, upgrade, rollback, Docker, Helm, recovery troubleshooting indexed by the exact log and error strings reference every public name and the contract version history Also fix stale descriptions: the contribution kinds listed in AGENTS.md and deerflow/extensions/AGENTS.md (now eight, including plugins), the run-evidence redaction claim (only auth_token is removed), and the plugin mount return value in docs/full-stack-plugins.md ({ dispose }, not a function). Changelog entries added in both languages. * docs(agents): trim root AGENTS.md to keep inherited chains under the size limit * docs(extensions): scope the manual to the 0.2.1 contract Plugins and request-scoped run evidence are not in 2.1.x-dev yet; they move to a stacked follow-up so this change can be cherry-picked. * docs(extensions): cover full-stack plugins and request-scoped run evidence Brings the extension manual up to deerflow-extension-api 0.2.3. Stacked on the 0.2.1 manual so that part can be cherry-picked to 2.1.x-dev alone. * docs(extensions): clarify PAT rejection on contributed routes --------- Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
…ytedance#5769) * docs(extensions): add an eleven-page extension developer manual Add a harness/extensions/ section in English and Chinese documenting the deerflow-extension-api 0.2.3 contract: index when to write an extension vs a tool, MCP server or skill; contribution kinds; loading; failure and trust model; versioning quick-start build, test, install and remove a working extension runtime load sequence, API version rules, scopes and ExtensionData, fail-open and cancellation, diagnostics middleware placements and where each lands, scope, ordering, observe-only wrap hooks, failure isolation observers task lifecycle, system model calls, agent assembly and fingerprint, context compaction services-and-routes service lifecycle, router rejection rules, auth, principals run-evidence service and request-scoped readers (bytedance#5727), cursor semantics, deletions, redaction, store differences plugins experimental full-stack plugins, inline modules and manifest-based browser assets (bytedance#5685) operations extension manager CLI, sources, upgrade, rollback, Docker, Helm, recovery troubleshooting indexed by the exact log and error strings reference every public name and the contract version history Also fix stale descriptions: the contribution kinds listed in AGENTS.md and deerflow/extensions/AGENTS.md (now eight, including plugins), the run-evidence redaction claim (only auth_token is removed), and the plugin mount return value in docs/full-stack-plugins.md ({ dispose }, not a function). Changelog entries added in both languages. * docs(agents): trim root AGENTS.md to keep inherited chains under the size limit * docs(extensions): scope the manual to the 0.2.1 contract Plugins and request-scoped run evidence are not in 2.1.x-dev yet; they move to a stacked follow-up so this change can be cherry-picked. * docs(extensions): clarify PAT rejection on contributed routes

Follow-up to #5647; related to #5510 and #5539.
Why
The initial full-stack plugin API requires one self-contained browser module. A larger plugin cannot split its JavaScript or resolve CSS/images relative to
import.meta.url. This implements the manifest + static resource direction discussed in #5647 while retaining the existing page/action/tool contracts and single-file transport.What changed
BrowserAssets(module, root, manifest="ui_manifest.json")in extension-api 0.2.3, alongsideBrowserModule. A versioned manifest identifies the entry module and explicitly lists allowed files; deployment still owns installation and activation.nosniff, a document sandbox policy and private immutable caching. The sandbox restricts direct document navigation (including SVG); plugin scripts loaded into the host page remain trusted code. Traversal, symlinked roots/files, missing resources, unsupported formats and oversized packages are rejected. File/package limits are 4/16 MiB, with at most 256 files.assets-v1/inline-v1through discovery. Packaged entries load as native credentialed module graphs, supporting relative static/lazy imports without Blob URL rewriting, with explicit Webpack/Turbopack ignore markers. Unknown transports and loading failures are isolated per plugin; host waiting for loading/evaluation has a 30-second deadline. This cannot cancel native evaluation or later top-level side effects.The existing deployment/restart/manual-refresh lifecycle remains. Historical server snapshots are not retained; cached code is not instantly revoked on uninstall. Split-origin deployments require credentialed CORS/cookies, and plugins must handle credentials for their own non-JS resources. CSS font/background limitations and CSP requirements are documented in
docs/full-stack-plugins.md.Surface area
frontend/backend/applanggraph.json, or prompt changedocker/or sandboxed executionskills/Frontend changes concern module loading/discovery. There are no new host screens or controls; the existing bookmarks example exercises packaged CSS and an SVG in its page.
Validation
make format,make lint: passed.make test: 18,398 passed, 206 skipped, 14 failed. The same 14 browser/web-fetch address-guard failures reproduce on the unmodified base61a99a1e; they report private/loopback-address refusals in this environment.make test-blocking-io: 149 passed.pnpm format,pnpm check, full unit suite: 1,939 passed; production build passed.git diff --check: passed (2 soft-budget warnings). A wording-only Gateway guidance reduction keeps the latest-main merge view below its hard limit; all 13 guidance tests passed.AI assistance
Tool(s) used: Codex.
How you used it: Implementation, documentation, regression tests, local browser verification and PR preparation. Changes were inspected and validated against the existing plugin contracts; test limitations are reported above.