Commit Graph

17783 Commits

Author SHA1 Message Date
cb651c343a fix: More review comments addressed 2026-05-08 17:53:31 -07:00
1d7e366498 fix: Review sweep 2026-05-08 17:28:24 -07:00
42fc6ae507 feat: DuckDuckGo as Extension in new Bundle System (#13044)
* feat: DuckDuckGo Extension for bundles

* fix ruff errors

* Update test_pilot_duckduckgo_upgrade.py

* test(lfx): handle non-empty canonical migration table + editable installs

- test_path_override_bypasses_cache: mirror the cached canonical table
  into the temp file instead of asserting it's empty.  Drift in
  migration_table.json (the duckduckgo entries shipped with the B1
  pilot) no longer breaks the cache-bypass invariant.

- test_lfx_duckduckgo_ships_manifest: editable installs (pip install -e)
  surface only dist-info entries in dist.files, so we cannot use that
  path to find extension.json in the workspace venv.  Detect editable
  mode via direct_url.json's PEP 660 marker and fall through to walking
  the source tree; non-editable wheel installs still exercise the
  dist.files path the loader uses at runtime.

* chore: auto-bake note keys and regenerate backend locales/en.json [skip ci]

* fix(docker): copy src/bundles before uv sync so workspace bundles resolve

Each directory under ``src/bundles`` is a uv workspace member referenced by
``langflow-base`` (and the root project) as a path dependency.  The Docker
builders ran ``uv sync --no-install-project`` after copying only the
top-level pyproject.toml files, so resolution failed with
``Distribution not found at: file:///app/src/bundles/<name>``.

Copying the whole ``src/bundles`` tree (rather than enumerating each
bundle) means a new bundle dropped under that dir does not require a
Dockerfile edit.  The full ``./src`` copy a few lines later produces the
same final layer either way; this earlier copy just unblocks the
dependency-resolution sync.

Touched all builders that run a workspace-resolving uv sync:

  * docker/build_and_push.Dockerfile
  * docker/build_and_push_base.Dockerfile
  * docker/build_and_push_ep.Dockerfile
  * docker/build_and_push_with_extras.Dockerfile
  * docker/dev.Dockerfile (bind-mount instead of COPY)

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-05-08 17:14:38 -07:00
81be42add3 Revert "fix(docker): copy src/bundles before uv sync so workspace bundles resolve"
This reverts commit 5aa008a3cd.
2026-05-08 17:12:23 -07:00
5aa008a3cd fix(docker): copy src/bundles before uv sync so workspace bundles resolve
Each directory under ``src/bundles`` is a uv workspace member referenced by
``langflow-base`` (and the root project) as a path dependency.  The Docker
builders ran ``uv sync --no-install-project`` after copying only the
top-level pyproject.toml files, so resolution failed with
``Distribution not found at: file:///app/src/bundles/<name>``.

Copying the whole ``src/bundles`` tree (rather than enumerating each
bundle) means a new bundle dropped under that dir does not require a
Dockerfile edit.  The full ``./src`` copy a few lines later produces the
same final layer either way; this earlier copy just unblocks the
dependency-resolution sync.

Touched all builders that run a workspace-resolving uv sync:

  * docker/build_and_push.Dockerfile
  * docker/build_and_push_base.Dockerfile
  * docker/build_and_push_ep.Dockerfile
  * docker/build_and_push_with_extras.Dockerfile
  * docker/dev.Dockerfile (bind-mount instead of COPY)
2026-05-08 17:06:08 -07:00
01e4a0fa96 Update router.py 2026-05-08 17:01:51 -07:00
80385ac835 Update component_index.json 2026-05-08 16:49:28 -07:00
5ac159ed81 Update component_index.json 2026-05-08 16:48:06 -07:00
65c14bf077 fix: Review comments addressed 2026-05-08 16:46:30 -07:00
b92b3d6e77 feat: End to end bundle installation 2026-05-08 15:20:20 -07:00
8873fad536 feat(frontend): palette Bundle reload action + loading + toasts (#13025)
* feat(lfx): add extension manifest schema, validate CLI, and error formatter (LE-1014)

Foundation for the Bundle Separation iteration. Defines what a valid
extension.json looks like (Pydantic models + Draft 2020-12 JSON Schema),
ships the offline `lfx extension validate` command, and ships the single
`format_extension_error` function that every other extension-system module
will use to render structured errors.

What's in lfx.extension:

- `ExtensionManifest` / `BundleRef` / `LangflowCompat` Pydantic models with
  `extra="forbid"` so unknown fields fail loudly. Deferred fields (`services`,
  `routes`, `hooks`, `starter_projects`, `userConfig`) are reserved as
  None-only so non-null values produce a dedicated
  `field-deferred-in-this-milestone` error instead of a generic schema wall.
  `bundles` accepts a list but rejects length > 1 with
  `multi-bundle-deferred-in-this-milestone` (validator-enforced; the loader
  re-checks at install time in LE-1015).
- `schema.build_schema()` + `build_schema_json()` produce the publishable
  artifact at schemas.langflow.org/extension/v1.json.
- `ExtensionError` typed envelope and `format_extension_error` -- one branch
  per discriminant. Codes are registered in `ERROR_CODES`; an
  `ExtensionError` constructed with an unknown code raises at construction
  time, preventing producers from shipping without a matching renderer.
- `validate_extension` runs four passes: manifest discovery + schema,
  path-safety (no `..`, no absolute paths, no symlink escape), AST
  inspection of every `.py` (syntax, Component subclass present, build()
  declared, top-level `import *`, top-level I/O primitives), and an opt-in
  `--execute-imports` that runs each module in a subprocess with a
  temporary HOME / TMPDIR / LANGFLOW_CONFIG_DIR and LANGFLOW_*/LFX_* env
  vars stripped.
- `lfx extension validate` and `lfx extension schema` typer subcommands.

Acceptance-criteria coverage in tests/unit/extension/:

- Round-trips every v0 manifest field; deferred fields rejected; multi-bundle
  rejected with the dedicated discriminant.
- JSON Schema validates the v0 example and rejects 12 malformed manifests
  with distinct error paths (>= 10 required by the ticket).
- Median default-validate runtime < 100ms on the basic template.
- Crafted side-effect bundle: default validate does NOT execute it (canary
  file is never written); `--execute-imports` DOES execute it and the canary
  appears, while LANGFLOW_* env vars are NOT inherited by the subprocess.
- Snapshot tests for every code in ERROR_CODES; a guard test verifies
  ERROR_CODES and the snapshot table are in lockstep so future additions
  cannot ship without a format branch and a snapshot.

Wiring: `lfx extension` is a sub-app under the Authoring help panel so
future tickets (LE-1016 init/dev, LE-1018 reload) can attach without
colliding with the existing `lfx validate` (which validates flow JSON, not
extensions).

* fix(lfx): fall back to tomli on Python 3.10 in extension manifest loader

tomllib is stdlib only on 3.11+, but lfx supports 3.10-3.13. Use the
existing tomli runtime dependency as the 3.10 fallback (same API, so the
import alias keeps the rest of the module unchanged).

Fixes ModuleNotFoundError seen in CI on the 3.10 job.

* Update src/lfx/tests/unit/extension/test_schema.py

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update src/lfx/src/lfx/extension/schema.py

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update src/lfx/src/lfx/cli/_extension_commands.py

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* feat(lfx): add single-Bundle loader and LANGFLOW_COMPONENTS_PATH discovery (LE-1015)

Introduces lfx.extension.loader: the runtime that turns an Extension on disk
into LoadedComponent records keyed by ext:<bundle>:<Class>@<slot>. Two paths in:

  - load_extension(root): one manifest, one Bundle, registered at @official.
    Re-checks multi-bundle at runtime (defense-in-depth vs. the schema).
  - discover_inline_bundles(paths): each subfolder of LANGFLOW_COMPONENTS_PATH
    is a Bundle at @extra. Walk order is platform-independent (sorted dirs,
    user-declared path order). First-wins on duplicate names; second emits
    duplicate-inline-bundle warning that names both paths.

Manifest-first precedence helpers (installed_extension_roots,
manifest_owning_distributions, filter_plugin_entry_points) let callers of
the legacy langflow.plugins entry-point loader skip distributions that ship
a manifest, so component entry-points are not double-registered.

New typed error codes: module-import-failed, duplicate-component-name,
duplicate-distribution, duplicate-inline-bundle, inline-bundle-name-invalid.
Each ships with a format branch and a snapshot test.

Tests cover the AC: single-bundle happy path, multi-bundle rejection,
missing/empty/no-Component bundle, duplicate class names, deterministic
walk order, recursive discovery, inline-bundle first-wins + dot-dir skip
+ bundle.json metadata, manifest-first precedence partition, PEP-503
distribution-name canonicalization.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(lfx): address LE-1015 review feedback

- Drop the unproduced duplicate-distribution error code; LE-1022 will add
  it once startup-time discovery has a place to surface it.  Restores the
  invariant that every code in ERROR_CODES has a producer.
- Clarify that intra-bundle relative imports are NOT supported in v0; only
  absolute references between bundle modules work in this milestone.
- Use strict=False when re-resolving bundle_root in the walker; the path
  was already existence-checked, and a concurrent removal in the narrow
  window should not raise across the loader's public boundary.
- Hoist the json import out of _read_inline_bundle_json's body.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(lfx): split extension loader into a small subpackage (LE-1015)

Addresses the file-size feedback from the LE-1015 review.  The single
870-line loader.py becomes a flat package keyed off the four section
banners that already existed inline:

    loader/
      __init__.py     # re-exports the public surface
      _types.py       # SLOT constants, LoadedComponent, LoadResult
      _discovery.py   # filesystem walk + importlib.util orchestration
      _detection.py   # Component subclass identification (MRO heuristic)
      _orchestrator.py # load_extension, discover_inline_bundles
      _plugins.py     # manifest-first precedence over langflow.plugins

Largest file is now _orchestrator.py at 440 LOC (was 870); every file is
well under the 800-LOC project guideline.  No behavior change: the public
import paths from lfx.extension are unchanged, all 38 loader tests still
pass.  ``_canonicalize_distribution`` is now exported as
``canonicalize_distribution`` from ``loader._plugins`` (it's a stable
PEP-503 helper that downstream modules will reach for, so it loses the
private underscore).

The test suite is split to mirror the package:

    tests/unit/extension/loader/
      conftest.py             # shared fixtures, FakeDist, autouse scrub
      test_load_extension.py  # @official slot, identity, failure modes
      test_inline_bundles.py  # LANGFLOW_COMPONENTS_PATH, @extra slot
      test_plugins.py         # manifest-first precedence helpers
      test_types.py           # LoadedComponent, LoadResult, code parity

Each test file imports its own slice of the public API and pulls fixtures
from conftest, so a reader looking at one banner can read it in isolation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(lfx): atomic-swap Bundle reload pipeline + endpoint + CLI (LE-1018)

Five-stage reload (parallel staging load -> validate -> swap under write
lock -> cleanup -> emit) for installed Bundles in Mode A.  In-flight
flows keep the pre-swap class via existing references; new flows pick up
the post-swap class atomically; concurrent reloads on the same Bundle
are rejected with reload-in-progress.

Adds:

* lfx/extension/registry.py -- BundleRegistry with per-bundle
  reload-in-progress guard and components_index.json writer
* lfx/extension/reload.py -- the five-stage pipeline; events emission
  is stubbed (TODO LE-1017) so the swap mechanics can ship before the
  events service lands
* loader: optional module_namespace param so Stage 1 lands in
  __reload_staging__.<id> instead of the live _lfx_ext.* namespace
* errors: four new typed reload codes (reload-in-progress,
  reload-bundle-not-installed, reload-bundle-name-mismatch,
  reload-source-missing) with branch templates and snapshot tests
* HTTP: POST /api/v1/extensions/{id}/bundles/{name}/reload, gated by
  the existing get_current_active_user dependency, returns 409 with a
  typed body for the in-progress collision case
* CLI: lfx extension reload <id> [--bundle <name>] -- HTTP client
  against the dev server with text/json output and proper exit codes
  (--all is gated until LE-1019 lands the list endpoint)
* tests: 16 reload-pipeline tests covering the AC matrix (rename
  round-trip, broken-bundle isolation, concurrent readers, in-flight
  flow, double-reload guard, bundle-name mismatch) plus 9 CLI client
  tests

Mode A only.  In Mode B/C bundle changes require a Docker image rebuild
and the reload path is not exercised.

The events emission in Stage 5 is intentionally stubbed -- the LE-1017
ticket will swap the body of _emit_bundle_reload_event in one place
without touching the pipeline core.

* feat(frontend): palette Bundle reload action + loading + toasts

Adds the frontend half of the bundle reload flow.  When the Bundle
header is right-clicked or its overflow ("⋮") icon clicked, a Reload
action fires POST /api/v1/extensions/{id}/bundles/{name}/reload and
surfaces the result via the existing alert-store toast system.

What landed (frontend only):

  * src/controllers/API/queries/extensions/ -- typed wire-format models
    (ReloadBundleResponse, ExtensionErrorPayload, ReloadInProgressDetail)
    and the useReloadBundle mutation hook.  The hook unwraps the 409
    `reload-in-progress` detail into a stable, parseable Error message so
    the UI can branch without reading status codes.
  * components/bundleHeaderActions.tsx -- new Select-based overflow menu
    next to the Bundle header chevron.  Three toast paths: success
    (green, with components +/- delta), structural failure (red, with
    typed errors and inline hints), reload-in-progress (notice).
    Loading state swaps the kebab icon for a spinning Loader2 while
    the request is in flight.  Renders nothing when no extension_id is
    on the bundle, so the static SIDEBAR_BUNDLES list is unaffected.
  * components/bundleItems.tsx -- wires the new actions in next to the
    chevron, plus a context-menu (right-click) capture that opens the
    same overflow trigger so keyboard / mouse / right-click all share
    one source of truth.
  * types/index.ts -- BundleItemProps.item gains an optional
    extension_id; took the opportunity to extract the SidebarBundle
    interface and tighten three pre-existing `any` types.
  * customization/feature-flags.ts -- ENABLE_EXTENSION_RELOAD gate, off
    by default until the bundle-list endpoint that populates extension_id
    per bundle ships.
  * controllers/API/helpers/constants.ts -- EXTENSIONS URL constant.
  * Tests: 7 component tests + 3 mutation-hook tests, all green.  Total
    sidebar + extensions test count: 479 passing.

Deferred:

  * Event-pipeline subscription is left as an inline TODO.  The mutation
    response carries enough information to drive the toasts on its own
    today; once the events service lands the toast wiring will move to
    a `bundle_reloaded` / `bundle_reload_failed` listener so multi-tab
    and multi-worker swaps surface exactly once.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 14:52:52 -07:00
77e7a93553 feat(lfx): append-only migration table + flow deserializer rewrite hook (#13024)
* feat(lfx): add extension manifest schema, validate CLI, and error formatter (LE-1014)

Foundation for the Bundle Separation iteration. Defines what a valid
extension.json looks like (Pydantic models + Draft 2020-12 JSON Schema),
ships the offline `lfx extension validate` command, and ships the single
`format_extension_error` function that every other extension-system module
will use to render structured errors.

What's in lfx.extension:

- `ExtensionManifest` / `BundleRef` / `LangflowCompat` Pydantic models with
  `extra="forbid"` so unknown fields fail loudly. Deferred fields (`services`,
  `routes`, `hooks`, `starter_projects`, `userConfig`) are reserved as
  None-only so non-null values produce a dedicated
  `field-deferred-in-this-milestone` error instead of a generic schema wall.
  `bundles` accepts a list but rejects length > 1 with
  `multi-bundle-deferred-in-this-milestone` (validator-enforced; the loader
  re-checks at install time in LE-1015).
- `schema.build_schema()` + `build_schema_json()` produce the publishable
  artifact at schemas.langflow.org/extension/v1.json.
- `ExtensionError` typed envelope and `format_extension_error` -- one branch
  per discriminant. Codes are registered in `ERROR_CODES`; an
  `ExtensionError` constructed with an unknown code raises at construction
  time, preventing producers from shipping without a matching renderer.
- `validate_extension` runs four passes: manifest discovery + schema,
  path-safety (no `..`, no absolute paths, no symlink escape), AST
  inspection of every `.py` (syntax, Component subclass present, build()
  declared, top-level `import *`, top-level I/O primitives), and an opt-in
  `--execute-imports` that runs each module in a subprocess with a
  temporary HOME / TMPDIR / LANGFLOW_CONFIG_DIR and LANGFLOW_*/LFX_* env
  vars stripped.
- `lfx extension validate` and `lfx extension schema` typer subcommands.

Acceptance-criteria coverage in tests/unit/extension/:

- Round-trips every v0 manifest field; deferred fields rejected; multi-bundle
  rejected with the dedicated discriminant.
- JSON Schema validates the v0 example and rejects 12 malformed manifests
  with distinct error paths (>= 10 required by the ticket).
- Median default-validate runtime < 100ms on the basic template.
- Crafted side-effect bundle: default validate does NOT execute it (canary
  file is never written); `--execute-imports` DOES execute it and the canary
  appears, while LANGFLOW_* env vars are NOT inherited by the subprocess.
- Snapshot tests for every code in ERROR_CODES; a guard test verifies
  ERROR_CODES and the snapshot table are in lockstep so future additions
  cannot ship without a format branch and a snapshot.

Wiring: `lfx extension` is a sub-app under the Authoring help panel so
future tickets (LE-1016 init/dev, LE-1018 reload) can attach without
colliding with the existing `lfx validate` (which validates flow JSON, not
extensions).

* fix(lfx): fall back to tomli on Python 3.10 in extension manifest loader

tomllib is stdlib only on 3.11+, but lfx supports 3.10-3.13. Use the
existing tomli runtime dependency as the 3.10 fallback (same API, so the
import alias keeps the rest of the module unchanged).

Fixes ModuleNotFoundError seen in CI on the 3.10 job.

* Update src/lfx/tests/unit/extension/test_schema.py

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update src/lfx/src/lfx/extension/schema.py

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update src/lfx/src/lfx/cli/_extension_commands.py

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* feat(lfx): add single-Bundle loader and LANGFLOW_COMPONENTS_PATH discovery (LE-1015)

Introduces lfx.extension.loader: the runtime that turns an Extension on disk
into LoadedComponent records keyed by ext:<bundle>:<Class>@<slot>. Two paths in:

  - load_extension(root): one manifest, one Bundle, registered at @official.
    Re-checks multi-bundle at runtime (defense-in-depth vs. the schema).
  - discover_inline_bundles(paths): each subfolder of LANGFLOW_COMPONENTS_PATH
    is a Bundle at @extra. Walk order is platform-independent (sorted dirs,
    user-declared path order). First-wins on duplicate names; second emits
    duplicate-inline-bundle warning that names both paths.

Manifest-first precedence helpers (installed_extension_roots,
manifest_owning_distributions, filter_plugin_entry_points) let callers of
the legacy langflow.plugins entry-point loader skip distributions that ship
a manifest, so component entry-points are not double-registered.

New typed error codes: module-import-failed, duplicate-component-name,
duplicate-distribution, duplicate-inline-bundle, inline-bundle-name-invalid.
Each ships with a format branch and a snapshot test.

Tests cover the AC: single-bundle happy path, multi-bundle rejection,
missing/empty/no-Component bundle, duplicate class names, deterministic
walk order, recursive discovery, inline-bundle first-wins + dot-dir skip
+ bundle.json metadata, manifest-first precedence partition, PEP-503
distribution-name canonicalization.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(lfx): address LE-1015 review feedback

- Drop the unproduced duplicate-distribution error code; LE-1022 will add
  it once startup-time discovery has a place to surface it.  Restores the
  invariant that every code in ERROR_CODES has a producer.
- Clarify that intra-bundle relative imports are NOT supported in v0; only
  absolute references between bundle modules work in this milestone.
- Use strict=False when re-resolving bundle_root in the walker; the path
  was already existence-checked, and a concurrent removal in the narrow
  window should not raise across the loader's public boundary.
- Hoist the json import out of _read_inline_bundle_json's body.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(lfx): split extension loader into a small subpackage (LE-1015)

Addresses the file-size feedback from the LE-1015 review.  The single
870-line loader.py becomes a flat package keyed off the four section
banners that already existed inline:

    loader/
      __init__.py     # re-exports the public surface
      _types.py       # SLOT constants, LoadedComponent, LoadResult
      _discovery.py   # filesystem walk + importlib.util orchestration
      _detection.py   # Component subclass identification (MRO heuristic)
      _orchestrator.py # load_extension, discover_inline_bundles
      _plugins.py     # manifest-first precedence over langflow.plugins

Largest file is now _orchestrator.py at 440 LOC (was 870); every file is
well under the 800-LOC project guideline.  No behavior change: the public
import paths from lfx.extension are unchanged, all 38 loader tests still
pass.  ``_canonicalize_distribution`` is now exported as
``canonicalize_distribution`` from ``loader._plugins`` (it's a stable
PEP-503 helper that downstream modules will reach for, so it loses the
private underscore).

The test suite is split to mirror the package:

    tests/unit/extension/loader/
      conftest.py             # shared fixtures, FakeDist, autouse scrub
      test_load_extension.py  # @official slot, identity, failure modes
      test_inline_bundles.py  # LANGFLOW_COMPONENTS_PATH, @extra slot
      test_plugins.py         # manifest-first precedence helpers
      test_types.py           # LoadedComponent, LoadResult, code parity

Each test file imports its own slice of the public API and pulls fixtures
from conftest, so a reader looking at one banner can read it in isolation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: LangflowCompat -> LfxCompat

* fix: Remove references to ticket

* fix: encode maxItems constraint in schema

* fix: deferred fields and schema version

* Fix ruff errors

* fix: align loader tests with renamed manifest API and strip ticket refs

The merge of feat/extension-validate brought in the LfxCompat / compat
rename, but the loader test fixtures still used LangflowCompat / bundle_api
and failed at the manifest layer. Update conftest.py + test_load_extension.py
to the post-rename API.

Strip 17 LE-XXXX ticket references from loader source files, errors.py
loader-specific comments, and the four loader test docstrings, matching the
convention applied to LE-1014. Descriptive prose preserved.

Promote _BUNDLE_NAME_RE to BUNDLE_NAME_RE on the manifest module so the
loader's orchestrator no longer reaches across modules into a private name.

* feat(lfx): append-only migration table + flow deserializer rewrite hook

Adds the migration layer of the Extension System: an append-only JSON table
that maps three legacy component reference shapes (bare class name, old
import path, pre-Phase-A namespaced slot) to the post-Phase-A canonical
ext:<bundle>:<Class>@<slot> identifier, plus a deserializer hook that
rewrites a saved-flow payload in place against that table on load.

What landed:

  * lfx.extension.migration.schema -- Pydantic models for MigrationEntry +
    MigrationTable with per-entry validators (exactly one of bare/import/slot
    populated; canonical target shape) and table-level uniqueness check.
  * lfx.extension.migration.loader -- canonical in-repo path, threadsafe
    process-lifetime cache, typed errors on every failure mode.
  * lfx.extension.migration.rewrite -- node-by-node rewrite, idempotent on
    canonical refs, difflib-backed closest-match suggestion for unmapped
    references, cross-bucket ambiguity surfaces component-name-ambiguous
    instead of silently loading into the wrong bundle.
  * Wired into Graph.from_payload before validate_flow_for_current_settings
    so every saved-flow load goes through migration first.
  * scripts/migrate/check_migration_append_only.py -- CI guard that diffs
    the working-tree table against origin/main and rejects removals or
    target mutations; reordering and additions are allowed.
  * 34 new unit tests covering rewrite paths, loader failure modes, schema
    invariants, and the CI script behavior.

What is deliberately deferred:

  * flow-migrated event emission. The events pipeline is unavailable in this
    iteration; the wiring point in Graph.from_payload is marked TODO and the
    MigrationReport already carries every field a future emitter needs.

The shipped migration_table.json starts empty; entries land alongside the
pilot bundle extraction in a follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 14:48:51 -07:00
6da7fb151e feat(lfx): add extension init and extension dev CLIs (LE-1016) (#12968)
* feat(lfx): add extension manifest schema, validate CLI, and error formatter (LE-1014)

Foundation for the Bundle Separation iteration. Defines what a valid
extension.json looks like (Pydantic models + Draft 2020-12 JSON Schema),
ships the offline `lfx extension validate` command, and ships the single
`format_extension_error` function that every other extension-system module
will use to render structured errors.

What's in lfx.extension:

- `ExtensionManifest` / `BundleRef` / `LangflowCompat` Pydantic models with
  `extra="forbid"` so unknown fields fail loudly. Deferred fields (`services`,
  `routes`, `hooks`, `starter_projects`, `userConfig`) are reserved as
  None-only so non-null values produce a dedicated
  `field-deferred-in-this-milestone` error instead of a generic schema wall.
  `bundles` accepts a list but rejects length > 1 with
  `multi-bundle-deferred-in-this-milestone` (validator-enforced; the loader
  re-checks at install time in LE-1015).
- `schema.build_schema()` + `build_schema_json()` produce the publishable
  artifact at schemas.langflow.org/extension/v1.json.
- `ExtensionError` typed envelope and `format_extension_error` -- one branch
  per discriminant. Codes are registered in `ERROR_CODES`; an
  `ExtensionError` constructed with an unknown code raises at construction
  time, preventing producers from shipping without a matching renderer.
- `validate_extension` runs four passes: manifest discovery + schema,
  path-safety (no `..`, no absolute paths, no symlink escape), AST
  inspection of every `.py` (syntax, Component subclass present, build()
  declared, top-level `import *`, top-level I/O primitives), and an opt-in
  `--execute-imports` that runs each module in a subprocess with a
  temporary HOME / TMPDIR / LANGFLOW_CONFIG_DIR and LANGFLOW_*/LFX_* env
  vars stripped.
- `lfx extension validate` and `lfx extension schema` typer subcommands.

Acceptance-criteria coverage in tests/unit/extension/:

- Round-trips every v0 manifest field; deferred fields rejected; multi-bundle
  rejected with the dedicated discriminant.
- JSON Schema validates the v0 example and rejects 12 malformed manifests
  with distinct error paths (>= 10 required by the ticket).
- Median default-validate runtime < 100ms on the basic template.
- Crafted side-effect bundle: default validate does NOT execute it (canary
  file is never written); `--execute-imports` DOES execute it and the canary
  appears, while LANGFLOW_* env vars are NOT inherited by the subprocess.
- Snapshot tests for every code in ERROR_CODES; a guard test verifies
  ERROR_CODES and the snapshot table are in lockstep so future additions
  cannot ship without a format branch and a snapshot.

Wiring: `lfx extension` is a sub-app under the Authoring help panel so
future tickets (LE-1016 init/dev, LE-1018 reload) can attach without
colliding with the existing `lfx validate` (which validates flow JSON, not
extensions).

* fix(lfx): fall back to tomli on Python 3.10 in extension manifest loader

tomllib is stdlib only on 3.11+, but lfx supports 3.10-3.13. Use the
existing tomli runtime dependency as the 3.10 fallback (same API, so the
import alias keeps the rest of the module unchanged).

Fixes ModuleNotFoundError seen in CI on the 3.10 job.

* Update src/lfx/tests/unit/extension/test_schema.py

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update src/lfx/src/lfx/extension/schema.py

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update src/lfx/src/lfx/cli/_extension_commands.py

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* feat(lfx): add single-Bundle loader and LANGFLOW_COMPONENTS_PATH discovery (LE-1015)

Introduces lfx.extension.loader: the runtime that turns an Extension on disk
into LoadedComponent records keyed by ext:<bundle>:<Class>@<slot>. Two paths in:

  - load_extension(root): one manifest, one Bundle, registered at @official.
    Re-checks multi-bundle at runtime (defense-in-depth vs. the schema).
  - discover_inline_bundles(paths): each subfolder of LANGFLOW_COMPONENTS_PATH
    is a Bundle at @extra. Walk order is platform-independent (sorted dirs,
    user-declared path order). First-wins on duplicate names; second emits
    duplicate-inline-bundle warning that names both paths.

Manifest-first precedence helpers (installed_extension_roots,
manifest_owning_distributions, filter_plugin_entry_points) let callers of
the legacy langflow.plugins entry-point loader skip distributions that ship
a manifest, so component entry-points are not double-registered.

New typed error codes: module-import-failed, duplicate-component-name,
duplicate-distribution, duplicate-inline-bundle, inline-bundle-name-invalid.
Each ships with a format branch and a snapshot test.

Tests cover the AC: single-bundle happy path, multi-bundle rejection,
missing/empty/no-Component bundle, duplicate class names, deterministic
walk order, recursive discovery, inline-bundle first-wins + dot-dir skip
+ bundle.json metadata, manifest-first precedence partition, PEP-503
distribution-name canonicalization.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(lfx): address LE-1015 review feedback

- Drop the unproduced duplicate-distribution error code; LE-1022 will add
  it once startup-time discovery has a place to surface it.  Restores the
  invariant that every code in ERROR_CODES has a producer.
- Clarify that intra-bundle relative imports are NOT supported in v0; only
  absolute references between bundle modules work in this milestone.
- Use strict=False when re-resolving bundle_root in the walker; the path
  was already existence-checked, and a concurrent removal in the narrow
  window should not raise across the loader's public boundary.
- Hoist the json import out of _read_inline_bundle_json's body.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(lfx): split extension loader into a small subpackage (LE-1015)

Addresses the file-size feedback from the LE-1015 review.  The single
870-line loader.py becomes a flat package keyed off the four section
banners that already existed inline:

    loader/
      __init__.py     # re-exports the public surface
      _types.py       # SLOT constants, LoadedComponent, LoadResult
      _discovery.py   # filesystem walk + importlib.util orchestration
      _detection.py   # Component subclass identification (MRO heuristic)
      _orchestrator.py # load_extension, discover_inline_bundles
      _plugins.py     # manifest-first precedence over langflow.plugins

Largest file is now _orchestrator.py at 440 LOC (was 870); every file is
well under the 800-LOC project guideline.  No behavior change: the public
import paths from lfx.extension are unchanged, all 38 loader tests still
pass.  ``_canonicalize_distribution`` is now exported as
``canonicalize_distribution`` from ``loader._plugins`` (it's a stable
PEP-503 helper that downstream modules will reach for, so it loses the
private underscore).

The test suite is split to mirror the package:

    tests/unit/extension/loader/
      conftest.py             # shared fixtures, FakeDist, autouse scrub
      test_load_extension.py  # @official slot, identity, failure modes
      test_inline_bundles.py  # LANGFLOW_COMPONENTS_PATH, @extra slot
      test_plugins.py         # manifest-first precedence helpers
      test_types.py           # LoadedComponent, LoadResult, code parity

Each test file imports its own slice of the public API and pulls fixtures
from conftest, so a reader looking at one banner can read it in isolation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(lfx): add `extension init` and `extension dev` CLIs (LE-1016)

The two scaffolding CLIs an Extension author types:

  - `lfx extension init <target>` writes the basic single-Bundle
    template (manifest with $schema, README, .gitignore, one Component
    subclass + a pytest smoke test).  AC #1: the generated extension
    validates clean against LE-1014.  AC #2: the generated test file is
    a valid pytest module that exercises the component's build() method.
    AC #3: any --template other than 'basic' fails with a typed
    template-deferred-in-this-milestone error and a non-zero exit.
    Refuses to scaffold over a non-empty target dir.

  - `lfx extension dev <target>` validates the local extension, records
    its absolute path in <config_dir>/extensions/dev_extensions.json,
    prints reload instructions, and execs `langflow run` (or
    `python -m langflow` when langflow isn't on PATH).  --skip-launch
    registers without launching (used by tests + external dev-server
    scripts); --skip-validate lets authors register a known-broken
    manifest to debug it under the loader.

Stack:
  - Wave 0: error codes (extension-target-exists,
    extension-target-invalid, local-extension-missing) with format
    branches and snapshot tests.
  - Wave 1: lfx.extension.init_template -- pure-data scaffolder, no
    Typer dependency so the CLI is a thin shell over it.
  - Wave 1: lfx.extension.dev_registry -- atomic JSON state file under
    the langflow user-cache dir; helpers for register / list /
    unregister / load_dev_extensions / dev_extension_component_paths.
  - Wave 2: lfx.cli._extension_commands gains init/dev subcommands.
  - Wave 2: langflow.main lifespan hook reads the dev registry after
    bundle loading and extends components_path with each registered
    bundle dir, so the existing palette discovery picks up dev
    extensions.  Missing paths surface as local-extension-missing
    warnings (AC #5) without aborting startup.

Tests (84 new, 197 total in tests/unit/extension/):
  - test_init_template.py: AC scenarios + identifier derivation +
    deterministic file shape.
  - test_dev_registry.py: register/list/unregister round-trip,
    idempotent re-register refreshes timestamp, malformed state file
    treated as empty, missing-path warning, recovery when path
    reappears, env-var override precedence.
  - test_cli.py: AC #1 init->validate, AC #3 deferred templates,
    --skip-launch registers without launching, --skip-validate
    short-circuits the pre-flight pass.
  - test_errors.py: snapshot rows for all three new codes; the
    every-known-code-has-a-snapshot test enforces parity.

LE-1018 (reload) reuses load_dev_extensions; LE-1022 (installed-pkg
discovery) shares the components_path extension pattern.  AC #4 ("boots
Langflow with the extension visible in the palette within 5s") is
delivered jointly by `extension dev` (registers + execs) and the
lifespan hook (loads on startup).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(lfx): address LE-1016 review feedback

Fixes the four HIGH issues + the elevated LOW from the review pass:

1. dev_extension_component_paths now forwards EVERY warning, not just
   local-extension-missing.  Previously a duplicate-component-name (or
   any future warning code) was silently dropped, hiding real signal
   from the lifespan hook's logs.

2. Defensive emit when a LoadResult has components but source_path is
   None.  The current loader always sets source_path, but a future
   hand-built LoadResult could violate that contract; we now surface a
   typed local-extension-missing error rather than dropping the
   extension silently.

3. Replaced the fragile ``min(len(parts))`` bundle-root selection with
   a relative-to-source-path measurement.  Handles deep-vs-shallow
   sibling extensions correctly without depending on absolute path
   depth.

4. Generated README now documents the langflow/lfx prerequisite under
   the Develop section so authors know `pytest` requires the lfx
   environment, not just Python.

5. Forced LANGFLOW_LAZY_LOAD_COMPONENTS=false unconditionally in the
   `extension dev` exec env (was setdefault, which let a developer's
   global lazy-loading export silently hide their dev components from
   the palette and miss AC #4's 5s budget).

Plus three MEDIUMs:

  - sys.modules cleanup in test_generated_test_file_runs_against_generated_component
    so a later test importing the same dotted path doesn't pick up a
    stale module from a deleted tmp_path.  Component instantiation
    moved inside the try block because Component.__init__ uses
    inspect.getsourcefile against self.__class__'s still-live module.
  - Added regex-drift test that pins the init_template patterns to
    match manifest.py's so a schema regex change can't quietly produce
    invalid scaffolded manifests.
  - Added two new dev_registry tests covering the forward-all-warnings
    contract and the source_path=None defense.

Tests: 201 passing (4 new); ruff check + format clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: LangflowCompat -> LfxCompat

* fix: Remove references to ticket

* fix: encode maxItems constraint in schema

* fix: deferred fields and schema version

* Fix ruff errors

* fix: align loader tests with renamed manifest API and strip ticket refs

The merge of feat/extension-validate brought in the LfxCompat / compat
rename, but the loader test fixtures still used LangflowCompat / bundle_api
and failed at the manifest layer. Update conftest.py + test_load_extension.py
to the post-rename API.

Strip 17 LE-XXXX ticket references from loader source files, errors.py
loader-specific comments, and the four loader test docstrings, matching the
convention applied to LE-1014. Descriptive prose preserved.

Promote _BUNDLE_NAME_RE to BUNDLE_NAME_RE on the manifest module so the
loader's orchestrator no longer reaches across modules into a private name.

* fix(lfx): align init template with renamed manifest API

After merging feat/extension-loader into this branch, two surfaces
fell out of sync with the renamed manifest schema:

- init_template generates extension.json with `lfx: {bundle_api: [1]}`,
  but the validator now requires `lfx: {compat: ["1"]}` per LfxCompat.
  This made `test_basic_template_validates_clean` fail with
  manifest-invalid (`lfx.compat: Field required; lfx.bundle_api: Extra
  inputs are not permitted`).
- test_init_template_regexes_match_manifest_schema reads
  `manifest_mod._BUNDLE_NAME_RE`, but that symbol was promoted to public
  `BUNDLE_NAME_RE` in c63f84a591. Update the test to reference the
  public name; init_template's local copy is still private since it
  also covers `_EXTENSION_ID_RE`, which remains private upstream.

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 14:44:18 -07:00
557993f4b4 [autofix.ci] apply automated fixes 2026-05-08 21:43:23 +00:00
882dc4978a feat(lfx): atomic-swap Bundle reload pipeline + endpoint + CLI (LE-1018) (#12979)
* feat(lfx): add single-Bundle loader and LANGFLOW_COMPONENTS_PATH discovery (LE-1015)

Introduces lfx.extension.loader: the runtime that turns an Extension on disk
into LoadedComponent records keyed by ext:<bundle>:<Class>@<slot>. Two paths in:

  - load_extension(root): one manifest, one Bundle, registered at @official.
    Re-checks multi-bundle at runtime (defense-in-depth vs. the schema).
  - discover_inline_bundles(paths): each subfolder of LANGFLOW_COMPONENTS_PATH
    is a Bundle at @extra. Walk order is platform-independent (sorted dirs,
    user-declared path order). First-wins on duplicate names; second emits
    duplicate-inline-bundle warning that names both paths.

Manifest-first precedence helpers (installed_extension_roots,
manifest_owning_distributions, filter_plugin_entry_points) let callers of
the legacy langflow.plugins entry-point loader skip distributions that ship
a manifest, so component entry-points are not double-registered.

New typed error codes: module-import-failed, duplicate-component-name,
duplicate-distribution, duplicate-inline-bundle, inline-bundle-name-invalid.
Each ships with a format branch and a snapshot test.

Tests cover the AC: single-bundle happy path, multi-bundle rejection,
missing/empty/no-Component bundle, duplicate class names, deterministic
walk order, recursive discovery, inline-bundle first-wins + dot-dir skip
+ bundle.json metadata, manifest-first precedence partition, PEP-503
distribution-name canonicalization.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(lfx): address LE-1015 review feedback

- Drop the unproduced duplicate-distribution error code; LE-1022 will add
  it once startup-time discovery has a place to surface it.  Restores the
  invariant that every code in ERROR_CODES has a producer.
- Clarify that intra-bundle relative imports are NOT supported in v0; only
  absolute references between bundle modules work in this milestone.
- Use strict=False when re-resolving bundle_root in the walker; the path
  was already existence-checked, and a concurrent removal in the narrow
  window should not raise across the loader's public boundary.
- Hoist the json import out of _read_inline_bundle_json's body.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(lfx): split extension loader into a small subpackage (LE-1015)

Addresses the file-size feedback from the LE-1015 review.  The single
870-line loader.py becomes a flat package keyed off the four section
banners that already existed inline:

    loader/
      __init__.py     # re-exports the public surface
      _types.py       # SLOT constants, LoadedComponent, LoadResult
      _discovery.py   # filesystem walk + importlib.util orchestration
      _detection.py   # Component subclass identification (MRO heuristic)
      _orchestrator.py # load_extension, discover_inline_bundles
      _plugins.py     # manifest-first precedence over langflow.plugins

Largest file is now _orchestrator.py at 440 LOC (was 870); every file is
well under the 800-LOC project guideline.  No behavior change: the public
import paths from lfx.extension are unchanged, all 38 loader tests still
pass.  ``_canonicalize_distribution`` is now exported as
``canonicalize_distribution`` from ``loader._plugins`` (it's a stable
PEP-503 helper that downstream modules will reach for, so it loses the
private underscore).

The test suite is split to mirror the package:

    tests/unit/extension/loader/
      conftest.py             # shared fixtures, FakeDist, autouse scrub
      test_load_extension.py  # @official slot, identity, failure modes
      test_inline_bundles.py  # LANGFLOW_COMPONENTS_PATH, @extra slot
      test_plugins.py         # manifest-first precedence helpers
      test_types.py           # LoadedComponent, LoadResult, code parity

Each test file imports its own slice of the public API and pulls fixtures
from conftest, so a reader looking at one banner can read it in isolation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(lfx): atomic-swap Bundle reload pipeline + endpoint + CLI (LE-1018)

Five-stage reload (parallel staging load -> validate -> swap under write
lock -> cleanup -> emit) for installed Bundles in Mode A.  In-flight
flows keep the pre-swap class via existing references; new flows pick up
the post-swap class atomically; concurrent reloads on the same Bundle
are rejected with reload-in-progress.

Adds:

* lfx/extension/registry.py -- BundleRegistry with per-bundle
  reload-in-progress guard and components_index.json writer
* lfx/extension/reload.py -- the five-stage pipeline; events emission
  is stubbed (TODO LE-1017) so the swap mechanics can ship before the
  events service lands
* loader: optional module_namespace param so Stage 1 lands in
  __reload_staging__.<id> instead of the live _lfx_ext.* namespace
* errors: four new typed reload codes (reload-in-progress,
  reload-bundle-not-installed, reload-bundle-name-mismatch,
  reload-source-missing) with branch templates and snapshot tests
* HTTP: POST /api/v1/extensions/{id}/bundles/{name}/reload, gated by
  the existing get_current_active_user dependency, returns 409 with a
  typed body for the in-progress collision case
* CLI: lfx extension reload <id> [--bundle <name>] -- HTTP client
  against the dev server with text/json output and proper exit codes
  (--all is gated until LE-1019 lands the list endpoint)
* tests: 16 reload-pipeline tests covering the AC matrix (rename
  round-trip, broken-bundle isolation, concurrent readers, in-flight
  flow, double-reload guard, bundle-name mismatch) plus 9 CLI client
  tests

Mode A only.  In Mode B/C bundle changes require a Docker image rebuild
and the reload path is not exercised.

The events emission in Stage 5 is intentionally stubbed -- the LE-1017
ticket will swap the body of _emit_bundle_reload_event in one place
without touching the pipeline core.

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 14:40:26 -07:00
4c854c9c1e feat(lfx): add single-Bundle loader and LANGFLOW_COMPONENTS_PATH discovery (#12967)
* feat(lfx): add extension manifest schema, validate CLI, and error formatter (LE-1014)

Foundation for the Bundle Separation iteration. Defines what a valid
extension.json looks like (Pydantic models + Draft 2020-12 JSON Schema),
ships the offline `lfx extension validate` command, and ships the single
`format_extension_error` function that every other extension-system module
will use to render structured errors.

What's in lfx.extension:

- `ExtensionManifest` / `BundleRef` / `LangflowCompat` Pydantic models with
  `extra="forbid"` so unknown fields fail loudly. Deferred fields (`services`,
  `routes`, `hooks`, `starter_projects`, `userConfig`) are reserved as
  None-only so non-null values produce a dedicated
  `field-deferred-in-this-milestone` error instead of a generic schema wall.
  `bundles` accepts a list but rejects length > 1 with
  `multi-bundle-deferred-in-this-milestone` (validator-enforced; the loader
  re-checks at install time in LE-1015).
- `schema.build_schema()` + `build_schema_json()` produce the publishable
  artifact at schemas.langflow.org/extension/v1.json.
- `ExtensionError` typed envelope and `format_extension_error` -- one branch
  per discriminant. Codes are registered in `ERROR_CODES`; an
  `ExtensionError` constructed with an unknown code raises at construction
  time, preventing producers from shipping without a matching renderer.
- `validate_extension` runs four passes: manifest discovery + schema,
  path-safety (no `..`, no absolute paths, no symlink escape), AST
  inspection of every `.py` (syntax, Component subclass present, build()
  declared, top-level `import *`, top-level I/O primitives), and an opt-in
  `--execute-imports` that runs each module in a subprocess with a
  temporary HOME / TMPDIR / LANGFLOW_CONFIG_DIR and LANGFLOW_*/LFX_* env
  vars stripped.
- `lfx extension validate` and `lfx extension schema` typer subcommands.

Acceptance-criteria coverage in tests/unit/extension/:

- Round-trips every v0 manifest field; deferred fields rejected; multi-bundle
  rejected with the dedicated discriminant.
- JSON Schema validates the v0 example and rejects 12 malformed manifests
  with distinct error paths (>= 10 required by the ticket).
- Median default-validate runtime < 100ms on the basic template.
- Crafted side-effect bundle: default validate does NOT execute it (canary
  file is never written); `--execute-imports` DOES execute it and the canary
  appears, while LANGFLOW_* env vars are NOT inherited by the subprocess.
- Snapshot tests for every code in ERROR_CODES; a guard test verifies
  ERROR_CODES and the snapshot table are in lockstep so future additions
  cannot ship without a format branch and a snapshot.

Wiring: `lfx extension` is a sub-app under the Authoring help panel so
future tickets (LE-1016 init/dev, LE-1018 reload) can attach without
colliding with the existing `lfx validate` (which validates flow JSON, not
extensions).

* fix(lfx): fall back to tomli on Python 3.10 in extension manifest loader

tomllib is stdlib only on 3.11+, but lfx supports 3.10-3.13. Use the
existing tomli runtime dependency as the 3.10 fallback (same API, so the
import alias keeps the rest of the module unchanged).

Fixes ModuleNotFoundError seen in CI on the 3.10 job.

* Update src/lfx/tests/unit/extension/test_schema.py

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update src/lfx/src/lfx/extension/schema.py

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update src/lfx/src/lfx/cli/_extension_commands.py

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* feat(lfx): add single-Bundle loader and LANGFLOW_COMPONENTS_PATH discovery (LE-1015)

Introduces lfx.extension.loader: the runtime that turns an Extension on disk
into LoadedComponent records keyed by ext:<bundle>:<Class>@<slot>. Two paths in:

  - load_extension(root): one manifest, one Bundle, registered at @official.
    Re-checks multi-bundle at runtime (defense-in-depth vs. the schema).
  - discover_inline_bundles(paths): each subfolder of LANGFLOW_COMPONENTS_PATH
    is a Bundle at @extra. Walk order is platform-independent (sorted dirs,
    user-declared path order). First-wins on duplicate names; second emits
    duplicate-inline-bundle warning that names both paths.

Manifest-first precedence helpers (installed_extension_roots,
manifest_owning_distributions, filter_plugin_entry_points) let callers of
the legacy langflow.plugins entry-point loader skip distributions that ship
a manifest, so component entry-points are not double-registered.

New typed error codes: module-import-failed, duplicate-component-name,
duplicate-distribution, duplicate-inline-bundle, inline-bundle-name-invalid.
Each ships with a format branch and a snapshot test.

Tests cover the AC: single-bundle happy path, multi-bundle rejection,
missing/empty/no-Component bundle, duplicate class names, deterministic
walk order, recursive discovery, inline-bundle first-wins + dot-dir skip
+ bundle.json metadata, manifest-first precedence partition, PEP-503
distribution-name canonicalization.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(lfx): address LE-1015 review feedback

- Drop the unproduced duplicate-distribution error code; LE-1022 will add
  it once startup-time discovery has a place to surface it.  Restores the
  invariant that every code in ERROR_CODES has a producer.
- Clarify that intra-bundle relative imports are NOT supported in v0; only
  absolute references between bundle modules work in this milestone.
- Use strict=False when re-resolving bundle_root in the walker; the path
  was already existence-checked, and a concurrent removal in the narrow
  window should not raise across the loader's public boundary.
- Hoist the json import out of _read_inline_bundle_json's body.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(lfx): split extension loader into a small subpackage (LE-1015)

Addresses the file-size feedback from the LE-1015 review.  The single
870-line loader.py becomes a flat package keyed off the four section
banners that already existed inline:

    loader/
      __init__.py     # re-exports the public surface
      _types.py       # SLOT constants, LoadedComponent, LoadResult
      _discovery.py   # filesystem walk + importlib.util orchestration
      _detection.py   # Component subclass identification (MRO heuristic)
      _orchestrator.py # load_extension, discover_inline_bundles
      _plugins.py     # manifest-first precedence over langflow.plugins

Largest file is now _orchestrator.py at 440 LOC (was 870); every file is
well under the 800-LOC project guideline.  No behavior change: the public
import paths from lfx.extension are unchanged, all 38 loader tests still
pass.  ``_canonicalize_distribution`` is now exported as
``canonicalize_distribution`` from ``loader._plugins`` (it's a stable
PEP-503 helper that downstream modules will reach for, so it loses the
private underscore).

The test suite is split to mirror the package:

    tests/unit/extension/loader/
      conftest.py             # shared fixtures, FakeDist, autouse scrub
      test_load_extension.py  # @official slot, identity, failure modes
      test_inline_bundles.py  # LANGFLOW_COMPONENTS_PATH, @extra slot
      test_plugins.py         # manifest-first precedence helpers
      test_types.py           # LoadedComponent, LoadResult, code parity

Each test file imports its own slice of the public API and pulls fixtures
from conftest, so a reader looking at one banner can read it in isolation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: LangflowCompat -> LfxCompat

* fix: Remove references to ticket

* fix: encode maxItems constraint in schema

* fix: deferred fields and schema version

* Fix ruff errors

* fix: align loader tests with renamed manifest API and strip ticket refs

The merge of feat/extension-validate brought in the LfxCompat / compat
rename, but the loader test fixtures still used LangflowCompat / bundle_api
and failed at the manifest layer. Update conftest.py + test_load_extension.py
to the post-rename API.

Strip 17 LE-XXXX ticket references from loader source files, errors.py
loader-specific comments, and the four loader test docstrings, matching the
convention applied to LE-1014. Descriptive prose preserved.

Promote _BUNDLE_NAME_RE to BUNDLE_NAME_RE on the manifest module so the
loader's orchestrator no longer reaches across modules into a private name.

* refactor(lfx): clarify broad except, log malformed bundle.json, expand test docstring

- Document why _discovery.import_bundle_module catches BaseException (startup-time loader, must surface bad bundles as typed errors rather than abort).
- Add debug-level logging when bundle.json is malformed or non-object so a stale-cache footgun is at least observable.
- Expand test_skips_re_imported_class docstring so future maintainers don't accidentally weaken the __module__-equality guard if package-style relative imports get added later.

Also drops the stale duplicate-distribution claim from the PR description; that code is correctly deferred to LE-1022 along with /all integration.

* feat(lfx): wire Extension System into /all, pathsep-split LANGFLOW_COMPONENTS_PATH, emit duplicate-distribution

Closes the four AC gaps the previous reviewer flagged on PR #12967:

1. /all integration: get_and_cache_all_types_dict now also calls a new
   import_extension_components() that loads installed Extensions via
   load_installed_extensions, loads inline bundles via discover_inline_bundles,
   and builds frontend-node templates with extension/bundle/extension_version
   fields stamped on. Failures are logged and skipped per bundle.

2. LANGFLOW_COMPONENTS_PATH is now split on os.pathsep so multi-entry env vars
   (e.g. /a:/b on POSIX) produce multiple components-path entries instead of
   one literal non-existent path. Empty segments and missing paths are skipped.

3. duplicate-distribution is a real producer: load_installed_extensions
   surfaces a typed warning on the winner LoadResult when two distributions
   share a canonical name, naming every involved manifest path.

4. Manifest-first precedence runtime wiring: new filter_component_entry_points
   loads each entry-point and only skips ones that resolve to a Component
   subclass on a manifest-shipping distribution. plugin_routes.load_plugin_routes
   now applies it so non-component entry-points (route registrars) keep
   loading per the AC's 'unaffected' promise. Added the previously-missing
   AC test for same-distribution component+non-component partition.

Also makes _distribution_canonical_name defensive against MagicMock test seams.

* fix(lfx): register extension components under namespaced ID; promote duplicate-distribution to error

Addresses the latest review of PR #12967:

P1: /all integration now keys the cache inner dict by LoadedComponent.namespaced_id
(ext:<bundle>:<Class>@<slot>) rather than the bare class name. Templates also carry
the namespaced_id as an explicit field so consumers that look at the value (not the
key) still see the canonical address. This is the form the LE-1020 migration table
will rewrite legacy class-name references to.

P2: load_installed_extensions now appends duplicate-distribution to result.errors
instead of result.warnings, so LoadResult.ok=False when two distributions share a
canonical name. The winner's components still appear in result.components so flows
already pinned to them keep working; only the conflict status changes. Updated test
to assert errors + ok=False; added explicit assertion that the winner's components
are still present.

* fix(lfx): installed-distribution discovery accepts pyproject.toml manifest form

Closes the latest review finding on PR #12967: the installed-distribution
scan only looked for extension.json, ignoring distributions whose manifest
lives in [tool.langflow.extension] inside pyproject.toml. The AC explicitly
treats both as valid manifest forms.

_distribution_manifest_path now:
- Returns extension.json immediately when present (preserves precedence
  matching load_manifest's discovery order).
- Falls back to pyproject.toml only when extension.json is absent AND the
  pyproject's [tool.langflow.extension] section is parseable.

Validation reuses load_manifest itself so the rule lives in exactly one
place; a stray pyproject.toml without the section is correctly ignored.

Tests cover: pyproject-only discovery, pyproject-without-section ignored,
extension.json wins on collision, end-to-end pyproject load at @official,
and pyproject-form manifest-first entry-point suppression.

* refactor(lfx): tighten loader invariants, surface silent skips, harden tests

Addresses the latest review feedback on PR #12967:

Type-level invariants (_types.py):
- LoadedComponent.__post_init__ enforces that @extra components must NOT
  carry a distribution. The reverse (@official without distribution) is
  permitted because load_extension is also used for dev-mode loads
  against a working tree before pip install.
- LoadResult docstring documents the partial-success contract: components
  may be non-empty when errors is non-empty (some files imported, others
  failed). Callers branching on ok get strict success.

Silent-failure fixes (_orchestrator.py + settings/base.py):
- inline-path-missing: a non-existent / non-dir LANGFLOW_COMPONENTS_PATH
  entry now produces a typed warning per skipped path so a typo no
  longer yields zero diagnostics. Settings-layer skip bumped from debug
  to warning for the same reason.
- bundle-json-invalid: a malformed or non-object bundle.json now surfaces
  a typed warning instead of silently rewriting the user-declared
  id/version to derived values under the same bundle name.
- no-component-subclass gating uses a call-local counter instead of
  result.errors so the diagnostic stays accurate when a future caller
  reuses a LoadResult (multi-bundle / batch wrapper scenarios).

Test hardening:
- test_re_imported_class_is_skipped_via_module_filter rewritten to
  actually exercise the __module__-equality check via sys.modules
  injection; previously passed via module-import-failed (relative-import
  failure), which would silently weaken if package registration changes.
- test_user_declared_path_order_is_preserved: AC #8's multi-path order
  case (distinct bundles in [path_b, path_a]) was unasserted; added.
- test_inline_module_import_failure_attributes_identity: AC #10's
  identity-on-partial-failure was covered for @official but not @extra;
  added.
- test_uses_real_distributions_by_default tightened to assert ep
  placement (in kept, not in skipped) instead of exact-list equality, so
  a future Langflow-shipped manifest doesn't silently flip the assertion.

bumped 64 -> 175 passing extension tests; 20 backend integration tests
still pass.

* fix(lfx): malformed pyproject manifests surface manifest-invalid instead of disappearing

Closes the latest review finding on PR #12967: a pyproject.toml with a
[tool.langflow.extension] section that has missing/invalid required
fields was silently dropped because _pyproject_has_extension_section
ran full schema validation via load_manifest and returned False on
ValueError/TypeError. That conflated 'no section' with 'section
malformed'.

Fix: detect section presence only. _pyproject_has_extension_section now
calls _read_pyproject_extension (TOML parse + key lookup, no schema
check). Behavior:
- Section absent or pyproject TOML unparseable -> False (treat as
  regular non-manifest package).
- Section present and is a table (valid OR schema-invalid) -> True.
- Section present but is not a table -> True; the author intended to
  declare an extension and load_extension will surface the typed error.

This way a typo'd pyproject Extension produces a typed manifest-invalid
LoadResult with extension_id attribution, and manifest-first precedence
still suppresses its legacy component entry-points -- matching the
'typed load results on success/failure' contract for the supported
pyproject manifest form.

Tests: two new cases pin the behavior. test_malformed_pyproject_section_
surfaces_manifest_invalid asserts a typed load-failure result with
distribution attribution; the second test pins manifest-first
suppression for malformed pyproject distributions.

* refactor(lfx): emit inline-path-unreadable, document reload contract, trim rot

Closes the remaining nits on PR #12967:

- inline-path-unreadable (new typed error code): a configured
  LANGFLOW_COMPONENTS_PATH entry that raises OSError on iterdir
  (typically permission-denied) now produces a typed LoadResult error
  carrying str(exc) instead of silently swallowing the message.
- duplicate-inline-bundle hint trimmed: removed forward promise about
  hard-error-in-a-later-release; same actionability, no expiration.
- discover_inline_bundles docstring trimmed from three paragraphs to
  two sentences (per CLAUDE.md anti-multi-paragraph rule).
- _distribution_manifest_path docstring trimmed to one-liner.
- installed_extension_roots dropped Used-by caller-narration list;
  manifest_owning_distributions docstring rewritten to call out the
  shadow-load risk for direct callers and point to load_installed_
  extensions for the typed warning surface.
- _discovery.import_bundle_module: replaced 'until that lands in a
  later milestone' rot with a single line ('Absolute imports only
  between bundle modules; relative imports unsupported.') AND added
  the single-load-per-process contract note documenting why LE-1018
  reload must scrub registry/sys.modules before re-invoking the loader.
- load_extension docstring grew a 'Single-load-per-process contract'
  block telling direct callers not to rely on this function for refresh.

Tests: new test_unreadable_path_emits_inline_path_unreadable pins the
OSError -> typed-error path.

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 14:34:17 -07:00
0d0bb83401 Merge branch 'release-1.10.0' into feat/extension-production-install 2026-05-08 14:09:26 -07:00
8a1a1f367b feat(lfx): installed-package + seed-directory discovery for production install (LE-1022)
Adds the read-only production install path for Modes A, B, and C of the
Bundle Separation iteration. Manifest-shipping pip-installed
distributions and seed-directory subdirectories are discovered at server
startup and registered as Extensions at @official.

* discovery.py: walks importlib.metadata.distributions() + the
  $LANGFLOW_SEED_DIR / /opt/langflow/bundles seed root; produces
  DiscoveredExtension records and typed errors for malformed manifests
  / configured-but-missing seed dirs.
* registry.py: ExtensionRegistry service with the immutability
  invariant for installed and seed entries. Mutation verbs (uninstall,
  disable, enable, install, update_entry) all raise
  ExtensionImmutableError carrying the typed
  installed-extension-immutable / seed-directory-immutable code so the
  invariant is testable today; the CLI uninstall surface ships in B4.
* lfx extension list: read-only inspector with text and JSON output
  for operators inspecting Mode B/C images.
* Errors: four new typed codes
  (installed-extension-immutable, seed-directory-immutable,
  seed-directory-not-found, duplicate-extension-id) plus snapshot
  coverage in tests/unit/extension/test_errors.py.
* Tests: 165 extension tests pass, including the LE-1022 acceptance
  cases -- three pip-installed wheels visible at @official, three seed
  bundles visible at @official, and the parametrized service-layer
  immutability check across every mutation verb.
* Docs: docs/Deployment/deployment-extensions-production.mdx covers
  the Dockerfile template, k8s deployment notes, the bundle packaging
  convention (extension.json shipped via package-data), and
  troubleshooting for the typed error codes.
2026-05-08 14:08:06 -07:00
3befb61940 fix: skip missing server file when delete_after_processing=True to avoid race condition (#12556)
* fix: skip missing server file when delete_after_processing=True to avoid race condition (fixes #8887)

* [autofix.ci] apply automated fixes

* test(lfx): cover file_path branch for missing-file ValueError

Switch the missing-file ValueError test to set self.component.file_path
(via a Data object) instead of self.component.path, so the file_path
decision branch in load_files_base is exercised when
delete_server_file_after_processing=False and silent_errors=False.

* fix: cache parsed result so concurrent outputs don't drop server-file data

Previously the race-condition fix in load_files_base() returned an empty
list when a concurrent output call had already deleted the server file.
That avoided the spurious ValueError but silently dropped data — a File
component with multiple connected outputs could "succeed" while a
downstream node received an empty Data wrapper.

Cache the parsed Data list on the component instance after the first
successful processing, and reuse it when a subsequent call discovers the
file is gone. Each connected output now sees the same parsed content
instead of empty data, while still preserving the deletion semantics on
the source file.

Updates the regression test to assert the cached result is returned
(equal to the first call's output) rather than locking in the empty-list
behavior.

* fix: serialize load_files_base with per-instance lock and key cache by paths

The previous race-condition fix had two remaining gaps flagged in review:

1. It was not concurrency-safe. Two threads could both pass
   _validate_and_resolve_paths before either deleted the source file;
   the loser would then drop to empty data inside _filter_and_mark_files
   when the file vanished mid-flight.
2. The post-hoc recovery ("if validation returned no files and any cache
   exists, return it") was too broad — a legitimate empty input on a
   later call could pick up a stale cached result for a different
   server-file state.

Address both:

- Acquire a per-instance threading.Lock around the entire
  validate/process/delete cycle so concurrent output methods on the
  same component are serialized. The first caller does the work; the
  others observe the cached parsed result.
- Cache the parsed Data list keyed by (local paths, server file paths,
  markdown flag) so different inputs and different processing variants
  don't share entries. Empty results are not cached, so a legitimate
  empty input cannot return stale data.
- Set an explicit _validate_skipped_due_to_delete_race flag inside
  _validate_and_resolve_paths so recovery only activates when validation
  actually skipped a missing delete-after-processing file. Recovery
  reuses any cached result for the same source paths (across markdown
  variants) — preserving content beats dropping it when the file is
  gone.

Adds a concurrency test that drives two threads through load_files_base
simultaneously and asserts both observe the same parsed data, plus a
test that an empty input does not return stale cached data.

* fix: eagerly init load_files_base lock in __init__ to close first-entry race

Lazy lock creation inside load_files_base() (hasattr-then-assign) is itself
racy: on the very first concurrent entry, two threads can each find the
attribute missing and create their own threading.Lock, bypassing
serialization for the exact race this PR is fixing.

Move the lock creation into __init__ so every instance has a single Lock
before any output method can run. Removes the lazy-init branch from
load_files_base().

---------

Co-authored-by: octo-patch <octo-patch@github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Eric Hare <ericrhare@gmail.com>
2026-05-08 13:56:27 -07:00
104d941dbf refactor: refine MCP Server tab layout and alignment (#13032)
improve mcp tab layout
2026-05-08 13:48:02 +00:00
ad43a7d0b6 feat(agents): structured default system prompt with runtime env injection (#12855)
* default system prompt feature for agent

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

* ruff style and checker

* [autofix.ci] apply automated fixes

* fix tests CI

* fix agent merge conflict

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes

* address QA suggestions

* message ordering

* chore: auto-bake note keys and regenerate backend locales/en.json [skip ci]

* add guardrailws

* Ruff check

* chore: auto-bake note keys and regenerate backend locales/en.json [skip ci]

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Eric Hare <ericrhare@gmail.com>
2026-05-08 13:12:35 +00:00
1dd95e2902 feat(deployments): support multi-version flows (#12950)
* feat(deployments): support multi-version flows

* fix(deployments): use flows in attach step

* refactor(deployments): split review step

* feat(deployments): scope default tool names

* test(deployments): update stepper state tests

* test(deployments): update attach flow tests

* fix(deployments): preserve removed flow state

* fix(deployments): keep stepper provider mounted

* fix(deployments): sync attached flow review

* refactor(deployments): share scoped lookups

* refactor(deployments): type version selection

* refactor(deployments): split payload builders

* chore(deployments): standardize labels

* refactor(deployments): isolate wxo naming

* refactor(deployments): simplify attach checks

* chore(deployments): drop stale effect ignores

* refactor(deployments): remount edit stepper

* fix(tests): update tests for effectiveAttachmentKey rename and scoped tool names

- use-connection-panel-state.test.ts: rename effectiveFlowId → effectiveAttachmentKey
  in baseParams() to match hook's refactored param name
- deployment-create.spec.ts: make SNAPSHOTS_DUPLICATE_MOCK route dynamic — echo back
  requested names as existing tools so duplicate check works with scoped names
  (getDefaultDeploymentToolName now generates "Flow {scope}-{id}" format)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 12:52:55 +00:00
9ba0976ff2 fix: remove stepper modal close button (#12986)
fix(deploy): remove stepper modal close button
2026-05-08 12:52:42 +00:00
07c3e67a71 fix(security): prevent arbitrary file read via public flow build endpoint (GHSA-rcjh-r59h-gq37) (#13029)
* fix(security): prevent arbitrary file read via public flow build endpoint (GHSA-rcjh-r59h-gq37)

The unauthenticated `POST /api/v1/build_public_tmp/{flow_id}/flow`
endpoint accepted a `files` array whose paths were resolved by
`LocalStorageService` and read off disk before being attached to the
LLM prompt. A path like `/etc/hosts` was split into `flow_id="/etc"`
and `file_name="hosts"`. `data_dir / "/etc"` collapsed to `/etc`
because joining an absolute segment resets the prefix, and the
containment check was anchored at that attacker-controlled folder
so it passed. The file contents were then echoed back through the
chat reply.

Fix layers:

1. Boundary validation in `build_public_tmp`: each `files` entry
   must match `{source_flow_id}/{basename}` with no traversal,
   backslash, or null bytes. Foreign flow_ids are rejected with
   HTTP 400.
2. `LocalStorageService._validated_path` now also rejects
   `flow_id` values containing path separators, traversal, or
   null bytes, and anchors the containment check at the data
   directory rather than the per-flow folder.

Adds regression tests for both layers covering `/etc/hosts`,
traversal, foreign-namespace and null-byte payloads, plus an
acceptance test that legitimate `{flow_id}/{filename}` references
still work for AUTO_LOGIN playground uploads.

* add identifier validation

---------

Co-authored-by: cristhianzl <cristhian.lousa@gmail.com>
2026-05-08 12:30:46 +00:00
683b5b2299 docs: update mdc file to reflect current usage (#13019)
docs-update-mdc-file-for-current-usage
2026-05-07 20:04:11 +00:00
7504eb4c72 fix: pin postgres image to bookworm in docker_example (#13027)
* fix: pin postgres image to bookworm in docker_example to prevent collation mismatch

The postgres:16 tag silently moved its base from Debian Bookworm
(glibc 2.36) to Trixie (glibc 2.41), causing a recurring collation
version mismatch warning on existing langflow-postgres volumes.

Pin to postgres:16-bookworm in both docker-compose files and update
the README so existing data volumes keep matching the OS locale data.

Refs: https://github.com/langflow-ai/langflow/issues/9608

* docs: pin postgres image to bookworm in current docs compose snippets

Mirror the docker_example pin in the four current docs that publish
copyable Compose snippets pairing postgres:16 with a persistent
langflow-postgres volume. Prevents the same Bookworm-to-Trixie
collation version mismatch warning when users follow docs instead
of docker_example.

Versioned historical docs are left as-is.

Refs: https://github.com/langflow-ai/langflow/issues/9608

* fix: switch postgres pin from bookworm to trixie for OS consistency

Aligns the pinned postgres base with the langflow runtime image, which
moved to Debian Trixie in #12990. Keeping postgres on bookworm would
have diverged the stack and locked the database to an aging glibc that
will receive fewer security backports as bookworm ages into oldstable.

The pin itself still solves the original bug — postgres:16 cannot
silently roll its OS underneath an existing volume.

Document the one-time REFRESH COLLATION VERSION step for users
upgrading from a bookworm-initialized volume in docker_example/README.md.

Refs: https://github.com/langflow-ai/langflow/issues/9608
2026-05-07 19:39:49 +00:00
b707c9a41d docs: opensearch connector feature (#12998)
* docs-add-opensearch-provider-and-adjust-kb-docs

* docs-combine-kb-config-sections-and-update-partial

* add-release-note

* Apply suggestions from code review

Co-authored-by: Mendon Kissling <59585235+mendonk@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: April I. Murphy <36110273+aimurphy@users.noreply.github.com>

* docs-clarify-embedding-model-step

---------

Co-authored-by: April I. Murphy <36110273+aimurphy@users.noreply.github.com>
2026-05-07 18:28:04 +00:00
406b372d7a chore(starter-projects): new Vector Store RAG template (#13018)
* chore(starter-projects): new Vector Store RAG template

Update the RAG starter flow to align with the new knowledge base
paradigm: removes the inline knowledge base creation sub-flow and
points users to the dedicated Knowledge Ingestion UI for loading
content into their vector store.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(starter-projects): tweak Vector Store RAG README description

Drop the outdated reference to two sub-flows in the in-template
README and refresh the saved metadata.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: autofix of starter project json

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Eric Hare <ericrhare@gmail.com>
2026-05-07 15:27:41 +00:00
abbe917811 test: fix flow state clean test on windows (#13013) 2026-05-07 10:01:24 -03:00
1217ced9e1 Fix: Migration 1.9.2 to 1.10.0 back to 1.9.2 idempotency issue (#12991)
* Fixing migration 1.9.2 -> 1.10.0 -> 1.9.2 idempotent.

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-05-06 21:55:17 +00:00
af8871cf61 fix: force pull latest base images in Docker nightly builds (#13014)
- Add pull: true to all docker build steps in nightly workflow
- Forces Docker to pull latest python:3.12-slim-trixie instead of using cached layers
- Ensures Debian Trixie base images are used instead of cached Bookworm layers
- Resolves issue where Docker layer caching prevented CVE fixes from taking effect
2026-05-06 21:26:57 +00:00
30ac789ca1 fix(frontend): default event_delivery to streaming to match server config (#12978)
Frontend was forcing event_delivery=polling in the build query, overriding
LANGFLOW_EVENT_DELIVERY=streaming on the server. Token-by-token streaming
in the Playground never worked because the frontend always took the polling
code path (customPollBuildEvents) instead of SSE (performStreamingRequest).

Aligns three default fallbacks with the server doc default (STREAMING):
- buildUtils.buildFlowVertices: query-param default
- utilityStore.eventDelivery: initial store value (race window before /config)
- use-get-config: fallback when /config omits event_delivery

buildFlowVerticesWithFallback already retries with POLLING on
STREAMING_NOT_SUPPORTED / ENDPOINT_NOT_AVAILABLE, so streaming-first is safe.

Fixes #12291
2026-05-06 17:56:34 +00:00
7c347a56ea docs: release note refactor (#12987)
* docs-1.10-release-notes-and-link-out-to-previous-versions

* docs-remove-outdated-windows-upgrade-note
2026-05-06 15:44:07 +00:00
92e21615c6 fix(tests): Stabilize traces and user-flow-cleanup e2e on Windows CI (#12997) 2026-05-06 12:09:04 -03:00
0aea910dac fix(frontend): paginator empty state shows "0 items" instead of "1-0 of 0 items" (#12965)
* fix(frontend): paginator empty state shows "0 items" instead of "1-0 of 0 items"

When totalRowsCount is 0 the paginator rendered "1-0 of 0 items" and
"of 0 pages", with the next button enabled because pageIndex (1) was
not equal to maxIndex (0). Hide the range when empty, floor maxIndex
to 1, and disable both navigation buttons when there is nowhere to go.

* Update legacy paginator tests to assert new empty-state copy
2026-05-06 14:03:57 +00:00
deecfc68fa fix(mcp): make run_tool wait_for timeout configurable, raise floor to… (#12996)
fix(mcp): make run_tool wait_for timeout configurable, raise floor to 180s

The hardcoded `timeout=30.0` on `asyncio.wait_for(session.call_tool(...))`
in both `MCPSseClient.run_tool` and `MCPStreamableHttpClient.run_tool` was
truncating in-flight tool calls whose servers needed longer than 30s to
respond — most visibly large MCP composers whose tool responses include
sizeable catalogs.

Failure mode:

  LLM → AgentExecutor → run_tool   (1 call from the LLM)
    ├── attempt 1: get_session (probe ..._1 fails → swap ..._2)
    │             → call_tool → 30s wait_for fires, TimeoutError raised
    │             (server is still working — response ignored on arrival)
    └── attempt 2: get_session (probe ..._2 fails → swap ..._3)
                  → call_tool → 30s wait_for fires again
                  → util.py dedup ("Repeated TimeoutError, not retrying")
                  → ValueError("Maximum retries exceeded ...")
  ← AgentExecutor surfaces error → flow fails

The retry loop's TimeoutError dedup correctly stops looping once it sees
the same error twice, but every call against a slow server hit the same
ceiling and never had a chance to succeed. The session swaps between
attempts also produced excess HTTP churn against the server.

Both `run_tool` methods now resolve the timeout from
`get_settings_service().settings.mcp_server_timeout` (already used for
connection setup at lines 1617 / 1887, env: `LANGFLOW_MCP_SERVER_TIMEOUT`)
with a `max(setting, 180.0)` floor so default deployments don't end up
*shorter* than the previous hardcoded 30s — the Pydantic default for
`mcp_server_timeout` is 20.

Touched:
  src/lfx/src/lfx/base/mcp/util.py
    - L1677, L1689 (stdio run_tool)
    - L1960, L1972 (streamable HTTP run_tool)

Unchanged on purpose:
  - L1217, L1381  session-creation `timeout=30.0` (separate concern).
  - L1060        `_validate_session_connectivity` 3.0s probe (separate
                  concern; addressed in follow-up if probe thrash persists).

Tuning:
  - LANGFLOW_MCP_SERVER_TIMEOUT now governs both connection setup and
    tool-call wait_for. The 180s floor clamps low values; raise the env
    var above 180 to take effect.

Repro / verification:
  - Trace run that previously failed at 67.08s with "Maximum retries
    exceeded with repeated TimeoutError errors" against
    `mcp-guardium_get_service_info` (47KB response payload) now
    completes on attempt 1.

Co-authored-by: MANSURA HABIBA <MANSURAH@ie.ibm.com>
2026-05-06 07:33:47 -07:00
4b46b57e5c fix: Ensure direct-uvicorn startup in macOS 2026-05-05 21:50:01 -07:00
78f82cae32 perf: Enhance Gunicorn preload functionality for Langflow (#12778)
* perf: Enhance Gunicorn preload functionality for Langflow

- Introduced a new preload module to optimize memory usage by running fork-safe initialization in the Gunicorn master process.
- Updated the lifespan management in `main.py` to check if the master has preloaded resources, allowing workers to inherit state and skip redundant setup.
- Adjusted the server loading process to accommodate the new preload logic, ensuring efficient resource management across worker processes.

* feat(cache): Implement teardown method for RedisCache to prevent socket leaks

- Added a `teardown` method to the `RedisCache` class to close the Redis connection, addressing potential socket leaks during process forking.
- Introduced unit tests to verify the functionality of the `teardown` method, ensuring it handles client closure and errors gracefully.
- Tests cover scenarios including normal closure, error handling during closure, and teardown with URL-based connections.

* feat(cache): Implement teardown method in RedisCache to prevent socket leaks

- Added a `teardown` method to the `RedisCache` class, ensuring proper closure of the Redis client connection before forking to avoid socket leaks.
- Created comprehensive unit tests to validate the functionality of the `teardown` method, covering various scenarios including normal operation and error handling.
- Updated existing tests to reflect the new teardown functionality and ensure RedisCache is recognized as an instance of `ExternalAsyncBaseCacheService`.

* refactor(preload): Simplify DB engine disposal logic in master preload

- Removed exception handling around the DB engine disposal to streamline the process, ensuring that the engine is disposed of without unnecessary error logging. This change enhances code clarity and maintains the intended functionality of resource management during the preload phase.

* refactor(preload): Streamline cache service teardown logic

- Simplified the cache service socket closure process in the master preload function to prevent sharing across forks. This change enhances code clarity by removing unnecessary exception handling while maintaining the intended functionality of resource management during the preload phase.

* feat(preload): Introduce per-step completion flags for improved state management

- Added completion flags in the preload state to track the status of various initialization steps, including profile picture copying, starter project creation, agentic global variable initialization, MCP server configuration, and flow loading.
- Updated the lifespan management in `main.py` to utilize these flags, allowing the system to skip redundant setup tasks if they have already been completed during the preload phase.
- This enhancement improves resource management and ensures that the application behaves correctly in a multi-worker environment.

* refactor(lifespan): Replace temp dir management with get_owned_temp_dirs function

- Updated the lifespan management in `main.py` to utilize the new `get_owned_temp_dirs` function, which encapsulates the logic for determining temp directory ownership based on the process type (master or worker).
- Removed the `is_master` check from the lifespan function, simplifying the code and enhancing clarity regarding temp directory cleanup responsibilities.
- Added the `get_owned_temp_dirs` function in `preload.py` to centralize temp directory ownership logic, ensuring that workers do not attempt to clean up directories owned by the master process.

* refactor(lifespan): Enhance initialization flow with conditional gates

- Introduced conditional gates in the `get_lifespan` function to manage the initialization of profile pictures, super users, bundles, component types, and starter projects based on their completion status.
- Improved logging to provide clearer insights into which steps are being skipped or executed, enhancing the overall clarity of the initialization process.
- Updated the preload logic in `preload.py` to ensure that agentic global variables and MCP server configuration are only initialized when necessary, maintaining efficient resource management in a multi-worker environment.

* fix: resolve double-call of initialize_auto_login_default_superuser and add preload tests

- Fix double-call issue: setup_superuser() now handles AUTO_LOGIN completely with file lock
- Add comprehensive unit tests for preload.py covering failure-fallback contract
- Simplify code by doing superuser initialization in initialize_services() (called early in both preload and worker startup)
- File lock protects multi-worker race conditions when preload is disabled
- Tests verify critical step failures propagate, best-effort steps continue on failure

Made-with: Cursor

* fix(preload): resolve missing import and agentic initialization conflicts

Fixed critical bugs introduced in c0e81a5401 that caused preload failures:

1. Missing import: Added DEFAULT_SUPERUSER_PASSWORD to module-level imports
   - Was only imported inside AUTO_LOGIN block but used when AUTO_LOGIN=false
   - Caused NameError that crashed preload with "session scope error"

2. Removed agentic variable initialization from setup_superuser()
   - Prevents double-initialization conflict with preload's dedicated step
   - initialize_agentic_global_variables() in preload handles all users

3. Made teardown_superuser() more robust
   - Now skips deletion instead of raising errors on FK constraints
   - Prevents startup failures when default superuser has associated flows

Resolves: "An error occurred during the session scope" preload error
Resolves: Ghost thread warnings from incomplete initialization
Made-with: Cursor

* refactor(preload): Enhance state management and initialization logic

- Updated the `_PreloadState` class to include `bundles_loaded` and `types_cached` flags for better tracking of initialization steps.
- Modified the `get_lifespan` function to utilize the new state flags, improving the conditional logic for loading bundles and caching component types.
- Implemented a `reset` method in `_PreloadState` to ensure consistent state restoration after preload failures, enhancing reliability in multi-worker environments.
- Simplified the teardown process in `ExternalAsyncBaseCacheService` by making `teardown` an abstract method, allowing direct calls without fallback checks.

This refactor improves clarity and efficiency in the preload and lifespan management processes.

* refactor(lifespan): Improve component types caching and starter project creation logic

- Enhanced the `get_lifespan` function to utilize a local handle for component types when the cache is inherited, ensuring consistency in multi-worker environments.
- Added logging for scenarios where the component types cache is empty, allowing for cache rebuilding instead of skipping starter project creation.
- Streamlined the starter project creation process with improved error handling and logging, ensuring clarity on failures and skipped operations.

This refactor enhances the reliability and clarity of the initialization process in the application.

* fix(tests): Update teardown_superuser test to preserve default superuser if never logged

- Renamed the test function to clarify its purpose.
- Modified the test logic to ensure that the default superuser is not removed during teardown if it has never been logged in, preventing foreign key errors.
- Added assertions to verify that the superuser remains intact and its properties are correctly set after teardown.

This change enhances the reliability of the superuser management in the test suite.

* feat: Implement orphaned MCP server config migration and add unit tests

- Introduced `migrate_orphaned_mcp_servers_config` function to recover MCP server config files that become orphaned after a database reset while preserving the config directory.
- Added logic to handle scenarios with multiple orphaned files, ensuring safe migration and logging for manual recovery.
- Created comprehensive unit tests to validate the migration functionality, covering cases for single orphan recovery, multiple orphans, and scenarios with no orphans.
- Enhanced the setup of the superuser to include the migration process, improving the robustness of user configuration management.

This update enhances the application's ability to recover user configurations in containerized environments.

* fix(teardown): Improve superuser removal logic and error handling

- Updated the `teardown_superuser` function to remove the default superuser when AUTO_LOGIN is disabled and the user has never logged in, enhancing user management.
- Improved error handling to raise a `RuntimeError` if the removal fails, ensuring that issues are logged and not silently ignored.
- Adjusted the `DatabaseService` teardown process to reflect the updated superuser removal logic and ensure proper resource disposal.

This change enhances the reliability of the superuser management during application shutdown.

* fix(setup_superuser): Enhance error handling for AUTO_LOGIN timeout scenario

- Updated the `setup_superuser` function to provide clearer logging and error messages when the AUTO_LOGIN lock times out and no default superuser exists.
- Introduced a check to verify the existence of the default superuser in the database, raising a `RuntimeError` with a descriptive message if it is not found.
- Added unit tests to validate the behavior of the setup process under timeout conditions, ensuring that the application fails gracefully when required superuser credentials are missing.

This change improves the robustness of the superuser initialization process and enhances error visibility during startup.

* fix(preload): Update error handling to log exceptions with traceback for best-effort steps

- Modified the error handling in the `_run_master_preload` function to log exceptions with traceback instead of warnings for best-effort steps, ensuring better visibility into failures.
- Updated unit tests to reflect the change in logging behavior, verifying that exceptions are correctly logged during preload operations.

This change enhances the clarity of error reporting during the preload process, aiding in debugging and monitoring.

* fix(preload): Ensure cleanup of temporary directories during reset

- Updated the `reset` method in the `_PreloadState` class to call `cleanup()` on each `TemporaryDirectory` before clearing the `temp_dirs` list, preventing on-disk directory leaks during failed preloads.
- Added a unit test to verify that `cleanup()` is called for all temporary directories during the reset process, ensuring proper resource management.

This change enhances the reliability of the preload process by ensuring that temporary resources are properly cleaned up.

* refactor(preload): Introduce PreloadStep enumeration and streamline step completion logic

- Added a `PreloadStep` enumeration to define ordered preload phases, enhancing clarity and maintainability of the preload process.
- Replaced direct state attribute checks with the `is_step_complete` function to improve readability and encapsulate step completion logic.
- Updated the `_run_master_preload` function to utilize `mark_step_complete` for recording successful completion of preload steps, enforcing prerequisite ordering.
- Enhanced unit tests to validate the new step completion logic and ensure proper functionality of the preload process.

This refactor improves the structure and reliability of the preload mechanism, making it easier to manage and understand the initialization flow.

* refactor(superuser): Remove redundant checks for superuser credentials in setup functions

- Eliminated unnecessary validation for `DEFAULT_SUPERUSER` and `DEFAULT_SUPERUSER_PASSWORD` in both `initialize_auto_login_default_superuser` and `setup_superuser` functions, simplifying the initialization logic.
- This change streamlines the superuser setup process, assuming that the necessary credentials are managed externally, thus enhancing code clarity and maintainability.

* refactor(preload): Streamline error handling and step completion in preload process

- Introduced a `_best_effort` function to encapsulate error handling for preload steps, improving code readability and maintainability.
- Updated the `_run_master_preload` function to utilize the new error handling mechanism for various steps, ensuring consistent logging and state management.
- Enhanced the documentation in the preload module to clarify the handling of fork-unsafe resources and the overall preload process.

This refactor enhances the robustness and clarity of the preload mechanism, making it easier to manage and understand the initialization flow.

* refactor(preload): Enhance error handling and resource cleanup in preload process

- Wrapped post-initialization work in a try/finally block to ensure the DB engine and cache service are always disposed of, preventing resource leaks in forked workers.
- Updated logging to provide clearer visibility into the preload steps, including error handling for best-effort operations.
- Improved the structure of the `_run_master_preload` function by consolidating related operations and ensuring consistent cleanup.

This refactor enhances the robustness and clarity of the preload mechanism, ensuring safe resource management during initialization.

* [autofix.ci] apply automated fixes

* test(setup_superuser): remove default superuser before lock-timeout no-superuser case

The `initialized_services` fixture starts with `AUTO_LOGIN=false`, which
runs `setup_superuser` through the credentials-fallback path and creates
the default superuser. The "raises_when_no_superuser" test then mocked
the lock to time out, but the existence check found that pre-created
user and returned `AUTO_LOGIN_LOCK_TIMEOUT_SUPERUSER_PRESENT` instead of
raising `RuntimeError`. Delete the default superuser before mocking the
lock so the no-superuser branch is actually exercised.

---------

Co-authored-by: Jordan Frazier <jordan.frazier@datastax.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Eric Hare <ericrhare@gmail.com>
2026-05-05 21:49:11 +00:00
fae08ab66a feat: Added API support for BE endpoints (#12423)
* add memories

* add memories

* accessibility and bug fixes

* fix types

* fix types

* [autofix.ci] apply automated fixes

* add new testcases

* [autofix.ci] apply automated fixes

* remove auto capture

* [autofix.ci] apply automated fixes

* coderabbit fixes and remove KB from FE

* added enpoint support

* coderabbit fix

* [autofix.ci] apply automated fixes

* added session and memory messages endpoint

* code rabbit fixes

* [autofix.ci] apply automated fixes

* merge session id fix

* [autofix.ci] apply automated fixes

* fix testcases

* [autofix.ci] apply automated fixes

* fix testcases

* fix traces sidebar

* memory query rewrite and testcase improvement

* [autofix.ci] apply automated fixes

* Improve seperation of concern for memory hook

* [autofix.ci] apply automated fixes

* handleToggleActive Signature Breaking Change

* improve testcase

* [autofix.ci] apply automated fixes

* fix testcases

* fix mcp testcase

* disable embedded option trigger causing user to to be able to switch provider when option is zero

* Peer review updates

* [autofix.ci] apply automated fixes

* added refresh logic and fixed prior issues from CR

* [autofix.ci] apply automated fixes

---------

Co-authored-by: Olayinka Adelakun <olayinkaadelakun@mac.war.can.ibm.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Olayinka Adelakun <olayinkaadelakun@Olayinkas-MacBook-Pro.local>
2026-05-05 21:27:47 +00:00
596e98b9b7 fix: Respect proxy environment variables in URL Component (#12989)
* fix: URLComponent ignores proxy in async mode (#12285)

The URLComponent fails to connect for users behind corporate proxies
because its asynchronous mode does not recognize standard system proxy
environment variables. The component defaults to use_async=True, which
initializes the RecursiveUrlLoader; the underlying async loader does
not natively respect system proxy environment variables, so the
component attempts a direct connection and fails in restricted network
environments.

Detect standard proxy environment variables (http_proxy, HTTP_PROXY,
https_proxy, HTTPS_PROXY) in URLComponent._create_loader. If a proxy
is detected and use_async is enabled, override use_async to False so
the loader uses its synchronous implementation, which natively
respects system proxies. Empty and whitespace-only values are
correctly evaluated as no-proxy and do not trigger the fallback.

Closes #10297

* fix(URLComponent): broaden proxy detection and harden tests

Address review findings on the proxy fix:

- Add ALL_PROXY and all_proxy to the detected env vars. ALL_PROXY is
  commonly set in corporate and container environments (curl, git,
  many Unix tools honour it), and is sometimes the only proxy var
  configured.
- Replace the unused proxy_url string with a single boolean any() over
  the env var keys, since only the presence of a proxy is consulted.
- Reorganize the proxy tests under TestURLComponentProxyHandling with
  a shared default-attributes helper, parametrize over all six env var
  spellings, and add coverage for multiple-simultaneous-proxies and
  the use_async=False path (which should not log a warning).

* [autofix.ci] apply automated fixes

* fix: remove no_proxy="*" macOS startup hack so corporate proxies work

Two startup paths set `os.environ["no_proxy"] = "*"` on macOS, which
disables proxy use for every HTTP client in the process and every child
gunicorn worker (httpx, requests, urllib3 — and therefore the OpenAI,
Anthropic, Groq, etc. SDKs that wrap them). For users behind corporate
proxies on macOS this made every external LLM call unroutable, even with
HTTPS_PROXY properly set.

The override traces to commit history with no concrete justification —
the only artifact is a Stack Overflow link about a uWSGI segfault, but
Langflow uses gunicorn, not uWSGI. Bench-verified that gunicorn boots
cleanly and serves requests on macOS without it (1 worker via
LangflowApplication, OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES retained,
HTTPS_PROXY survives intact in parent and child).

Verification:
  - gunicorn worker spawns and serves /health (200 OK), exits cleanly
  - httpx, urllib, requests all resolve HTTPS_PROXY after the macOS init
    (previously: all three returned empty proxy maps because of no_proxy=*)
  - OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES still set — the actual
    fork-safety fix is preserved

Closes the macOS half of #10297. Companion fix for the async URLComponent
half is in #12285 / branch pr-12285-rebased.

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

* [autofix.ci] apply automated fixes (attempt 3/3)

* Orjson update

---------

Co-authored-by: Diogo Veiga <diogo.veiga@tecnico.ulisboa.pt>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-05-05 13:55:39 -07:00
c4d7a247f4 fix: update Docker base images to latest Python 3.12 and Debian Trixie to resolve CVEs (#12990)
* fix: update Docker base images to latest Python 3.12 to resolve CVEs

- Update all Dockerfiles from python:3.12.13-slim-trixie to python:3.12-slim-trixie
- This ensures automatic security patches for Debian base image CVEs
- Affects nightly builds (base, main, main-all) and release builds (backend, ep)
- Using unpinned patch version (3.12 vs 3.12.13) follows security best practices

* fix: update builder stage to Debian Trixie for consistency

- Update all Dockerfiles from bookworm-slim to trixie-slim in builder stage
- Ensures consistency between builder and runtime Debian versions
- Eliminates CVEs in both build and runtime environments
- Uses ghcr.io/astral-sh/uv:python3.12-trixie-slim for all builders
2026-05-05 20:25:20 +00:00
364fd5dd9a fix: Notifications overlap the bottom-center menubar (#12946)
fix: raise CanvasBanner above bottom-center menubar to prevent overlap

Co-authored-by: Olayinka Adelakun <olayinkaadelakun@Olayinkas-MacBook-Pro.local>
2026-05-05 19:44:24 +00:00
32308507e8 feat: Frontend memory (#12293)
* add memories

* add memories

* accessibility and bug fixes

* fix types

* fix types

* [autofix.ci] apply automated fixes

* add new testcases

* [autofix.ci] apply automated fixes

* remove auto capture

* [autofix.ci] apply automated fixes

* coderabbit fixes and remove KB from FE

* fix testcases

* [autofix.ci] apply automated fixes

* fix testcases

* fix mcp testcase

---------

Co-authored-by: Olayinka Adelakun <olayinkaadelakun@mac.war.can.ibm.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Olayinka Adelakun <olayinkaadelakun@Olayinkas-MacBook-Pro.local>
2026-05-05 19:36:41 +00:00
017cb1f93c feat(assistant): Add Refresh List and Manage Model Providers to model selector (#12985)
* add model provider config on model selection

* [autofix.ci] apply automated fixes

* fix fe tests playwright

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-05-05 19:26:27 +00:00
d04debcb2e feat: native structured output on AgentComponent with prompt fallback (#12935)
* initial commit for structural response

* add schema preprocessing tests

* fix agent duplication message

* add playwright tests

* [autofix.ci] apply automated fixes

* chore: auto-bake note keys and regenerate backend locales/en.json [skip ci]

* ruff fixes

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-05-05 18:34:29 +00:00
b8a2b126ca feat(lfx): extension manifest schema, validate CLI, and error formatter (LE-1014) (#12952)
* feat(lfx): add extension manifest schema, validate CLI, and error formatter (LE-1014)

Foundation for the Bundle Separation iteration. Defines what a valid
extension.json looks like (Pydantic models + Draft 2020-12 JSON Schema),
ships the offline `lfx extension validate` command, and ships the single
`format_extension_error` function that every other extension-system module
will use to render structured errors.

What's in lfx.extension:

- `ExtensionManifest` / `BundleRef` / `LangflowCompat` Pydantic models with
  `extra="forbid"` so unknown fields fail loudly. Deferred fields (`services`,
  `routes`, `hooks`, `starter_projects`, `userConfig`) are reserved as
  None-only so non-null values produce a dedicated
  `field-deferred-in-this-milestone` error instead of a generic schema wall.
  `bundles` accepts a list but rejects length > 1 with
  `multi-bundle-deferred-in-this-milestone` (validator-enforced; the loader
  re-checks at install time in LE-1015).
- `schema.build_schema()` + `build_schema_json()` produce the publishable
  artifact at schemas.langflow.org/extension/v1.json.
- `ExtensionError` typed envelope and `format_extension_error` -- one branch
  per discriminant. Codes are registered in `ERROR_CODES`; an
  `ExtensionError` constructed with an unknown code raises at construction
  time, preventing producers from shipping without a matching renderer.
- `validate_extension` runs four passes: manifest discovery + schema,
  path-safety (no `..`, no absolute paths, no symlink escape), AST
  inspection of every `.py` (syntax, Component subclass present, build()
  declared, top-level `import *`, top-level I/O primitives), and an opt-in
  `--execute-imports` that runs each module in a subprocess with a
  temporary HOME / TMPDIR / LANGFLOW_CONFIG_DIR and LANGFLOW_*/LFX_* env
  vars stripped.
- `lfx extension validate` and `lfx extension schema` typer subcommands.

Acceptance-criteria coverage in tests/unit/extension/:

- Round-trips every v0 manifest field; deferred fields rejected; multi-bundle
  rejected with the dedicated discriminant.
- JSON Schema validates the v0 example and rejects 12 malformed manifests
  with distinct error paths (>= 10 required by the ticket).
- Median default-validate runtime < 100ms on the basic template.
- Crafted side-effect bundle: default validate does NOT execute it (canary
  file is never written); `--execute-imports` DOES execute it and the canary
  appears, while LANGFLOW_* env vars are NOT inherited by the subprocess.
- Snapshot tests for every code in ERROR_CODES; a guard test verifies
  ERROR_CODES and the snapshot table are in lockstep so future additions
  cannot ship without a format branch and a snapshot.

Wiring: `lfx extension` is a sub-app under the Authoring help panel so
future tickets (LE-1016 init/dev, LE-1018 reload) can attach without
colliding with the existing `lfx validate` (which validates flow JSON, not
extensions).

* fix(lfx): fall back to tomli on Python 3.10 in extension manifest loader

tomllib is stdlib only on 3.11+, but lfx supports 3.10-3.13. Use the
existing tomli runtime dependency as the 3.10 fallback (same API, so the
import alias keeps the rest of the module unchanged).

Fixes ModuleNotFoundError seen in CI on the 3.10 job.

* Update src/lfx/tests/unit/extension/test_schema.py

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update src/lfx/src/lfx/extension/schema.py

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update src/lfx/src/lfx/cli/_extension_commands.py

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* fix: LangflowCompat -> LfxCompat

* fix: Remove references to ticket

* fix: encode maxItems constraint in schema

* fix: deferred fields and schema version

* Fix ruff errors

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-05-05 18:28:01 +00:00
10745ac3a5 fix: encode non-ASCII filenames in Content-Disposition headers (RFC 5987) (#12918)
* fix: encode non-ASCII filenames in Content-Disposition headers

HTTP headers only support ASCII/latin-1. Placing Chinese or other
non-ASCII characters directly in filename="..." caused a latin-1
serialization error (500) on download.

Fix all four download routes to use an ASCII-safe fallback in
filename= and the URL-encoded form in filename*=UTF-8'':
- api/v1/files: /download/{flow_id}/{file_name}
- api/v1/flows_helpers: /flows/download/ (ZIP)
- api/v2/files: /files/{file_id} and /files/batch/

Also fix the frontend filename extraction regex in both
use-get-download-flows.ts and use-get-download-files.ts to parse
RFC 5987 (filename*=UTF-8''<encoded>) with decodeURIComponent,
falling back to plain filename= for older responses.

Closes #8105

* test: add non-ASCII filename encoding tests for file download routes

Cover the RFC 5987 Content-Disposition encoding fix across all
affected download endpoints:
- api/v1/files: parametrized test with Chinese, Japanese, accented
  Latin, and ASCII filenames verifying filename*=UTF-8'' encoding
- api/v2/files: same parametrized test for single-file download plus
  a batch download test verifying the ZIP Content-Disposition header
- flows download: test with Chinese flow names verifying the ZIP
  response uses a decodable RFC 5987 filename

Reproduces the regression from issue #8105 where downloading files
with non-ASCII names returned HTTP 500.

* fix: use safe="" in quote() for RFC 5987 filename encoding and tighten test assertion

Ensures forward slashes and other special characters are percent-encoded
in Content-Disposition filename* values. Also fixes weak in-operator
assertion in v2 test to check the correct direction.

* fix: centralize RFC 5987 Content-Disposition encoding and patch header injection

Extract build_content_disposition() helper in api/utils/core.py that:
- Escapes double-quotes in the ASCII fallback (prevents parameter smuggling
  via filenames like evil"; x=injected)
- Uses quote(safe="") to percent-encode forward slashes
- Returns the dual-param format (filename= + filename*=) for broad client
  compatibility

Replace the duplicated 3-line encoding blocks in v1/files.py, v2/files.py,
flows_helpers.py, and projects_files.py with a single call to the helper.

projects_files.py was missing from the original fix: it used quote() without
safe="" and emitted only filename*= with no ASCII fallback.

* test: strengthen Content-Disposition header assertions

- v2/test_files.py: expected_rfc5987 now includes the file extension so a
  regression that strips the extension from the RFC 5987 value is detected
- test_database.py: strengthen the dual-flow download assertion to verify
  both filename= and filename*= params are present, not just the prefix
- test_database.py: rename test_download_flows_non_ascii_content_disposition
  to test_download_flows_content_disposition_dual_param and update the
  docstring to accurately reflect that the ZIP filename is always ASCII
  (timestamp-based); the test now verifies the dual-param format

* fix: sanitize control chars and escape backslash in build_content_disposition

Strip ASCII control characters (CR/LF/NUL and others in 0x00-0x1f/0x7f range)
from filename before encoding to prevent header injection. Also escape backslash
before double-quote in the ASCII fallback per RFC 6266 §4.1 quoted-pair grammar.

* test: add adversarial tests for build_content_disposition and strengthen v2 assertion

Add direct unit tests for the helper covering: quote escaping, backslash escaping,
CRLF/NUL/control char stripping, empty filename, and very long names. Replace
weak substring assertions in the v2 single-file download test with exact equality
against the stored filename returned by the upload response.

* refactor: extract Content-Disposition parser to shared frontend util

Extract the duplicated 14-line RFC 5987 parsing block from use-get-download-files
and use-get-download-flows into a single parseContentDispositionFilename util.
Wraps decodeURIComponent in try/catch to gracefully fall back to the legacy
filename= param on malformed percent-sequences. Adds Jest tests covering all
branches: RFC 5987 priority, legacy fallback, CJK decode, null header, and
malformed input.

* test: replace Portuguese test filename with English equivalent

Rename the accented Latin test case from the Portuguese
arquivo_com_acentuação.txt to naïve_résumé.txt so all test
data uses English while still exercising the same ï/é encoding path.
2026-05-05 15:12:44 +00:00
52ae55b20a docs: deprecate voice to voice websocket endpoint (#12970)
* docs-deprecate-voice-to-voice-endpoint

* docs-explain-voice-mode-vs-speech-to-text
2026-05-05 14:30:31 +00:00
73b30f588b fix(frontend): render node dropdowns above build/notification overlays (#12973)
* fix(frontend): render node dropdowns above build/notification overlays

ReactFlow nodes apply `transform` which creates a local stacking context.
The model selector and generic node dropdown previously rendered without
a Portal, so their `z-50` popover content was trapped inside that
context and got covered by the build status / error notification panel
(also `z-50`, sibling of the canvas).

Switch both `dropdownComponent` and `modelInputComponent` to always use
the portalled `PopoverContent` and bump the popover layer to `z-[60]`.
Mounting the popover into `document.body` escapes the node's stacking
context, and the higher z-index keeps it above the build panel
regardless of portal mount order.

* fix(frontend): flip node dropdowns above build/notification overlays

Prior commit moved the dropdown popovers to a Portal so they could
escape the node's stacking context, which fixed the overlap with the
build status / error notification panel but also pulled the popovers
out of the React Flow viewport's CSS scale transform — the dropdowns
no longer scaled with canvas zoom and looked oversized when zoomed out
and too small when zoomed in.

Switch to a non-portal-based fix: keep the popovers inline in the node
(so they continue to scale with canvas zoom like the rest of the node)
and let Radix flip them above the trigger when the build panel is on
screen.

- Subscribe to `useFlowStore` for `isBuilding` and `buildInfo` and
  derive a `showingBuildPanel` flag in `dropdownComponent` and
  `modelInputComponent`.
- Restore the original `PopoverContentWithoutPortal` selector in both
  files (children / editNode / inspectionPanel keep portaled content).
- Always pass `avoidCollisions` and a dynamic
  `collisionPadding.bottom` equal to `BUILD_PANEL_COLLISION_PADDING_PX`
  (160) when the panel is visible, otherwise `0`. Radix then auto-flips
  the popover above the trigger whenever the bottom region is reserved.
- Move `BUILD_PANEL_COLLISION_PADDING_PX` into
  `constants/constants.ts` with a comment so the magic number is
  defined once and documented.
2026-05-05 14:10:47 +00:00
41083413c0 fix: Duplicate lines rendered in the tracing view (#12944)
flush border top

Co-authored-by: Olayinka Adelakun <olayinkaadelakun@Olayinkas-MacBook-Pro.local>
2026-05-05 13:57:20 +00:00