mirror of
https://github.com/langflow-ai/langflow.git
synced 2026-07-24 04:13:36 +08:00
v1.11.0.dev19
18243 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| 7af6676b88 | Update version for nightly v1.11.0.dev19 | |||
| ce71c72868 |
chore: restore bundles that are now published (#13825)
* chore: restore bundles that are now published * fix: torchvision * flip torchvision sign |
|||
| 711b6b4f61 |
test: remove permanently-skipped dead specs (#13800)
These three spec files contained only unconditionally-skipped tests, so they never executed: store-shard-0 (both use the test.skip modifier), store-shard-3 (both bodies call test.skip()), generalBugs-shard-4 (test.skip(true, ...) disabled, broken with the uplift designs). Zero coverage loss - none ran. Store remains covered by store-shard-2; move-flow remains covered by general-bugs-move-flow-from-folder. Refs LE-1620 (Tier 2) |
|||
| 5cb0edde5a | fix: keep nightly prerelease wheels explicit (#13815) | |||
| b686855a94 |
feat(lfx): add pre-warm functionality (#13682)
* feat(lfx): add `prewarm` command for snapshot/preload warm-up Warm component imports, the graph execution machinery, and specific flows before a Firecracker snapshot or Gunicorn --preload fork, so the first build/run after restore skips the cold lazy-import cost. - prewarm_core_imports(): import core component classes + run a model-free hermetic flow (fork-safe: no network, no threads) - prewarm_flow(flow, run=...): warm a specific flow by building it, or fully executing it for max warmth (drops graph + gc.collect after a run) - freeze_heap(): gc.freeze() for copy-on-write sharing across fork/snapshot - CLI: lfx prewarm with --flow, --skip-run, --freeze, and the gated --unsafe-run-may-leak-connections (Firecracker only; not fork-safe) * feat(lfx): extract shared fork-safety toolkit (lfx.fork) Add lfx.fork with the benign-thread allowlist, ghost-thread / ghost-TCP detectors, a best-effort fork-safety report, and a dispose-in-finally helper. - prewarm_flow now reports fork-hostile state left by a run (ghost_threads / ghost_connections), and the CLI warns when a run leaves connections open (safe for Firecracker restore, unsafe before a Gunicorn/preload fork) - refactor langflow's gunicorn pre_fork hook to use lfx.fork instead of its own duplicated benign-thread allowlist + detection (behavior preserved) * feat(lfx): dispose pluggable services before fork in prewarm path The fork-safe prewarm path (prewarm_core_imports / prewarm_flow) triggers lazy service discovery + instantiation, but never disposed what it instantiated. With only bundled (fork-safe no-op) services this was latent, but once a real lfx.services plugin (a DB pool, external cache socket, or telemetry thread) is registered, the Gunicorn --preload fork would inherit its live fds — the exact hazard Langflow's preload avoids via engine.dispose() / cache teardown() before its master fork. Wire that disposal into lfx: - ServiceManager.teardown(raise_on_error=False): new strict mode that attempts every teardown then re-raises the first failure. Default preserves existing best-effort behavior. - preload.teardown_warm_services(): disposes the global service manager, fatal (PrewarmError) on failure so a half-disposed process is never captured into a fork. - prewarm_core_imports / prewarm_flow gain teardown_services=True (opt-out). prewarm_flow skips teardown on run=True (Firecracker intentionally keeps live connections). Results expose services_torn_down. - The prewarm command centralizes teardown to one pass after all warming (so the per-flow layer doesn't re-instantiate disposed services) and skips it on --unsafe-run. The global manager self-heals after teardown (deps re-registers factories on next access; plugin service_classes survive — only instances drop). * fix(lfx): capture prewarm_flow teardown failure in error, don't raise prewarm_flow promises that one bad flow never aborts a multi-flow warming loop (load/build/run failures go into result.error rather than raising). The service-teardown step ran after the try/finally and could raise PrewarmError, breaking that contract — a direct caller looping over many flows would be aborted mid-batch by a single plugin's teardown failure. Funnel the teardown failure into result.error like the other failures, and document the cross-loop teardown caveat (warming runs in throwaway asyncio loops; a loop-bound plugin resource may fail cross-loop, surfacing as a fail-safe PrewarmError rather than silent corruption). * fix(lfx): deliver input_value to prewarm runs; close fork-safety test gaps Address code-review findings on the prewarm foundation: - preload: wrap input_value in InputValueRequest in _run_flow_once. astep only reads inputs via .model_dump(), so the raw dict was silently dropped and the --unsafe-run flow executed with empty input. - preload: dedup the two async_start loops into _run_flow_once. - server: restore the 'psutil not installed' debug breadcrumb lost when pre_fork moved to the shared lfx.fork helpers. - cli: include failed-import count in the prewarm summary line. Tests (close clean-path-only gaps, same class as the input_value miss): - input_value now reaches the flow output (regression). - find_ghost_connections detects a real open connection (not just the empty path). - prewarm_flow(run=True) surfaces a dirty fork-safety report. - CLI emits the fork-unsafe warning when a run leaves ghost state. - cross-loop service teardown fails loudly as PrewarmError. - idempotent prewarm does not re-import (stable module identity). - tighten warm-vs-cold speedup floor 5x -> 10x; verbose + unsafe-run CLI coverage. - autouse fixture isolates the global ServiceManager between tests. * chore(lfx): trim reviewer-facing comments and dedup prewarm tests Cleanup pass on the prewarm foundation, no behavior change: - tighten multi-sentence 'why' comments/docstrings to one natural line in preload, fork, _prewarm_commands, and manager.teardown. - drop the tautological BENIGN_THREAD_PREFIXES membership test (the behavioral ghost-thread tests already cover it). - reuse the _write_hermetic_flow helper in test_prewarm_flow_build_only instead of inlining the flow construction. * fix(lfx): always run build-only teardown in prewarm_flow, even after a build error Address CodeRabbit review on PR #13682: - preload: drop the 'result.error is None' guard so prewarm_flow(run=False) disposes services even when the build failed — a partial build can leave services live and the process fork-unsafe (the library API was exposed; the CLI already did a final centralized teardown). Teardown failures now append to any existing build error instead of overwriting it. - tests: cover build-failure-still-tears-down and the combined-error message. - cli test: drop the fragile fork-unsafe warning assertion (process-global state incl. harness threads makes it flaky under --timeout-method=thread); connection-cleanliness is asserted at the library level instead. * test(lfx): make prewarm speedup test measure cold-start, fix CI flake The perf test timed build+run only (imports happened before the timer), where the warm benefit is just a few x and machine-dependent — it failed on CI py3.14 at 6x vs the 10x floor. Move imports inside the timed region so the test measures the real value: full cold-start (import + build + run) in a fresh interpreter. Import elimination is worth hundreds of x (observed ~700x locally), so a 10x floor now has an enormous, machine-independent margin. * fix(lfx): disable tracing during the prewarm warm-up run The hermetic warm-up run writes nothing to the DB (message store needs a session_id, vertex-build logging a flow_id — the hermetic graph has neither), but lfx and Langflow share one service manager, so in a Langflow process the graph AND each component independently resolve the real tracer and would ship a junk 'prewarm' trace to a configured provider (LangSmith/Langfuse/...) and open a socket, breaking fork-safety. Wrap the run in a _tracing_disabled() context manager that blanks TRACING_SERVICE in the shared manager so every get_tracing_service() returns None (no-tracing build path), restoring the prior state afterward via an object() sentinel that distinguishes 'absent' from 'present-but-None'. Tests: warm-up run never touches a registered tracer; _tracing_disabled restores all three prior states (absent / real service / present-None). * fix(lfx): make fork-safety connection check reliable; drop dead helpers Address review on PR #13682: - deps: declare psutil in lfx (was only transitive via docling/accelerate extras, so a lean 'lfx serve' --preload install silently skipped the TCP ghost-connection check). find_ghost_connections now logs a warning instead of silently returning [] if psutil is ever missing. - cli: emit the fork-unsafe warning even when an --unsafe-run flow errors mid-run. Teardown is skipped on that path, so dirty state left by a failed run must still be announced; the warning is no longer gated behind the success branch. - fork: remove fork_safe_teardown and ForkSafetyReport.is_clean — no production callers (they belong to the follow-up gunicorn PR), plus their now-dead tests/imports. * fix(lfx): await-free all-types lookup so flows load (un-awaited coroutine) AllTypesDict.get_type_dict called the async get_all_types_dict without awaiting it, so the sync all_types_dict property did {**coroutine} and raised "'coroutine' object is not a mapping". This path is hit during graph build when a vertex falls back to resolve its base_type, so any flow reaching it failed to load (lfx run and lfx prewarm --flow alike). Use the sync build_custom_components instead — it is exactly what get_all_types_dict awaits internally (verified equivalent: identical categories/components/templates; only build-time timestamps differ, even sync-vs-sync). The consumer path is sync, so it must not be a coroutine. Verified: 21/33 starter templates warm via --flow end-to-end; the rest fail only on missing optional component SDKs (trustcall, langchain_chroma, ...), not parsing. |
|||
| d5005244f0 |
Revert "fix: keep tests and the starter-projects endpoint green while bundles are temporarily unpublished (#13809)"
This reverts commit
|
|||
| a42af78fe6 |
test: decouple core tests from disabled bundles (#13805)
* test: decouple core tests from disabled bundles * chore: auto-bake note keys and regenerate backend locales/en.json [skip ci] * chore: trigger ci * test: remove datastax bundle test-only community dependency * test: fix bundle-independent frontend specs --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> |
|||
| b565a14bdc | fix: keep tests and the starter-projects endpoint green while bundles are temporarily unpublished (#13809) | |||
| d2648b95c7 |
test: guard temporarily-disabled bundles in component/template tests + skip Windows-flaky model spec (#13807)
test: guard disabled bundles in component/template tests and skip Windows-flaky model spec |
|||
| cf9cabdd5a |
fix: guard temporarily-disabled bundle deps across backend + frontend tests (#13801)
fix: guard temporarily-disabled bundle deps in backend tests and Cuga component |
|||
| 9121161cda |
fix: skip provider-component tests while bundles are temporarily disabled (#13797)
fix(frontend): skip provider-component tests when bundles temporarily disabled |
|||
| ecd7888161 | fix: temporarily disable unpublished bundle deps | |||
| e7bf0da0da | fix: include sdk wheel in bundle smoke test | |||
| 3f4c1a0a55 | fix: unblock nightly bundle install resolution | |||
| 1cb31f5153 | fix: cap composio client version | |||
| 4a3622b417 |
chore: release cleanup (#13789)
* chore: release cleanup * chore: clean store service comment * [autofix.ci] apply automated fixes * merge release branch * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> |
|||
| 4a8b47e46a |
Merge remote-tracking branch 'origin/release-1.10.1' into release-1.11.0
# Conflicts: # .secrets.baseline # pyproject.toml # src/backend/base/langflow/initial_setup/starter_projects/Pokédex Agent.json # src/backend/base/langflow/initial_setup/starter_projects/Structured Data Analysis Agent.json # src/backend/base/langflow/services/auth/service.py # src/backend/base/langflow/services/job_queue/service.py # src/backend/base/pyproject.toml # src/backend/tests/unit/components/models_and_agents/test_cuga_agent.py # src/backend/tests/unit/test_redis_job_queue_service.py # src/bundles/lfx-bundles/src/lfx_bundles/deepseek/deepseek.py # src/bundles/lfx-bundles/src/lfx_bundles/glean/glean_search_api.py # src/bundles/lfx-bundles/src/lfx_bundles/homeassistant/home_assistant_control.py # src/bundles/lfx-bundles/src/lfx_bundles/homeassistant/list_home_assistant_states.py # src/bundles/lfx-bundles/src/lfx_bundles/huggingface/huggingface_inference_api.py # src/bundles/lfx-bundles/src/lfx_bundles/lmstudio/lmstudioembeddings.py # src/bundles/lfx-bundles/src/lfx_bundles/xai/xai.py # src/frontend/package-lock.json # src/frontend/package.json # src/lfx/pyproject.toml # src/lfx/src/lfx/_assets/component_index.json # src/lfx/src/lfx/components/files_and_knowledge/filesystem.py # src/lfx/tests/unit/components/tools/test_filesystem_deny_list.py # uv.lock |
|||
| 6453fa8a75 | ci: throttle bundle publishes to PyPI | |||
| 9e2b8a1beb |
feat: wire list_visible_resource_ids prefilter into list endpoints (#13541)
* feat: wire list_visible_resource_ids prefilter into list endpoints Wire BaseAuthorizationService.list_visible_resource_ids() into the flows, projects, and deployments list endpoints so a registered authorization plugin can prefilter visible rows at the DB layer instead of fetching every candidate and filtering in memory (which defeats the hook at large-tenant scale). - Add visible_id_prefilter() (gates on AUTHZ_ENABLED; returns None for the OSS pass-through) and restrict_to_owned_or_visible() (the documented owner-rows ⊕ visible-ids SQL union, applied before pagination so totals stay accurate) to the authorization listing module. - read_flows / read_projects / read_project: when the plugin returns a concrete id list, widen the owner-scoped query to (owned ⊕ visible) in SQL and skip the per-row filter_visible_resources (no N+1). None (OSS) preserves today's owner-scoped query + in-memory filter exactly. - deployments: thread allowed_ids through list_deployments_synced into list_deployments_page and count_deployments_by_provider so the page query and its total share the prefilter. OSS pass-through (list_visible_resource_ids -> None) keeps every list endpoint byte-for-byte unchanged; existing tests pass unedited. * fix(authz): align list-prefilter null-owner semantics with the in-memory fallback The DB-layer prefilter folded a `user_id IS NULL` term into the owner clause, so the SQL union (`id IN (visible) OR user_id = me OR user_id IS NULL`) returned every null-owner row unconditionally, while the in-memory fallback policy-checks them via batch_enforce (filter_visible_resources' owner_extractor returns None, which never == user_id). The SQL path was strictly broader than the fallback. Keep the `IS NULL` term in the fallback clause only; the prefilter union now uses an owned-only clause, so a null-owner row is visible only when the plugin lists its id. OSS behavior (prefilter declined -> None) is byte-for-byte unchanged. - read_projects / read_flows: prefilter passes an owned-only owner_clause; the legacy null term stays in the fallback (flows' is AUTO_LOGIN-only, reachable since AUTHZ_ENABLED and AUTO_LOGIN are independent flags) - deployment crud _scope_to_owner_or_allowed: delegate the list branch to restrict_to_owned_or_visible (widened from SelectOfScalar[T] to bound=Select[Any] so it accepts the multi-column page query), dropping the third copy of the owner-or-visible union; the None branch stays local to avoid a degenerate IN () - regression tests via a prefilter-returning authz double (the PolicyTest double only exercises the fallback); verified RED against the buggy clause Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(authz): relax deployment-list provider gate so the prefilter can widen cross-user shared listing GET /deployments resolved the provider account with the strict owner gate (get_owned_provider_account_or_404) before the authz prefilter ran. A shared deployment lives under its *owner's* provider account, so a caller granted READ on it 404'd on that gate and never reached the (owner ⊕ visible) SQL union — the prefilter only helped for foreign-owned rows under a provider the caller already owned. Compute visible_id_prefilter up front; when it returns a non-empty list (registered authz plugin engaged), resolve the provider account by id alone via get_shared_listing_provider_account_or_404 so the shared row can surface. The union + ensure_deployment_permission still govern which rows are returned, and the list response exposes no provider secrets (only provider_key is read). OSS pass-through returns None and load_from_provider keeps the strict owner gate, so default installs are byte-for-byte unchanged. An empty prefilter list also keeps the strict gate (no extra visibility to surface). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
|||
| a66b75ac26 | chore: require bumped sdk for lfx release | |||
| f85901e49d | chore: bump sdk release version | |||
| 4c8fb6b954 | fix: gate release rc setup for stable releases | |||
| 12d8128356 |
feat(frontend): RBAC permission gating via /authz/me/permissions (#13543)
* feat(frontend): RBAC permission gating via /authz/me/permissions The backend POST /api/v1/authz/me/permissions endpoint had zero frontend consumers, so the UI showed every action regardless of the user's RBAC permissions — denials surfaced only as failed requests. This adds the OSS permission-gating primitive that consumes it. - useGetEffectivePermissions: typed React Query hook for the batch endpoint. Modeled as a cached query keyed by the requested resource set (POST body); caps resource_ids at 500 and omits actions/domain when defaulted. - PermissionsProvider context + usePermissions().can(id, action). Providers are mounted per list/surface (homePage, folder sidebar, deployments-content, flow toolbar); leaf components are pure consumers. - Gating applied to flows (Edit/Export/Duplicate/Delete + drag-to-move and the canvas Run/Share/Deploy), projects/folders (Rename/Download/Delete), and deployments (Test/Update/Delete). - Fail-open by design: when permission data is absent (loading, errored, or no provider) or authz is off (OSS pass-through returns every action for every id), all controls stay enabled — no change for non-RBAC installs. - Standalone tests for the pure gating logic, the hook request shape, and provider/component gating (fail-open default, denied-action gating, and that a gated control does not fire its handler). The Share modal (3.9) and Roles/Audit admin UIs (4.6/4.7) are FE(EE) and out of scope; this builds the OSS primitive those EE surfaces also depend on. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(frontend): address CodeRabbit review on permission gating - deploy-dropdown: gate only the publish controls on write permission; the Share menu trigger stays enabled so API access, export, MCP, and embed remain available to read-only users - flow-toolbar-options: scope the toolbar permissions query to the flow's project domain, matching the HomePage list scoping - deployments-content: defensively filter falsy deployment ids before passing them to the permissions query - homePage: scope default-collection permissions to myCollectionId so the implicit route evaluates the same project domain as the explicit folder route * fix(frontend): gate home-page bulk delete/download by permission The bulk toolbar (delete/download) rendered outside the PermissionsProvider and was always enabled, while the per-card menu gated the same delete/read actions on the same flow ids. Lift the provider above HeaderComponent and disable the bulk buttons unless every selected flow allows the action, so the single-item and bulk entry points stay consistent. Fail-open is preserved for non-RBAC installs. * feat(frontend): add OSS no-op share-action seam to flow dropdown Introduces `custom-flow-share-action` customization stub (renders null in OSS) and wires it into the flow dropdown menu between Duplicate and Delete. Follows the existing custom-store-sidebar build-time swap pattern so the Enterprise build can inject the Share menu item + dialog without changing OSS behavior. Props are id-based (resourceId / resourceType / resourceName) and typed via PermissionResourceType for reuse across resource types. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
|||
| 7896b9f7f1 |
feat(bundles): metapackage split (Phase A) — engine-only lfx, lfx-bundles long tail, 5 graduated partner packages (#13563)
* feat(extension): add manifest-less lfx.bundles discovery + precedence tier Foundation for the bundle metapackage split (1.11). Adds a third @official-slot discovery source: a distribution declaring the [project.entry-points."lfx.bundles"] group ships a package whose immediate subdirectories are each a manifest-less bundle, folder-walked and registered at @official with no extension.json (the langchain-community model). - new loader/_bundles_root.py::load_lfx_bundles_extensions, mirroring discover_inline_bundles but sourcing roots from the lfx.bundles entry-point group via find_spec and reusing _load_bundle_directory - discovery precedence becomes installed > seed > lfx_bundles > dev > inline so a manifest-shipping lfx-<provider> always shadows the same-named provider in the metapackage (lets a provider graduate with no lockstep release), emitting the existing bundle-shadowed warning - new bundle-discovery-malformed warning code, emitted on warnings (never flips ok / aborts startup) for unresolvable declarations and invalid provider folder names - exported via loader/__init__ and the lfx.extension PEP-562 lazy surface 10 new tests in test_load_lfx_bundles.py; full extension unit suite (449) passes; ruff + format clean; mypy clean on the new module. * docs(bundles): changelog entry for the lfx.bundles discovery surface The BUNDLE_API.md changelog gate diffs each PR's branch against main, so the entry covering this PR's surface additions (lfx.bundles discovery, the lfx_bundles precedence tier, bundle-discovery-malformed) must live on this branch, not only further up the stack. Text is verbatim from the graduate-partners commit so the stacked merge dedupes trivially. * fix(extension): reject plain-module lfx.bundles targets; correct validate wording CodeRabbit review fixes on #13563: - _spec_package_dir no longer falls back to a plain module's parent directory -- a single-file entry-point target now emits the bundle-discovery-malformed warning instead of folder-walking unrelated sibling directories as bundles (+ regression test) - BUNDLE_API.md / loader docstring no longer say manifest-less providers are 'exempt from lfx extension validate'; they are not valid validator input (validate requires a manifest and reports manifest-not-found) * fix(extension): review fixes — shadowed metapackage providers never import; typed manifest-less reload refusal Review findings on #13563: - A manifest-less provider whose bundle name is already claimed by a higher-precedence @official source (installed/seed) is now skipped WITHOUT importing: all @official sources share the _lfx_ext.official.<bundle>.* sys.modules namespace, so importing the losing copy overwrote the winner's live modules even though _resolve_bundle_shadowing dropped its components afterwards. The orchestrator passes the claimed-name map (built by the new _claimed_official_bundles helper, mirroring the resolver's winner rule); the skipped copy still emits the typed bundle-shadowed warning. This collision is the expected post-graduation state, not an error. - Manifest-less records now carry a manifestless provenance flag (additive field on LoadResult + BundleRecord). The reload pipeline refuses them with the new typed reload-manifestless-unsupported code instead of routing through load_extension and failing with a misleading manifest-not-found. Hot reload of metapackage providers is a process-restart concern (pip-installed content); a loader-based reload path can follow if QA wants it. BUNDLE_API.md changelog entry extended for both surface changes. * fix(extension): harden lfx.bundles discovery per review — broad find_spec guard, namespace portions, per-mode error codes Review findings from ogabrielluiz on #13563: - find_spec on a dotted declaration imports the PARENT package, whose __init__ can raise anything; a non-Import error escaped the old catch tuple and (through the palette cache's catch-all) wiped every source's components for the boot. Catch Exception and degrade to the malformed sentinel; comment corrected. - Namespace packages: walk ALL submodule_search_locations portions (one root per portion) instead of only locations[0], and dedupe resolved roots by path so duplicate entry-point declarations (or overlapping portions) never walk a directory twice and self-shadow. _spec_package_dir -> _spec_package_dirs. - Split bundle-discovery-malformed into one code per failure mode, mirroring the inline tier: bundles-provider-name-invalid (rename the directory), bundles-root-unreadable (check permissions), keeping bundle-discovery-malformed for unresolvable declarations (fix the entry-point). Same-tier duplicates get duplicate-lfx-bundles-provider instead of overloading bundle-shadowed, whose rendered template (format_extension_error renders templates, not ad-hoc messages) was wrong on both counts for that case. - The claimed-bundles cross-source skip now emits bundle-shadowed on errors (matching _resolve_bundle_shadowing) so filtering by code never mixes severities; bundle-shadowed is already in the CLI warn-only set. 3 new regression tests (raising parent package, namespace portions, duplicate entry points); BUNDLE_API changelog updated. * feat(bundles): create lfx-bundles metapackage skeleton (#13564) * feat(bundles): add lfx-bundles metapackage skeleton Creates the manifest-less lfx-bundles distribution (the langchain-community model) that the long-tail providers will move into. Empty skeleton for now; the bulk move (scripts/migrate/consolidate_bundles.py) populates the provider folders + per-provider extras later. - src/bundles/lfx-bundles: a single pyproject declaring the lfx.bundles entry-point (lfx_bundles = "lfx_bundles"), an lfx>=1.10.0,<2.0.0 pin, and a generated (currently empty) `all` extra; plus a bare lfx_bundles namespace package and a README documenting the model + install stories - wired into the root workspace via the existing bundle marker blocks: dep lfx-bundles[all]>=1.0,<2.0, uv source, and member - hyphen dir name so release.yml's src/bundles/*/pyproject.toml glob builds it with zero workflow change Verified: the wheel builds (entry_points.txt carries the lfx.bundles group); load_lfx_bundles_extensions (PR-1) discovers the entry point and the empty skeleton registers zero providers with no error. * fix(ci): nightly bundle rename follows [extras] refs and self-refs Two gaps in update_bundle_versions.py around extras suffixes, both fatal or silently wrong for the first nightly carrying the lfx-bundles metapackage: - update_root_pyproject_for_bundle's dep regex required the version specifier immediately after the name, so "lfx-bundles[all]>=1.0,<2.0" (a MAIN dep) was left unrewritten while the workspace member was renamed to lfx-bundles-nightly; uv lock then tries to resolve stable lfx-bundles from PyPI, where it does not exist. The same gap silently left "lfx-docling[local]>=0.1.0" optional-dep refs pointing at the stable distribution. The regex now tolerates an [extras] group and carries it into the replacement. - rename_bundle_pyproject skipped self-referencing extras, so the metapackage's generated `all` extra kept 45 "lfx-bundles[<provider>]" members after the rename, pulling the stable distribution (same lfx_bundles import package, install collision) once published. Self refs now follow the rename, idempotently. Tests drive the real script module, mirroring test_bundle_lfx_pin.py. This closes the PR-2 audit item flagged when the metapackage was introduced. The canonical-pre-release cutover would retire the nightly rename entirely; until it lands, the rename must be correct. * fix(bundles): address review — drop orphaned nightly bundle-rename script, fix README install stories - Delete scripts/ci/update_bundle_versions.py and its tests: the nightly bundle-rename track it served was retired by the canonical pre-release cutover (src/bundles/NIGHTLY.md) and no workflow or Makefile target on main or any release branch invokes it. Stale doc references in sync_bundle_lfx_pin.py and test_bundle_lfx_pin.py updated to match. - README: split the install section into what works today (langflow, bare lfx) vs what arrives with the bulk move / engine-only split (lfx[bundles], per-provider lfx-bundles[<provider>] extras), and note the generated all extra is empty until the first provider tranche lands. * ci(bundles): cross-bundle test matrix (lfx contract axis) (#13566) * ci(bundles): add cross-bundle test matrix (lfx contract axis) Tests every extracted bundle (the lfx-bundles metapackage + each graduated lfx-<provider>) against the lfx contract surface it depends on -- overdue since 1.10 left 4 independently-versioned bundles depending on lfx. - .github/workflows/cross-bundle-test.yml: discovers bundles via the same src/bundles/*/pyproject.toml glob release.yml uses, then per (bundle x python 3.10/3.13): installs the in-repo lfx + the bundle, imports the declared entry-point package, asserts lfx.bundles discovery is error-free (for the metapackage), runs `lfx extension validate` for manifest bundles, and runs the bundle's own tests/. - Triggers: pull_request (src/bundles, src/lfx), workflow_dispatch, a weekly schedule, and workflow_call. The lfx-minor axis seeds with the in-repo lfx (the 1.10 line is unpublished); when minors publish, add the version dimension (oldest+latest get full tests, every supported minor gets contract-smoke) per the epic's cost-control shape. Verified: the workflow YAML parses and the contract-smoke logic imports a real bundle (lfx_arxiv) cleanly. * fix(ci): cross-bundle smoke tolerates extras-less SDK degradation Review findings on the cross-bundle matrix: - CRITICAL: the contract smoke asserted zero discovery errors, but the CI venv installs lfx-bundles WITHOUT per-provider extras, so providers whose modules import their SDK at top level degrade with module-import-failed -- the expected graceful-degradation contract. The smoke now fails only on structural codes and reports the degraded-module count. (Graduated partner bundles carry their deps directly, not as extras, so their steps are unaffected.) - the scheduled run now actually delivers the exhaustive grid the header promised (all supported Pythons on schedule; oldest+latest on PRs) - concurrency group with cancel-in-progress on PRs so stacked bundle PRs don't queue N-bundles x N-pythons jobs per push * ci(bundles): tomli fallback for the py3.10 cross-bundle smoke tomllib is stdlib only from Python 3.11, so the contract-smoke heredoc failed with ModuleNotFoundError on every py3.10 matrix leg. Install the tomli backport into the smoke venv and import it as a fallback. * fix(test): coherent sys.modules restore in the ibm without_ibm_db fixture The cross-bundle matrix's py3.10 leg exposed test-ordering pollution in the ibm bundle suite: without_ibm_db monkeypatch-deleted three lfx_ibm modules and re-imported them mid-test, but entries created during the test survive teardown (delitem with raising=False records nothing for keys it didn't find) and re-imported parents never regain the submodule attribute bindings that mock.patch target resolution walks on Python 3.10 (3.11+ mock resolves via sys.modules and tolerates the incoherence). Two fixture uses in sequence left the tree inconsistent and 29 later watsonx tests failed with AttributeError on the package. Snapshot, drop, and restore the entire lfx_ibm module tree wholesale instead. Full suite now passes on both 3.10 and 3.13. * feat(lfx): add lfx[bundles] extra and keep lfx engine-only (#13565) * feat(bundles): add lfx-bundles metapackage skeleton Creates the manifest-less lfx-bundles distribution (the langchain-community model) that the long-tail providers will move into. Empty skeleton for now; the bulk move (scripts/migrate/consolidate_bundles.py) populates the provider folders + per-provider extras later. - src/bundles/lfx-bundles: a single pyproject declaring the lfx.bundles entry-point (lfx_bundles = "lfx_bundles"), an lfx>=1.10.0,<2.0.0 pin, and a generated (currently empty) `all` extra; plus a bare lfx_bundles namespace package and a README documenting the model + install stories - wired into the root workspace via the existing bundle marker blocks: dep lfx-bundles[all]>=1.0,<2.0, uv source, and member - hyphen dir name so release.yml's src/bundles/*/pyproject.toml glob builds it with zero workflow change Verified: the wheel builds (entry_points.txt carries the lfx.bundles group); load_lfx_bundles_extensions (PR-1) discovers the entry point and the empty skeleton registers zero providers with no error. * feat(lfx): add lfx[bundles] extra and keep lfx engine-only lfx ships no bundles by default. The new optional extra pulls the long-tail metapackage for users who want the provider components without the full langflow server install. - src/lfx/pyproject.toml: [project.optional-dependencies] bundles = ["lfx-bundles[all]>=1.0,<2.0"]; intentionally NO lfx[all] - pip install lfx -> engine only pip install "lfx[bundles]" -> engine + the lfx-bundles long tail (documented as a headless/serverless deployment footnote, not a headline) Verified against the built lfx wheel METADATA: lfx-bundles appears only under `extra == "bundles"`, never in core Requires-Dist, so the engine-only default is preserved. * fix(ci): nightly bundle rename follows [extras] refs and self-refs Two gaps in update_bundle_versions.py around extras suffixes, both fatal or silently wrong for the first nightly carrying the lfx-bundles metapackage: - update_root_pyproject_for_bundle's dep regex required the version specifier immediately after the name, so "lfx-bundles[all]>=1.0,<2.0" (a MAIN dep) was left unrewritten while the workspace member was renamed to lfx-bundles-nightly; uv lock then tries to resolve stable lfx-bundles from PyPI, where it does not exist. The same gap silently left "lfx-docling[local]>=0.1.0" optional-dep refs pointing at the stable distribution. The regex now tolerates an [extras] group and carries it into the replacement. - rename_bundle_pyproject skipped self-referencing extras, so the metapackage's generated `all` extra kept 45 "lfx-bundles[<provider>]" members after the rename, pulling the stable distribution (same lfx_bundles import package, install collision) once published. Self refs now follow the rename, idempotently. Tests drive the real script module, mirroring test_bundle_lfx_pin.py. This closes the PR-2 audit item flagged when the metapackage was introduced. The canonical-pre-release cutover would retire the nightly rename entirely; until it lands, the rename must be correct. * ci(bundles): freeze lfx/components against new top-level providers (#13567) * feat(bundles): add lfx-bundles metapackage skeleton Creates the manifest-less lfx-bundles distribution (the langchain-community model) that the long-tail providers will move into. Empty skeleton for now; the bulk move (scripts/migrate/consolidate_bundles.py) populates the provider folders + per-provider extras later. - src/bundles/lfx-bundles: a single pyproject declaring the lfx.bundles entry-point (lfx_bundles = "lfx_bundles"), an lfx>=1.10.0,<2.0.0 pin, and a generated (currently empty) `all` extra; plus a bare lfx_bundles namespace package and a README documenting the model + install stories - wired into the root workspace via the existing bundle marker blocks: dep lfx-bundles[all]>=1.0,<2.0, uv source, and member - hyphen dir name so release.yml's src/bundles/*/pyproject.toml glob builds it with zero workflow change Verified: the wheel builds (entry_points.txt carries the lfx.bundles group); load_lfx_bundles_extensions (PR-1) discovers the entry point and the empty skeleton registers zero providers with no error. * ci(bundles): freeze lfx/components against new top-level providers After the metapackage split, new providers go to lfx-bundles (or a graduated lfx-<provider>), never in-tree. Adds an additions-only CI gate. - scripts/ci/check_components_frozen.py: stdlib gate comparing the live top-level dir listing of src/lfx/src/lfx/components/ against a committed baseline; fails on any directory not in the baseline. Removals are allowed so M4 shim cleanup (which shrinks the set) never trips it. - scripts/ci/frozen_component_dirs.txt: the 111-dir baseline. - .github/workflows/lint-py.yml: new freeze-components job runs the gate (stdlib-only, no uv sync). Verified locally: passes on the baseline, fails (exit 1) on a simulated new provider directory with an actionable message, passes again after cleanup. * fix(ci): freeze gate counts only dirs shipping __init__.py Review finding: a stray directory holding only __pycache__ bytecode (left behind by a branch switch) tripped the additions-only gate locally. A directory without __init__.py is not a provider; ignore it. * fix(ci): nightly bundle rename follows [extras] refs and self-refs Two gaps in update_bundle_versions.py around extras suffixes, both fatal or silently wrong for the first nightly carrying the lfx-bundles metapackage: - update_root_pyproject_for_bundle's dep regex required the version specifier immediately after the name, so "lfx-bundles[all]>=1.0,<2.0" (a MAIN dep) was left unrewritten while the workspace member was renamed to lfx-bundles-nightly; uv lock then tries to resolve stable lfx-bundles from PyPI, where it does not exist. The same gap silently left "lfx-docling[local]>=0.1.0" optional-dep refs pointing at the stable distribution. The regex now tolerates an [extras] group and carries it into the replacement. - rename_bundle_pyproject skipped self-referencing extras, so the metapackage's generated `all` extra kept 45 "lfx-bundles[<provider>]" members after the rename, pulling the stable distribution (same lfx_bundles import package, install collision) once published. Self refs now follow the rename, idempotently. Tests drive the real script module, mirroring test_bundle_lfx_pin.py. This closes the PR-2 audit item flagged when the metapackage was introduced. The canonical-pre-release cutover would retire the nightly rename entirely; until it lands, the rename must be correct. * feat(bundles): move 45 long-tail providers into lfx-bundles + graduate 5 partner packages (#13568) * feat(bundles): add lfx-bundles metapackage skeleton Creates the manifest-less lfx-bundles distribution (the langchain-community model) that the long-tail providers will move into. Empty skeleton for now; the bulk move (scripts/migrate/consolidate_bundles.py) populates the provider folders + per-provider extras later. - src/bundles/lfx-bundles: a single pyproject declaring the lfx.bundles entry-point (lfx_bundles = "lfx_bundles"), an lfx>=1.10.0,<2.0.0 pin, and a generated (currently empty) `all` extra; plus a bare lfx_bundles namespace package and a README documenting the model + install stories - wired into the root workspace via the existing bundle marker blocks: dep lfx-bundles[all]>=1.0,<2.0, uv source, and member - hyphen dir name so release.yml's src/bundles/*/pyproject.toml glob builds it with zero workflow change Verified: the wheel builds (entry_points.txt carries the lfx.bundles group); load_lfx_bundles_extensions (PR-1) discovers the entry point and the empty skeleton registers zero providers with no error. * feat(bundles): consolidate first long-tail tranche into lfx-bundles Adds scripts/migrate/consolidate_bundles.py (the inverse of port_bundle.py -- moves in-tree providers into the manifest-less lfx-bundles metapackage) and runs it on a verified 5-provider tranche: tavily, exa, wikipedia, yahoosearch, wolframalpha. Per provider the script: - moves src/lfx/src/lfx/components/<p>/ -> lfx_bundles/<p>/ (lowercase names); - leaves a fail-soft import shim (first line `# lfx-bundles-shim`) so `from lfx.components.<p> import X` keeps working when lfx-bundles is installed, and raises an actionable ImportError otherwise; - merges the provider's third-party deps into a PEP 685-normalized lfx-bundles extra and regenerates the `all` aggregate. Dep parity holds: `uv sync` is a no-op because those deps were already pulled via langflow-base[complete]; - appends the 4-entry migration block per Component class (28 entries) so saved flows referencing lfx.components.<p>.<Class> migrate to ext:<p>:<Class>@official. To avoid double registration, the in-tree component walk (_load_components_dynamically) now skips shimmed provider dirs, and component_index.json is regenerated (355->348 components, 95->90 modules); the moved providers load only at @official via lfx.bundles discovery. Verified: discovery finds all 5 at @official with no errors; shims resolve; `import lfx.components` still works; the index drops the 7 moved component entries (residual `tools`-category name refs resolve via the shim); ruff clean. First tranche proves the engine; the remaining long-tail scales by extending PROVIDER_DEPS (each provider's deps verified individually -- the careful part). * feat(bundles): consolidate 30-provider tranche 2 into lfx-bundles Extends PROVIDER_DEPS with 30 individually-verified providers and runs the consolidation: vector stores (chroma, clickhouse, couchbase, milvus, mongodb, pgvector, pinecone, qdrant, supabase, upstash, weaviate), model providers (groq, mistral, ollama, perplexity, sambanova), and tools/memory/data (apify, assemblyai, confluence, firecrawl, git, glean, icosacomputing, mem0, needle, scrapegraph, serpapi, unstructured, youtube, zep). Dep verification (the careful part): every spec comes from langflow-base's per-provider extras or its direct dependencies; langchain-community providers carry the wrapper plus the SDK the wrapper lazy-imports (e.g. pgvector, atlassian-python-api); requests is declared explicitly where imported (it is only transitive in today's env); pinecone keeps its python_version<'3.14' marker verbatim. Tranche excludes: providers with langflow imports (vlmrun), provider-specific lfx.base dirs (composio/huggingface/langwatch), case- sensitive names (FAISS/Notion), the openai-SDK family (azure/aiml/deepseek/ litellm/lmstudio/novita/openrouter/vllm/xai/cometapi -- cleaner after PR-8), and the partner set. Dep parity verified at the resolution level: the uv.lock diff is +220 lines of lfx-bundles extras metadata with ZERO packages added or removed (`name =` diff empty), so pip install langflow resolves the identical set. Also: 192 append-only migration entries (48 classes x 4, zero bare-name ambiguities); component_index.json regenerated 348->300 components / 90->60 modules (exactly the moved set, no stale standalone entries); mongodb_atlas.py SLF001 per-file-ignore and the mem0/mongodb detect-secrets baseline entries migrated to the new paths (lint/secrets exceptions travel with moved files); 35 bundles now discover at @official with 55 components; shims verified across categories; 449 extension tests pass. * feat(bundles): consolidate openai-SDK family tranche 3 into lfx-bundles 10 providers that ride the langchain-openai wrapper, deferred from tranche 2 until the partner graduation settled the openai-SDK dep story: aiml, azure, cometapi, deepseek, litellm, lmstudio, novita, openrouter, vllm, xai. Dep verification: every provider declares langchain-openai>=1.1.6; the openai SDK is declared only where a component imports it directly (aiml, deepseek, litellm, lmstudio, vllm, xai -- wrapper-transitive elsewhere); requests declared where imported (cometapi, deepseek, novita, xai); lmstudio's lazy NVIDIAEmbeddings path gets langchain-nvidia-ai-endpoints~=1.0.0. The litellm component drives LiteLLM-served endpoints through the OpenAI client and does NOT import the litellm package -- langflow-base's litellm extra stays put. These providers use the lazy _dynamic_imports __init__ shape; it survives the move unchanged (import_mod resolves via __spec__.parent and lfx.components._importing remains a core helper). Dep parity: uv.lock diff has zero package additions/removals (all specs already resolved via langflow-base[complete]). Also: 56 append-only migration entries (14 classes x 4, zero ambiguities); component_index.json regenerated 300->286 components / 60->50 modules (exactly the moved set); detect-secrets baseline re-keyed; 45 bundles now discover at @official with 69 components; shims verified (azure/xai/litellm/deepseek); ruff clean. * fix(ci): nightly bundle rename follows [extras] refs and self-refs Two gaps in update_bundle_versions.py around extras suffixes, both fatal or silently wrong for the first nightly carrying the lfx-bundles metapackage: - update_root_pyproject_for_bundle's dep regex required the version specifier immediately after the name, so "lfx-bundles[all]>=1.0,<2.0" (a MAIN dep) was left unrewritten while the workspace member was renamed to lfx-bundles-nightly; uv lock then tries to resolve stable lfx-bundles from PyPI, where it does not exist. The same gap silently left "lfx-docling[local]>=0.1.0" optional-dep refs pointing at the stable distribution. The regex now tolerates an [extras] group and carries it into the replacement. - rename_bundle_pyproject skipped self-referencing extras, so the metapackage's generated `all` extra kept 45 "lfx-bundles[<provider>]" members after the rename, pulling the stable distribution (same lfx_bundles import package, install collision) once published. Self refs now follow the rename, idempotently. Tests drive the real script module, mirroring test_bundle_lfx_pin.py. This closes the PR-2 audit item flagged when the metapackage was introduced. The canonical-pre-release cutover would retire the nightly rename entirely; until it lands, the rename must be correct. * feat(bundles): graduate partner set to standalone lfx-<provider> packages (#13573) * feat(bundles): graduate partner set to standalone lfx-<provider> packages Extracts the five partner/flagship providers into manifest-shipping distributions: lfx-openai, lfx-anthropic, lfx-amazon, lfx-datastax, lfx-cohere. Each ships extension.json (lfx.compat ["1"]), a langflow.extensions entry-point, an lfx>=1.10.0,<2.0.0 pin, and is wired into the root workspace marker blocks. Zero flow impact by the bundle-name invariant: ext:<provider>:<Class>@official ids are unchanged. Mechanics: - adopts the five-phase scripts/migrate/port_bundle.py from feat/bundle-mass-extraction (handles shared-base moves, consumer rewrites, surgical index updates, migration-table appends) and runs it per partner with --migration-release 1.11.0 - datastax's shared lfx.base.datastax moves into lfx_datastax.base; its backend unit tests move to src/bundles/datastax/tests (74 pass); its ruff per-file-ignore and its check_component_env_writes ALLOWLIST entry travel; 10 repo consumers rewritten (incl. the vector_store_rag starter project) - amazon_bedrock_converse.py legacy langflow.* imports rewritten to the lfx.* equivalents (thin re-export aliases; behavior-identical) pre-extraction - runtime deps pinned from langflow-base's extras / direct deps (wrapper- guaranteed SDKs stay transitive, parity-exact); --remove-base-extra dropped the openai/anthropic/cohere/aws extras from langflow-base[complete] in favor of the bundle pins; the astradb extra stays (also used outside datastax) - in-tree dirs replaced with marker shims pointing at lfx_<p>.components.<p> (skipped by the in-tree walk; legacy from lfx.components.<p> imports keep working); lfx/components/__init__.py entries kept, consistent with the consolidated providers - validate.py: accept classes whose base is a *derived* Component base (LCVectorStoreComponent / LCToolComponent / ...) -- they inherit the class-level outputs declaration and only override the output method, which the AST-only check could not see; bare Component subclasses keep the strict build/outputs requirement (+ regression test) - BUNDLE_API.md changelog entries added for the branch's surface changes: the lfx.bundles manifest-less discovery group + precedence tier + bundle-discovery-malformed code (from the foundation PR) and the validator acceptance above - 100 append-only migration entries (20 classes x 5 partners x 4 shapes); component_index.json surgically updated 300->280 components / 60->55 modules, sha256 recomputed and verified - detect-secrets baseline re-keyed for moved partner files Dep parity: lock diff adds only the five lfx-* package names; no third-party package added or removed. One benign transitive re-resolution: anthropic SDK 0.105.2 -> 0.109.0 (allowed by langchain-anthropic's range on both sides). Verified: all five register at @official via the installed-manifest tier (extension_id/distribution = lfx-<p>, 20 components); manifest-less lfx_bundles unchanged (35 bundles, disjoint); all 5 shims import; engine-safe; `lfx extension validate` passes for all five; 450 extension unit tests, 60 pilot-upgrade migration tests, 74 moved datastax tests pass; ruff clean. * fix(bundles): shim lfx.base.datastax so stored-flow imports keep resolving The datastax graduation moved lfx.base.datastax into lfx_datastax.base, but saved flows and starter templates (Hybrid Search RAG, TemplateAssistant) embed `from lfx.base.datastax.astradb_base import AstraDBBaseComponent` inside their stored component code fields, which is re-executed verbatim at flow build time. Without a shim that import raises ModuleNotFoundError and the flows fail to build (test-starter-projects red). Mirror the lfx.components shim contract: module-aliasing to lfx_datastax.base, narrow except that only translates a missing bundle (transitive dep failures re-raise untouched), marker-tagged single-file dir, removed at M4 together with the components shims. * chore(bundles): bounded version ranges for curated lfx-* packages (#13576) langflow's pyproject is the release-coordination point now that lfx and the lfx-* bundles release independently. Every curated lfx-* dependency declares a BOUNDED range (>=A,<B), never an exact pin -- exact pins in reusable library metadata create resolver conflicts for downstreams that depend on a different version. Exact pins for reproducibility live in uv.lock and the release-build manifests. - the 4 pilots + 5 graduated partners: >=0.1.0 -> >=0.1.0,<1.0.0 (bundles follow their own 0.1.x cadence) - lfx-bundles[all]>=1.0,<2.0 unchanged (versions with the metapackage contract) - the three lfx-docling[...] optional-dependency refs bounded the same way - policy documented inline in the bundle-deps marker block; the cross-bundle CI matrix verifies the ranges resolve together across the supported lfx axis Verified: uv lock resolves with zero package/version changes (bounds are metadata-only today); the nightly rename's dep regex already matches the bounded form. * test(bundles): lock the in-tree shim contract + breakage audit (#13577) * chore(bundles): bounded version ranges for curated lfx-* packages langflow's pyproject is the release-coordination point now that lfx and the lfx-* bundles release independently. Every curated lfx-* dependency declares a BOUNDED range (>=A,<B), never an exact pin -- exact pins in reusable library metadata create resolver conflicts for downstreams that depend on a different version. Exact pins for reproducibility live in uv.lock and the release-build manifests. - the 4 pilots + 5 graduated partners: >=0.1.0 -> >=0.1.0,<1.0.0 (bundles follow their own 0.1.x cadence) - lfx-bundles[all]>=1.0,<2.0 unchanged (versions with the metapackage contract) - the three lfx-docling[...] optional-dependency refs bounded the same way - policy documented inline in the bundle-deps marker block; the cross-bundle CI matrix verifies the ranges resolve together across the supported lfx axis Verified: uv lock resolves with zero package/version changes (bounds are metadata-only today); the nightly rename's dep regex already matches the bounded form. * test(bundles): lock the in-tree shim contract + breakage audit Locks the five-point contract for the 50 lfx-bundles import shims under lfx/components/ (45 metapackage + 5 graduated partners): 1. first line is the `# lfx-bundles-shim` marker (keys the in-tree walk's double-registration skip); 2. a shim dir is exactly one __init__.py -- no impls, no deps; 3. sys.modules module-aliasing (submodule trees resolve); 4. bundle missing -> actionable ModuleNotFoundError whose wording is part of the contract ("pip install lfx-bundles" / "pip install lfx-<p>"); 5. a missing *transitive* dep re-raises untouched. Test shape (env-adaptive by design -- the lfx test env prunes the venv, so nothing here assumes bundles are installed): - source-level sweep of every real shim, parameterized (no imports); - hermetic mechanism tests against a synthetic shim + synthetic target (alias-resolves / locked-message / transitive-reraise); - the walk-skip detector and the marker sweep must agree exactly; - one adaptive live test on lfx.components.tavily exercising whichever branch the running env is in. 106 tests pass in the pruned lfx env (where the live test exercises the bare-engine locked-message branch). Breakage audit (gh code search, recorded in the PR): ~20-25 public repos reference lfx.components.*; the majority are langflow forks (vendored tree, unaffected by packaging). Genuine library consumers exist and the moved providers have real surface (openai: 11 external repos, datastax: 17) -- covered by the shims wherever bundles are co-installed (every `pip install langflow`). Decision: deprecation warnings NOT warranted now; revisit at M4 (shim removal), where one loud minor release before removal is the right shape. * test(bundles): extend the shim contract sweep to lfx.base shims The datastax graduation moved lfx.base.datastax into lfx_datastax.base and left a module-aliasing shim behind (stored flow code fields embed the legacy import and are re-executed verbatim at build time). Sweep lfx/base for marker shims with the same source-level contract as the components sweep: one-file stub, module-aliasing to the bundle's base subpackage, narrow name-checked except, locked install message. * docs(bundles): install shapes, override rule, bundle_api_version (#13578) Documents the deliberately small user-facing rule surface of the metapackage split (1.11): - extensions-overview.mdx: the two install stories (pip install langflow = everything, same as today; pip install lfx = engine only, bring your own bundles); the bundle-name-is-identity invariant; the current package table (lfx-bundles metapackage + the 5 partner packages + the 4 pilots); the legacy-import shim behavior with the locked ModuleNotFoundError wording and the migration recipe for direct lfx users (switch to lfx[bundles] or pin lfx-<provider> packages); the override rule ("ship a manifest -- a manifest always wins", with the bundle-shadowed warning); bundle_api_version as the single compat number (lfx.compat ["1"]; manifest-less packages ride their PEP 508 lfx pin) and the no-version-arithmetic rule. - deployment-lfx-compatibility.mdx: lfx[bundles] as the headless/serverless deployment footnote (engine + lfx-bundles[all]; slimmer per-provider alternative; intentionally no lfx[all]). Both files verified MDX-safe (no unbackticked angle brackets). * fix(bundles): stack-review fixes — symlink containment, idempotency, docs accuracy (#13579) * chore(bundles): bounded version ranges for curated lfx-* packages langflow's pyproject is the release-coordination point now that lfx and the lfx-* bundles release independently. Every curated lfx-* dependency declares a BOUNDED range (>=A,<B), never an exact pin -- exact pins in reusable library metadata create resolver conflicts for downstreams that depend on a different version. Exact pins for reproducibility live in uv.lock and the release-build manifests. - the 4 pilots + 5 graduated partners: >=0.1.0 -> >=0.1.0,<1.0.0 (bundles follow their own 0.1.x cadence) - lfx-bundles[all]>=1.0,<2.0 unchanged (versions with the metapackage contract) - the three lfx-docling[...] optional-dependency refs bounded the same way - policy documented inline in the bundle-deps marker block; the cross-bundle CI matrix verifies the ranges resolve together across the supported lfx axis Verified: uv lock resolves with zero package/version changes (bounds are metadata-only today); the nightly rename's dep regex already matches the bounded form. * test(bundles): lock the in-tree shim contract + breakage audit Locks the five-point contract for the 50 lfx-bundles import shims under lfx/components/ (45 metapackage + 5 graduated partners): 1. first line is the `# lfx-bundles-shim` marker (keys the in-tree walk's double-registration skip); 2. a shim dir is exactly one __init__.py -- no impls, no deps; 3. sys.modules module-aliasing (submodule trees resolve); 4. bundle missing -> actionable ModuleNotFoundError whose wording is part of the contract ("pip install lfx-bundles" / "pip install lfx-<p>"); 5. a missing *transitive* dep re-raises untouched. Test shape (env-adaptive by design -- the lfx test env prunes the venv, so nothing here assumes bundles are installed): - source-level sweep of every real shim, parameterized (no imports); - hermetic mechanism tests against a synthetic shim + synthetic target (alias-resolves / locked-message / transitive-reraise); - the walk-skip detector and the marker sweep must agree exactly; - one adaptive live test on lfx.components.tavily exercising whichever branch the running env is in. 106 tests pass in the pruned lfx env (where the live test exercises the bare-engine locked-message branch). Breakage audit (gh code search, recorded in the PR): ~20-25 public repos reference lfx.components.*; the majority are langflow forks (vendored tree, unaffected by packaging). Genuine library consumers exist and the moved providers have real surface (openai: 11 external repos, datastax: 17) -- covered by the shims wherever bundles are co-installed (every `pip install langflow`). Decision: deprecation warnings NOT warranted now; revisit at M4 (shim removal), where one loud minor release before removal is the right shape. * docs(bundles): install shapes, override rule, bundle_api_version Documents the deliberately small user-facing rule surface of the metapackage split (1.11): - extensions-overview.mdx: the two install stories (pip install langflow = everything, same as today; pip install lfx = engine only, bring your own bundles); the bundle-name-is-identity invariant; the current package table (lfx-bundles metapackage + the 5 partner packages + the 4 pilots); the legacy-import shim behavior with the locked ModuleNotFoundError wording and the migration recipe for direct lfx users (switch to lfx[bundles] or pin lfx-<provider> packages); the override rule ("ship a manifest -- a manifest always wins", with the bundle-shadowed warning); bundle_api_version as the single compat number (lfx.compat ["1"]; manifest-less packages ride their PEP 508 lfx pin) and the no-version-arithmetic rule. - deployment-lfx-compatibility.mdx: lfx[bundles] as the headless/serverless deployment footnote (engine + lfx-bundles[all]; slimmer per-provider alternative; intentionally no lfx[all]). Both files verified MDX-safe (no unbackticked angle brackets). * fix(bundles): review fixes — symlink containment, idempotency, docs accuracy Fixes from the multi-agent stack review (kept as a separate PR so the reviewed PRs' diffs stay frozen for QA's independent pass): - _bundles_root.py: directory-level symlink containment — a provider dir that resolves outside the bundle root is skipped with a typed bundle-discovery-malformed warning, mirroring the seed-directory walk's is_within rule (+ regression test). Anchors the trust boundary to the installed package tree. - consolidate_bundles.py: migration-append idempotency guard — entries are deduped on full content so a partially-failed earlier run cannot duplicate rows on re-run (table stays append-only). - langflow-base pyproject: documented WHY the aws extra is deliberately retained after the lfx-amazon graduation (boto3 also backs the server's own S3 storage backend and lfx's S3 ingestion — review suggested removing it; that would regress S3 storage support). - extensions-overview.mdx: note that `lfx extension list` shows manifest-shipping extensions only; manifest-less packages load at startup but are not listed. 93 loader+migration tests pass (incl. the new symlink test); ruff clean. * refactor(bundles): stabilize import_mod as a public BUNDLE_API utility The lazy-import helper that bundle packages call from their __getattr__-based __init__.py files lived at lfx.components._importing -- an internal path with no stability contract, imported by 35 separately-installed bundle __init__ files (30 lfx-bundles providers + the 5 graduated partners). - canonical home is now lfx.utils.lazy_import.import_mod (code unchanged); lfx.components._importing re-exports it so in-tree callers and any external code on the old path keep working - all 35 bundle-side imports rewritten to the stable path - BUNDLE_API.md: surface table entry + changelog (additive) - contract tests: re-export identity, both call forms, the AttributeError conversion Verified: identity holds across both paths; lazy __init__ loads work through the new path for both bundle families; 110 tests pass; ruff clean. * fix(bundles): tombstone the broken legacy ZepChatMemory build method (#13580) build_message_history targeted the zep-python v1 SDK (ZepClient + zep_python.langchain.ZepChatMessageHistory); both were removed in zep-python 2.x and the zep extra pins 2.0.2, so the method has been unable to run for as long as the pin has existed -- its ImportError guard misleadingly told users to 'pip install zep-python' (already installed). The component is legacy=True with helpers.Memory as its designated replacement, so rather than hand-write a new integration against the 2.x SDK, the method now raises a clear RuntimeError pointing at the Message History component. Flow identity is preserved: class/component name, display_name, description, inputs and the memory output are byte-identical, so saved flows keep loading, i18n locale keys are unchanged, and migration_table.json needs no edits. New bundle tests pin the stub contract (identity, actionable error, no zep_python import). * test(bundles): extend the shim contract sweep to lfx.base shims The datastax graduation moved lfx.base.datastax into lfx_datastax.base and left a module-aliasing shim behind (stored flow code fields embed the legacy import and are re-executed verbatim at build time). Sweep lfx/base for marker shims with the same source-level contract as the components sweep: one-file stub, module-aliasing to the bundle's base subpackage, narrow name-checked except, locked install message. * test(bundles): extras-drift guard for the lfx-bundles metapackage Risk-2 of the metapackage split: the generated `all` extra and the per-provider extras must never drift by hand-edit, or `pip install langflow` silently loses a provider's deps. Guard the four invariants: extras <-> provider dirs (PEP 685-normalized), `all` == the exact self-ref set, normalized keys collision-free, and the metapackage provider set disjoint from the graduated partner distributions. * fix(extension): symlink-escaped providers reject with path-escape, matching the documented contract The lfx.bundles provider containment check (stack-review symlink fix) emitted bundle-discovery-malformed, whose template and hint describe a broken entry-point declaration. The changelog's path-safety entry already documents symlink escapes as path-escape on every other discovery path; use the same code here. Also resolves the semantic merge conflict with the per-mode code split (the removed _malformed_error location kwarg). * fix(tests): repoint lfx tests off moved providers; complete provider fallback map CI fail-fast had been masking these: the LFX test job runs in an engine-only env where openai/anthropic/chroma are now bundle-package shims, so every test that used them as the example category failed on all Python versions (3.14 just reported first), and the backend Group 2 leg failed collecting test_lfx_bundles_extras.py on Python 3.10. - flow_requirements: complete _PROVIDER_PACKAGE_FALLBACKS for the nine moved model providers (OpenAI, Anthropic, Amazon Bedrock, Groq, Google Generative AI, SambaNova, IBM watsonx.ai, Ollama + existing Azure OpenAI). In engine-only installs MODEL_PROVIDERS_DICT registers only in-tree providers, so the dynamic source-inspection path cannot resolve moved providers; the static fallbacks keep lfx run/serve requirements inference working. A dict hit still wins. - test_dynamic_imports / test_import_utils: use composio (still-in-tree lazy category with its SDK absent in the bare env) as the example category instead of openai/anthropic/chroma; patch import_module at its canonical home lfx.utils.lazy_import. - flow-builder tests (build_flow_from_spec, flow_builder_tools, propose_field_edit): specs use LanguageModelComponent (in-tree) instead of OpenAIModel. - test_lfx_bundles_extras: tomli fallback for Python 3.10 (tomllib is stdlib 3.11+; pytest guarantees tomli on <3.11). Full lfx unit suite: 4550 passed. Backend extras+pin tests: 24 passed. * fix(tests): repoint MCP client-server tests off graduated OpenAIModel key Same fail-fast-masked class as |
|||
| 7db4bcb890 |
fix: degrade gracefully when caching unpicklable values in RedisCache (#13781)
* fix: degrade gracefully when caching unpicklable values in RedisCache RedisCache.set only caught pickle.PicklingError, but dill raises other exception types for inherently unserializable live objects -- a bare TypeError for an ssl.SSLContext, AttributeError for dynamically-created pydantic models, etc. None of these subclass PicklingError, so they escaped the guard and crashed the entire vertex build whenever a flow with a live LLM client (e.g. ChatGoogleGenerativeAI) ran with LANGFLOW_CACHE_TYPE=redis. Serialize in isolation from the network write and treat any serialization failure as an uncacheable value: log a warning and skip the cache write rather than raising. A skipped write just means the value is recomputed on the next access, matching how the in-memory cache already tolerates such objects. Genuine Redis write failures (the setex path) still surface. Fixes #13764 * fix: evict stale Redis entry when skipping an unserializable cache write The upsert path is get -> merge -> set. When set() now skips an unserializable value, any previously-cached entry for that key was left in Redis (up to the 1h TTL), so a subsequent get() could serve stale data instead of recomputing. Delete the key on the skip path so the cache correctly reflects 'no valid value'. |
|||
| 8c98cdf5cc |
fix(security): block socket/urllib network egress in component code scanner (#13784)
* fix(security): block socket/urllib network egress in component code scanner Completes the CVE-2026-33873 / GHSA-v8hw-mh8c-jxfc fix. The AST scanner `scan_code_security()` blocked `subprocess` but omitted `socket` and `urllib`, so LLM-generated / assistant-submitted component code importing `socket.connect()` or `urllib.request.urlopen()` passed the scan and still executed server-side during validation — enabling raw-socket reverse shells, raw exfiltration, and `urllib` SSRF (incl. `file://` local reads and cloud IMDS credential theft). Add the network/IPC stdlib attack class to the blocklist (same class as `subprocess`): - whole modules: socket, socketserver, ftplib, telnetlib, smtplib, poplib, imaplib, nntplib, xmlrpc, pty - submodules (precise, preserving safe siblings): urllib.request, urllib.error, http.client, http.server - os.dup2 / os.dup attribute calls (socket->shell fd redirection) High-level HTTP via `requests`/`httpx` stays allowed by design (legit API components need it), and the safe `urllib.parse` / `from http import HTTPStatus` siblings remain importable. This scanner is defense-in-depth, not a full sandbox (see #12787); residual SSRF via the permitted HTTP clients is unchanged. Adds regression tests covering each blocked module, the reporter PoC payloads, and the safe-sibling no-regression cases. * fix(security): resolve import-alias and wildcard-import scanner bypasses The component-code scanner matched restricted module members only by the literal module name, so `import os as o; o.dup2(...)` (alias) and `from os import *; dup2(...)` (wildcard) slipped past the os.*/sys.* attribute checks — `os`/`sys` are importable as whole modules, only their members are restricted. - track import aliases (incl. `import os.path as p`) and resolve them in the attribute-call and attribute-read checks - track `from <mod> import *` and treat bare references to restricted members as direct attribute access (calls via _check_name_call, reads via visit_Name), using member sets derived from the existing tables so they stay in sync - collect imports in an order-independent pre-pass Safe siblings still pass (`o.path.join`, aliased `requests`, wildcard `getcwd`/`listdir`). Adds regression tests for both bypass patterns plus no-regression cases. * fix(security): flag dotted submodule access (urllib.request/http.client) A bare `import urllib` / `import http` is allowed (the package root is safe for urllib.parse / http.HTTPStatus), but at runtime the assistant import chain has already loaded `urllib.request` and `http.client`, so `import urllib; urllib.request.urlopen(...)` reaches the blocked submodule without an explicit submodule import and scanned as safe — re-opening the SSRF / HTTP-client path. Detect dotted attribute chains that resolve to a blocked submodule in visit_Attribute (alias-resolved on the root name, exact-match per node to avoid double-flagging the chain). Catches the no-import form too (pure runtime-preload reliance) and `import urllib as u; u.request...`. Safe siblings still pass: urllib.parse.*, http.HTTPStatus, os.path.*. Adds regression tests for the bare-import and alias bypass variants. |
|||
| f85a32a052 |
feat: native v2 workflows endpoint with pluggable stream protocols (#13307)
* feat: native v2 workflows endpoint with pluggable stream protocols
Rebased onto release-1.10.0. The base independently rebuilt the v2
workflows backend (RBAC, body globals, share-aware fetch); keep our
forward design and conform its auth to that work:
1. Auth: keep get_current_user_for_workflow (session-or-API-key authN
that does not hold a DB connection during the inline run, avoiding
the SQLite lock contention api_key_security would cause) and enforce
the base's RBAC on top: ensure_flow_permission(EXECUTE) before run,
(READ) before status reconstruct, with widen_for_shares fetch.
2. Port the base's request-body globals onto the v2 WorkflowRunRequest.
The X-LANGFLOW-GLOBAL-VAR-* headers stay supported (the Responses API
passes globals that way); body globals win on conflict. Converters
echo the effective globals via effective_globals.
3. Public endpoint keeps the v1 build_public_tmp posture
(access_type==PUBLIC, run-as-owner); RBAC applies to the
authenticated endpoint only.
4. Preserve the base's post-build KB-cache invalidation in the AG-UI
build path.
The endpoint, AG-UI bridge, pluggable stream adapters, public endpoint,
and re-attach are unchanged.
* feat(api): add output_text and session_id to v2 workflow response
The synchronous /api/v2/workflows response keyed every result under its
component id, so reading the answer meant knowing an id you can't predict.
Surface two additive fields:
- output_text: the flow's single text answer (ChatOutput/TextOutput). None
when the flow has zero or multiple text outputs, so callers read outputs
rather than the shortcut guessing which channel is the answer.
- session_id: echoes the resolved session so chat/memory callers can
continue the same thread (v1 /run returned this; v2 had dropped it).
outputs is unchanged, so this is non-breaking.
* test(api/v2): cover output_text and session_id on the v2 workflow response
Pin the sync-response shortcuts on the v2 workflows endpoint:
- output_text surfaces the lone ChatOutput/TextOutput text and stays None for
non-output message nodes, data-only flows, and multi-text flows
- session_id echoes the resolved session; the error response exposes neither
- each outputs entry exposes only {type, status, content, metadata}, with the
component id carried by the dict key
Also drop the component_id kwarg the converter passed to ComponentOutput, which
has no such field and silently dropped it.
* feat(api/v2): structured output with resolution reason on v2 response
Replace the flat output_text shortcut with an `output` object carrying the
resolved text answer plus a `reason` that explains why it resolved that way
(single/multiple/none/non_string/failed), so a null answer is always
diagnosable instead of silently None. `reason` follows the LLM-domain
finish_reason/stop_reason convention, distinct from the lifecycle status.
Also add `display_name` to each ComponentOutput (the stable component id
stays the dict key) and a computed `has_errors` flag derived from errors.
* feat(api/v2): add request-side output selection (output_ids)
Let a sync caller name the output(s) they want via output_ids so
output.text resolves deterministically (reason=single) on multi-output
flows instead of going null. Selection is steer-only: it picks the
answer among the named outputs without filtering the outputs map.
Invalid ids are rejected with 422 before the flow runs (and before any
job row is created), so a typo costs no compute. Resolution considers
selected outputs that actually fired, so branching flows resolve to
whichever candidate ran.
* feat(api/v2): emit per-output events on the langflow stream
Give v2-workflows sync and the langflow stream protocol one parser. The
stream now emits a normalized "output" event per terminal output carrying
an OutputEvent (the ComponentOutput shape sync returns in outputs[id], plus
component_id). A shared build_component_output() backs both the sync
converter and the adapter, and the build loop ships authoritative vertex
metadata as an additive output_meta key on end_vertex (existing consumers
read build_data and ignore it).
This is access-pattern parity (one parser, same fields, same terminal set),
not byte-identical content: the stream reuses the v1 build path whose
display serialization differs from sync's run_graph output.
* fix(api/v2): enforce no-code-execution gate on public workflow endpoint
The v2 public endpoint only ran validate_flow_for_current_settings and
skipped validate_public_flow_no_code_execution, which the v1
build_public_tmp path applies. A public flow containing a Python
interpreter/REPL (or the legacy Python Code Structured tool, Smart
Transform lambda) was therefore an unauthenticated server-side
code-execution primitive (report H1-3754930).
Mirror v1: import the validator and call it right after the
public-access gate. PublicFlowValidationError subclasses
CustomComponentValidationError, so the existing handler already
sanitizes it to a 400 'This flow cannot be executed.' without leaking
the blocked component class names.
Add a non-mocking test that builds a public flow with a real
PythonREPLComponent and asserts the sanitized 400 (verified RED: returns
200 without the gate).
LE-1389
* fix(api/v2): reconstruct background workflow status from job-keyed vertex builds
A completed background job's GET status 500'd with 'No vertex builds found
for job_id'. The background build path differed from the sync path twice:
1. generate_flow_events minted a fresh run_id instead of using job_id, so
vertex builds were keyed by an id the status query never uses. Thread
run_id through _stream_event_frames -> generate_flow_events and pass
job_id from the background buffer so graph.run_id == job_id (the sync
path already does graph.set_run_id(job_id)).
2. The SSE build loop (build_vertices) only persisted builds when log_builds
was set and never passed job_id. Tie log_builds to job-tracked runs
(run_id present) and pass job_id=graph.run_id on the persist call.
Job-tracked runs also persist streaming terminal vertices so
reconstruction is complete; the live build path (run_id is None) keeps
its original behavior, so the v1 build path is unchanged.
Test: a real background run polled to completion, then GET status asserts a
reconstructed 200 (verified RED: 500 'No vertex builds found' before the
fix). Covers the non-streaming flow. v1 build path unchanged (35 build
tests pass); AG-UI suite 46 pass.
LE-1389
* fix(api/v2): merge workflow AG-UI cancellation hardening LE-1389
* fix(api/v2): signal cross-worker workflow stops LE-1389
* fix(api/v2): report unconfirmed workflow stops LE-1389
* fix(api/v2): keep background workflows out of polling watchdog LE-1389
* fix(api/v2): buffer parallel messages in the AG-UI translator instead of dropping them
Parallel components stream tokens for different message ids interleaved.
The translator tracked a single open message: the first foreign token
closed the open message and tombstoned its id, so every later event for
it was dropped and its remaining text never reached the client.
Tokens for a message that cannot take the wire now buffer until the open
message genuinely ends (its add_message finalizer), then flush in arrival
order; complete messages landing mid-stream buffer the same way instead
of interleaving a second START. end/error drain all buffers before the
terminal event. The wire still carries at most one open text message, so
the stream stays AG-UI-conformant.
* fix(api/v2): gate AG-UI message finalization on non-partial state and purge removed buffers
A partial add_message re-fire (the agent path emits these at tool
start/end for a message it is still streaming) was treated as the
finalizer: it closed and tombstoned the id, so the post-tool answer
was dropped. Only a non-partial add_message finalizes now; state
defaults to complete, so payloads without properties are unchanged.
remove_message now purges a buffered message and tombstones its id,
so text the backend retracted is not flushed to the client later.
* Fix AG-UI workflow lifecycle edges
* [autofix.ci] apply automated fixes
* fix(frontend): enable downlevelIteration for jest Set/Map iteration
ts-jest compiles with target es5; without downlevelIteration, [...set] and
for...of over a Set/Map emit ES5 that yields nothing. That silently broke the
AG-UI bridge tests: runningNodeIds spread, markRunningNodesFailed, and
restoreOriginalBuildStatuses all iterated empty. Production (Vite/SWC, modern
target) was never affected; only the ts-jest harness was. Fixes the 3 failing
jest tests on this branch with no other suite changes (4994/4994 pass).
* fix(api/v2): surface inactivated branch vertices over AG-UI
A branch component (If-Else, Conditional Router) reports its not-taken
vertices in build_data.inactivated_vertices, but the AG-UI translator only
emitted the branch node's own success/error status and dropped that list. The
canvas seeds every planned node as pending from vertices_sorted; skipped
vertices then get no build_start/end_vertex, so they stayed stuck on pending
instead of rendering as inactive (the v1 build path marked them INACTIVE).
The translator now appends an inactive STATE_DELTA op per inactivated vertex,
and the frontend bridge maps the new inactive status to BuildStatus.INACTIVE
and tears its edges down like a completed node. Fixes the If-Else regression
in general-bugs-reset-flow-run.spec.ts.
* fix(api/v2): dedupe repeated inactive node deltas in AG-UI stream
build.py keeps reporting a conditionally-excluded vertex in
inactivated_vertices on every subsequent end_vertex (the excluded set
persists until the ConditionalRouter clears it), so the translator was
putting the same inactive STATE_DELTA on the wire once per remaining
vertex. Track emitted inactive nodes and skip re-emitting; drop a node
from the set when it actually runs again (build_start/end_vertex) so a
loop re-activation can still re-emit inactive later.
* fix(api/v2): address review findings on the v2 workflows endpoint
- recover session_id for completed background jobs from the persisted
terminal message instead of always returning null, so GET status can
continue the same chat/memory thread
- replay a user-cancel as a CUSTOM cancel marker + RUN_FINISHED (agui) and
a `cancelled` terminal (langflow) instead of RUN_ERROR, so a re-attaching
client no longer reads a deliberate stop as a failure
- cancel the evicted still-running buffer writer when the background-run
registry is full, so it stops appending into a run no reader can find
- derive per-component status from the error artifact / valid flag instead
of hardcoding COMPLETED, and stop the langflow adapter dropping `valid`
- throttle the unauthenticated public endpoint per IP and bound its
input_value/session_id length
- document the sync-only scope of request-body globals
- document that live event re-attach is intentionally owner-only
* test(lfx): register public_flow_rate_limit_per_minute in settings composition
* refactor(v2 workflows): split workflow.py and address review blockers
Splits the ~1.5k-line workflow.py into focused modules and folds in the
execution-timeout and error-sanitization fixes from Cristhianzl's review of #13307.
- B1: workflow.py now holds only the four route handlers. Validation guards move
to workflow_validation, the sync/stream run loop to workflow_execution, and the
durable background machinery to workflow_background (layered, acyclic).
- I1: add workflow_execution_timeout (default 300) and apply a single wall-clock
ceiling across sync, stream, background, and public via _stream_event_frames. A
timeout becomes a sanitized terminal error and marks a background job failed.
- I3: the route error handlers no longer echo raw exception text. They return a
generic, code-tagged message and log the full exception server-side.
- R1: remove the "commented out / future scope" comments that sat over live
dataframe-extraction code in converters.py.
- R4: drop the worker-routing internals from the reattach 409 message.
Tests cover the timeout terminal-error path and the error-body sanitization, and
the settings field-count guard is updated for the new setting.
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
|
|||
| f58914bc84 |
fix: release candidate version selection (#13776)
* fix: share release candidate version selection * fix: review comments addressed1.10.1rc3 |
|||
| 2302ef9931 |
fix(component): reconnect stale cached SQL database connections on SQL Database component (#13733)
* Implement check for stale cached database connection Add stale connection check for cached database. * [autofix.ci] apply automated fixes * fix(component): use SQLAlchemy pre-ping for SQL database cache * [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Eric Hare <ericrhare@gmail.com> |
|||
| 408b737980 |
fix(agents): stop leaking LangChain chain markers to stdout (#13662) (#13730)
* fix(agents): stop leaking LangChain chain markers to stdout The Agent verbose input defaulted to True and was passed straight to LangChain's AgentExecutor, which attaches a StdOutCallbackHandler that prints "> Entering new ... chain" / "> Finished chain." via print() -- bypassing Langflow's logging entirely and flooding container/k8s/OpenShift stdout logs on every Agent execution, even with LANGCHAIN_VERBOSE=false. Gate the markers on the LANGCHAIN_VERBOSE env var (off by default) via a new resolve_agent_verbose() helper, wired into the legacy AgentExecutor paths (base LCAgentComponent.run_agent/get_agent_kwargs, ALTK base agent, CSV Agent). The env var is the conventional LangChain switch; set LANGCHAIN_VERBOSE=true to restore the markers. The component verbose input no longer attaches the stdout handler on its own. Agent steps remain visible in the UI via the existing event stream. Restamps the CSV Agent component-index code_hash + index sha256. Fixes #13662 * fix(agents): surface LANGCHAIN_VERBOSE gating in the verbose input info (#13662) Address review I1: after gating stdout chain markers on LANGCHAIN_VERBOSE, the legacy AgentExecutor agents still showed a Verbose toggle whose value is no longer read, so the UI control silently did nothing. Add an info string to LCAgentComponent's shared verbose BoolInput explaining the markers are now gated on LANGCHAIN_VERBOSE (off by default) and the toggle no longer attaches LangChain's stdout handler, so the UI and behavior agree. The main Agent already drops this input (create_agent path); the change surfaces in the legacy LCAgentComponent agents that inherit get_base_inputs() (CSV/JSON/SQL/XML/OpenAPI/OpenAI Tools/Tool Calling/Vector Store Router + Cuga). Restamps the component index for those 9 inherited entries. * chore: auto-bake note keys and regenerate backend locales/en.json [skip ci] * Update component_index.json * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) * [autofix.ci] apply automated fixes * fix(agents): document ALTK verbose env gate * [autofix.ci] apply automated fixes --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> |
|||
| 0300305dc5 |
docs: fix empty see also sections in extensions (#13774)
* docs: fix empty see also sections in extensions * docs: update 1.10x version |
|||
| f39416b806 |
fix: make IBM WatsonX models selectable and runnable in the Langflow Assistant (#13771)
* fix ibm models integration on assistant * update assistant docs * [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> |
|||
| 0125c386ac |
fix(i18n): fix MCP hardcoded strings (#13621)
* fix(i18n): wrap hardcoded MCP auto-install strings with t() Adds mcp.serverNotRunning, mcp.installDisabledWarning, mcp.installTooltip, and mcp.failedToInstall keys to en.json and replaces the hardcoded strings in McpAuthSection, McpAutoInstallContent, and useMcpServer. Downloads updated translations for all 6 locale files from GP. * fix(i18n): wrap hardcoded playground no-input strings with t() Adds playground.runFlow and playground.noInputHint keys to en.json and replaces hardcoded strings in both no-input.tsx components (playgroundComponent and IOModal). Reuses flowBuild.stop for the Stop button. Uses Trans for the hint paragraph with embedded Chat Input link; works around react-i18next v16 conditional type by casting to React.FC<TransProps<string>>. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(i18n): route welcome-screen template apply through resetFlow for translations useApplyTemplateToCurrentFlow was calling setNodes() / setEdges() directly and then useFlowStore.setCurrentFlow() (metadata-only), bypassing the resetFlow → syncNodeTranslations pipeline. This meant component display names and descriptions were never translated to the active language when a user selected a starter template from the welcome overlay. Fix: use useFlowsManagerStore.setCurrentFlow() instead, which calls resetFlow and therefore syncNodeTranslations with the already-loaded typesStore data. Keep the direct setNodes/setEdges path as a fallback when currentFlow is null. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore(i18n): sync all locale files from GP Frontend: adds playground.runFlow and playground.noInputHint translations for all 6 locales (new keys from no-input component i18n work). Backend: adds template_notes.vector_store_rag.e8deaec6 (updated README note with docs.langflow.org URL replacing localhost), drops orphaned keys for renamed/removed components (chunkdoclingdocument, doclinginline) that no longer exist in en.json. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * [autofix.ci] apply automated fixes * fix: guard saveFlow rollback against stale flow reference Only restore the previous flow on save failure if the user hasn't already navigated to a different flow, preventing the rollback from clobbering a newer active selection. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(tests): fix CI failures in use-apply-template and no-input tests - Fix double-renamed declaration setCurrentFlowInManagerInManager → setCurrentFlowInManager - Fix mock state key from setCurrentFlowInManager to setCurrentFlow (matches hook's store selector) - Update test assertions to check setCurrentFlowInManager instead of setNodes/setEdges (hook routes through manager store when currentFlow exists) - Enhance jest.setup.js Trans mock to parse <N>text</N> i18nKey tags and render React elements from components prop (fixes "Add a" text not found in no-input test) * fix(frontend): fix Biome import order in playground no-input component --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> |
|||
| 47760d22fb |
fix(ci): drop macOS Intel from py3.14 cross-platform set (onnxruntime x86_64 wheel gap) (#13773)
fix(ci): drop macOS Intel from py3.14 cross-platform set (onnxruntime x86_64 gap) Follow-up to #13772. The new Python 3.14 experimental job on macOS Intel (macos-latest-large / x86_64) fails at dependency resolution: No solution found ... onnxruntime>=1.24.1 has no wheels with a matching platform tag (macosx_*_x86_64) ... onnxruntime>=1.17.0,<=1.23.2 has no cp314 ABI ... lfx==...rc0 depends on markitdown -> magika -> onnxruntime ... unsatisfiable. This is a permanent macOS x86_64 ecosystem gap, not the find-links bug: - langflow pins `onnxruntime>=1.26; python_version>='3.14'` (pyproject.toml), and - onnxruntime dropped macOS x86_64 wheels at 1.24, while the older onnxruntime that still ships macOS x86_64 wheels (<=1.23.2) has no cp314 wheels. So macOS Intel + py3.14 can never resolve. macOS Intel resolves fine through 3.13 (the py<3.14 branch pins onnxruntime<1.24, which still has x86_64 wheels); macOS arm64 has cp314 wheels and is unaffected. The job is non-blocking (continue-on-error) so it never blocked releases, but it's a guaranteed, no-signal failure burning the costly macos-latest-large runner every release. Drop macOS Intel from the 3.14 experimental matrix (keep linux/windows/macOS-arm64) and update the docs/comments to explain the x86_64 wheel gap. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>1.10.1.rc0 |
|||
| b0d4fe0645 |
fix(ci): resolve pre-release cross-platform install failure; graduate py3.13, add py3.14 experimental (#13772)
fix(ci): resolve pre-release cross-platform install + graduate py3.13, add py3.14 The cross-platform install test failed *only* on "Pre-release" release runs, in the Python 3.13 (Experimental) jobs, while normal releases passed. Root cause: the experimental job's "Force reinstall local wheels to prevent downgrades" step drops `--no-deps` when `pre_release=true` (to allow pre-release dependency resolution) but never passed `--find-links` to the local wheel dirs. uv then re-resolved langflow-base's `lfx>=X.Y.Zrc0,<X.(Y+1).dev0` constraint against PyPI only, where the matching rc/dev wheel is not yet published, and failed with "No solution found when resolving dependencies ... unsatisfiable". Stable releases keep `--no-deps`, never re-resolve, and so never hit it. Fix: build a `FIND_LINKS` array over the local wheel dirs (sdk/lfx/base/bundles) and thread it into both force-reinstall `uv pip install` commands (Windows and Unix). Harmless on the stable `--no-deps` path; required on the pre-release path. Also: - Graduate Python 3.13 from the experimental matrix to the stable (blocking) matrix on linux amd64, macOS arm64, and windows amd64. macOS Intel (macos-latest-large) stays at 3.12 only on the blocking matrix to limit use of the costly runner, matching the existing 3.10/3.12 design. - Add Python 3.14 as the new experimental (non-blocking, continue-on-error) set, mirroring the platform coverage 3.13 previously had (incl. macOS Intel). - Update the test-summary messages and cross-platform-test.md accordingly. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
|||
| cc398d94a6 | fix(lfx): make OpenDsStar optional (#13749) | |||
| b8d7a1a534 |
fix(frontend): stop browser autofill from corrupting component config fields (#13748)
* fix(frontend): stop browser autofill from corrupting component config fields Chrome (and password managers) ignore autocomplete="off" for fields they heuristically treat as credentials. A node's API-key (type=password) field makes Chrome classify the config panel as a login form and inject a saved username (e.g. "admin") into adjacent text/name fields plus a saved password into the key field. Langflow autosaves, so merely opening a node and clicking a field can silently overwrite and persist the injected values, corrupting the flow — across users, including ones who never logged into the instance. Suppress autofill on node-config inputs by default: - Base Input/Textarea primitives now emit `new-password` (secret fields) / `off` plus the 1Password/LastPass/Bitwarden/Dashlane opt-out data-* attributes. `new-password` also breaks Chrome's username-pairing heuristic, so adjacent text/name fields (e.g. the component name) stop being filled. - The popover (API-key/secret + str) and TextAreaComponent secret fields key the token on the field's secret-ness, not the live `type`, so revealing a masked value does not re-arm autofill. - Real credential forms (login / signup / admin login) opt back in via a new `allowAutofill` prop, preserving wanted password-manager autofill. Auth-form `name` attributes (used by Playwright selectors) are untouched. Adds unit tests locking the suppressed/opt-in attribute contract. Known low-risk gaps deferred to follow-up (not credential-like, not the reported vector): ag-grid table cell editors, numeric inputs, file-path input, Ace editor. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Update src/frontend/src/components/ui/input.tsx Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * fix(frontend): repair malformed autofillProps after suggestion merge The accepted CodeRabbit suggestion on the Input primitive left a duplicated, dangling object-literal fragment after the autofillProps statement, a syntax error that broke the frontend build (and therefore all CI). Remove the duplicate so the intended branch logic stands: an allowAutofill field uses a caller-provided autoComplete when given, otherwise emits no autocomplete attribute (browser default); suppressed fields keep new-password/off + the password-manager opt-outs. Updates the unit test to the refined contract (allowAutofill without an explicit autoComplete emits no attribute) and adds coverage for an explicit autoComplete on an opted-in field. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(frontend): suppress autofill in ag-grid table cell editors ag-grid mounts its cell editor <input>/<textarea> outside React when a cell enters edit mode, bypassing the hardened Input/Textarea primitives. Editable table cells (TableNodeComponent -> TableModal -> shared TableComponent, plus global variables and other editable grids) were therefore still autofillable, and autosave would persist any injected value. Add an onCellEditingStarted handler on the shared TableComponent that stamps autocomplete="off" + the password-manager opt-out data-* attributes onto the active editor (inline and large-text popup editors) via a small reusable suppressAutofillOnElement helper. Closes the last node-config autofill vector called out as deferred in the original fix. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> |
|||
| 309a558b55 |
chore(deps): bump toolguard floor to 0.2.20 (#13769)
toolguard 0.2.20 ships the Windows build-time code-generation fixes from AgentToolkit/toolguard#28 (repr()-embedded paths in the pytest runner, utf-8 file reads, and pyright executable resolution). Raising the floor from 0.2.16 to 0.2.20 guarantees Windows users get the working green-loop codegen rather than the silent stub fallback, completing the langflow-side Policies/ToolGuard Guard-mode fix (#13751). The existing toolguard.runtime circular-import structure is unchanged in 0.2.20, so lfx's _warm_circular_imports() helper still applies. |
|||
| f5ba8f54ca |
fix: handle Windows path separators in Policies ToolGuard Guard mode (#13727) (#13751)
* fix: handle Windows path separators in Policies ToolGuard Guard mode (#13727) Guard mode read generated guard files back from the node template with str(file_name), where file_name is a pathlib.Path from the toolguard result model. On Windows str() yields backslashes, but the files are stored (by sync_generated_guard_code_inputs) under their POSIX relative path via Path.as_posix() (forward slashes). Every lookup therefore missed on Windows, attrs.get(...) returned None, and None["value"] crashed with "'NoneType' object is not subscriptable". - Add PoliciesComponent._template_field_key() to normalize a file name to the POSIX key the sync step writes; route all reads in make_toolguard_result() through it so lookups match on every platform. - Raise a clear "re-run Generate" error when a generated field is missing instead of subscripting None. - Relax validate_before_generate(): api_key is optional (required=False, advanced=True) and credentials can come from the model connection/env, so only the model selection is required. Fixes the spurious "model or api_key cannot be empty!" block. - Regenerate the bundled component index for the updated source. The separate "Generate emits the pass # FIXME stub" and Windows charmap-decode defects are in the upstream toolguard package (latest 0.2.19) and are out of scope here; this component is ready to consume a fixed toolguard release once available. Fixes #13727 * [autofix.ci] apply automated fixes * Update templates * [autofix.ci] apply automated fixes * fix: address review feedback on Policies component - Add `str | Path` type hints to `_template_field_key` and the inner `read_content` helper (mypy compliance). - Give the empty-template `ValueError` in `make_toolguard_result` a descriptive message instead of a bare raise. - Regenerate the bundled component index for the updated source. * Update component_index.json * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> |
|||
| 9bd47b601f | Template and comp update | |||
| 2b4e720e36 |
fix: stop temp_dirs masking startup errors; fail-fast on unresolvable SQLite path (#13634) (#13729)
fix: fail-fast on unresolvable SQLite path; stop temp_dirs masking startup errors Addresses #13634 (Bug 2 in full; Bug 1 diagnostics + docs — relative-path normalization deferred to a separate design decision per maintainer triage). - main.py: bind `temp_dirs = []` before the lifespan `try` so the shutdown `finally` cleanup never raises UnboundLocalError and masks the real startup error when failure occurs before bundle loading. - database/service.py: add get_sqlite_database_file_path() and check_sqlite_database_path(), surfacing an actionable error (resolved path, CWD anchoring, absolute-path guidance) when a SQLite DB's parent directory is missing — instead of the opaque "Error creating DB and tables". Diagnostics only: no path normalization, no directory creation, no rejection of currently-working relative paths. - database/utils.py: run the check in initialize_database() before creation. - docs: note that SQLite LANGFLOW_DATABASE_URL paths should be absolute. - tests: regression coverage for both bugs. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
|||
| 271c7506bd |
fix(lfx): preserve custom static models through models.dev override (#13676)
Custom models added to the bundled *_constants.py lists (e.g. openai_constants.py) were silently discarded once a models.dev snapshot was active. apply_models_dev_overrides() replaced a covered provider's entire static group with the models.dev rows and skipped any later same-provider group (the OpenAI embeddings group), so user-added LLM and embedding entries never reached get_unified_models_detailed() and the model/embedding pickers. Fold any static entry whose name models.dev does not cover into that provider's override list (mutating the same list object that is appended to the result, so a later embeddings group folds into it too). models.dev still wins for every name it does cover, preserving its fresher metadata. This is especially important for embeddings: the embedding dropdown has no free-text combobox, so a custom embedding must be present in the list to be selectable. Fixes #13556 Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
|||
| db2f5c447e |
fix(smart-router): stop unselected branches from executing downstream nodes (#13536)
* fix(smart-router): stop unselected branches from executing downstream nodes Smart Router only marked unselected branches INACTIVE via stop(), but that state is reset between scheduling passes. When two routes reconverge on a shared downstream node (e.g. both feeding one Chat Output/agent), the re-activated unselected branch was picked up again through that shared node and executed -- producing extra/duplicate provider calls and running paths that should have been skipped. Smart Router now also records a *persistent* conditional exclusion for every unselected output (mirroring the If-Else ConditionalRouter), so unselected branches stay excluded for the whole run while the matched branch -- and any shared downstream node reachable from it -- still runs. Because Smart Router keeps a single matched output and must exclude all the others, add Graph.exclude_branches_conditionally(), which excludes several output branches from one source vertex at once without clearing siblings (the single-branch exclude_branch_conditionally clears prior exclusions per call). Exclusions accumulate across process_case/default_response calls. Adds a graph-level regression test covering separate-leaf, reconverging, and three-route topologies. Regenerates the SmartRouter component_index entry. Fixes #13440 * fix(smart-router): keep shared downstream nodes runnable * fix(graph): avoid branch traversal through feedback cycles * test(graph): cover If-Else branch reconvergence; DRY conditional exclusion Addresses PR review feedback on #13536: - Add an If-Else (ConditionalRouter) reconvergence regression test guarding the shared exclude_branch_conditionally path (RED before the fix, GREEN after); it surfaces a latent If-Else bug the same fix resolves. - DRY exclude_branch_conditionally / exclude_branches_conditionally via a shared _replace_conditional_exclusions helper; the single-branch method delegates to the multi-branch one (strictly behavior-preserving). - Document the conditionally-excluded-vertex guard in vertex_types._get_result. - Narrow the test module import guard from Exception to ImportError. * fix(graph): drop excluded predecessor from list inputs; cover Else routing Addresses PR review feedback on #13536: - Skip a conditionally-excluded, unbuilt predecessor when building an is_list=True input so it contributes nothing instead of injecting the input's template default (a stray empty element next to the real branch's value). RED before the fix via a new list-merge regression test. - Add graph-level Smart Router Else coverage: a matched route excludes the Else branch (its leaf stays unrun while the matched leaf and merge still run); no match runs the Else branch while the category branches stay unrun. - Drop the module-level try/except ImportError -> pytest.skip guard in the test module in favor of direct imports, matching the sibling graph tests. * test(graph): cover Smart Router outputs reconverging directly on one node LE-1427 / Alice's report: both router outputs connected to the same downstream component. Existing reconvergence tests route through intermediate nodes; this covers the router as the immediate predecessor of the shared node, where the merge is reachable from a sibling output of the router itself. RED without the sibling-output protection in exclude_branches_conditionally, GREEN with it. * Fix the conditional branch * Update component_index.json * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> |
|||
| eb2473914e |
fix(docker): force HTTP/1.1 on frontend nginx upstream proxy (#9010) (#13725)
fix(docker): force HTTP/1.1 on frontend nginx upstream proxy The frontend nginx config proxied to the backend without setting proxy_http_version, so nginx used its default of HTTP/1.0 for the upstream requests. Behind an HTTP/1.1-only proxy (e.g. an Istio/Envoy service-mesh sidecar) those requests are rejected with HTTP 426 Upgrade Required, breaking /api, /health, and /health_check. Set proxy_http_version 1.1 explicitly on every proxy_pass block in both default.conf.template (rendered into the production frontend image) and nginx.conf. This is version-independent and keeps working regardless of which nginx the image ships (the image pins nginx 1.28.0, whose upstream default is still HTTP/1.0). Verified with the pinned nginx image in front of an HTTP/1.1-only mock upstream: /api, /health, /health_check return 200 and nginx forwards upstream as HTTP/1.1 (was 426 / HTTP/1.0 before the fix). Fixes #9010 |
|||
| 194f42be81 |
perf: cache Ollama model capabilities to fix slow Cloud model toggles (#13722)
* perf: cache Ollama model capabilities to fix slow Cloud model toggles Enabling/disabling models for an Ollama provider pointed at the cloud base URL (https://ollama.com) lagged on every toggle, while a local Ollama stayed instant (issue #12399). Root cause: get_ollama_models() probes capabilities with one POST /api/show per model on top of the GET /api/tags listing, so a single catalog read costs N + 1 upstream requests. GET /enabled_models reads both llm and embeddings (replace_with_live_models(model_type=None)), so each read paid 2 x (N + 1), and every model toggle triggers a refetch. On Ollama Cloud's large public catalog over the internet this crawls; local Ollama has a tiny catalog and ~0 latency so it stays fast. The existing 30s list cache only helps clustered same-capability reads. Fix: add a per-model capability cache keyed by (base_url, model_name). A model:tag's capabilities are intrinsic to the model, so the /api/show result is reused instead of re-probed. This makes the llm and embedding reads share a single fan-out (N probes, not 2N) and makes any read after the short list-TTL re-probe only models never seen before, not the whole catalog. TTL is bounded at 10 minutes so a re-pull that changes a tag's capability class self-heals quickly. A probe failure is still absorbed and left uncached so one bad model never poisons or sticks in the catalog. Backend-only and behavior-preserving: the model lists returned are identical; only the upstream request count drops. * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) * perf: avoid caching empty Ollama capabilities and prune stale entries Address review feedback on the per-model capability cache (#13722). A 200 /api/show with no capabilities resolved to [] and was cached for the full TTL, while a real RequestError/HTTPStatusError was correctly left uncached, so a transient empty response could hide a model from the picker for 10 minutes. Cache only a populated capability list, so an empty/absent array is re-probed on the next read like the error path. Real Ollama always returns a populated array, so no legitimately-capable model is ever re-probed. The capability cache expired on read but never evicted, retaining one entry per distinct model seen for the process lifetime. Prune it to the live catalog on each fresh read so it tracks the current catalog; entries for other base URLs are untouched. Adds behavioral tests for both paths. --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> |
|||
| 4ebac3f863 |
chore: upgrade langchain (#13750)
upgrade langchain |
|||
| 81196ceabd |
fix: validate uploaded MCP servers config to close command-injection path (#13709)
* fix: validate uploaded MCP servers config to close command-injection path The `_mcp_servers_<uid>.json` file-upload path stored the raw uploaded bytes as the user's MCP servers config without validation. Those `command`/`args` are later spawned via the MCP stdio transport, so an authenticated user could upload a config with an arbitrary command and have it executed on the server — bypassing the command allow-list that the structured `/api/v2/mcp/servers` endpoints already enforce via `MCPServerConfig` (CWE-77 / CWE-94). This wires the same `MCPServerConfig` validation into the file-upload branch: the uploaded JSON must be an object with an `mcpServers` map, and every entry is validated against the allow-list before anything is written to storage. The file is rewound so the subsequent save re-reads the original bytes. Adds a regression test asserting a disallowed command is rejected with 422 and nothing is persisted, and updates the existing replace test to use an allow-listed command. * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) * Update test_mcp_servers_file.py * test: cover MCP config validator reject branches + close upload lock bypass Address review on the MCP-config-upload validation fix: - Enforce the MCP-servers lock on the /api/v2/files upload path. The structured /api/v2/mcp/servers endpoints 403 non-superuser writes when mcp_servers_locked is on, but the upload branch writes the same _mcp_servers_<uid>.json that get_server_list reads, so without the same guard a non-superuser could replace their MCP config while locked. - Pin the validator's own reject branches: invalid-JSON (422) and a non-object 'mcpServers' value (422), neither persisting anything. - Add lock-guard coverage: blocked for a locked non-superuser (403), still allowed for a superuser. * test: update MCP upload replacement fixture --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> |
|||
| 0b14a6b06b |
fix(security): gate ToolGuard guard-code execution on allow_custom_components (#13701)
* fix(security): gate ToolGuard guard-code execution on allow_custom_components The Policies/ToolGuard component executes guard Python whose source is taken from the component's client-editable CodeInput template values (make_toolguard_result reads attrs[...]["value"]) and exec'd via load_toolguards_from_memory. Those values are not covered by the custom-component hash gate, so an authenticated user could run arbitrary backend Python even with allow_custom_components=false (stored exec / cross-tenant). guard_tools() now refuses to run when allow_custom_components is False, before importing or loading any toolguard runtime, so the client-supplied guard code is never exec'd. When no settings service is present (lfx standalone), execution is allowed as a local/trusted context. * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) * fix(security): fail closed when settings service is unavailable Address review feedback on the ToolGuard exec gate: - _code_execution_allowed() now denies execution when the settings layer is present but the service returns None, matching the behavior of validate_flow_for_current_settings (which raises in that case). Fail-open is reserved for the truly standalone path where the settings layer cannot be imported at all (lfx used as a bare library). - Strengthen the blocked-path test to assert the toolguard runtime is never imported when execution is refused. - Add a test pinning the allow side: allow_custom_components=True must reach _import_toolguard, so a future flip of the default is caught. * Update component_index.json * [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> |
|||
| 2b7b113049 |
fix: warn on empty value and decrypt failure in get_all variable listing (#13741)
Co-authored-by: Janardan S Kavia <janardanskavia@Janardans-MacBook-Pro.local> |
|||
| 7799d1a9c1 |
fix(security): scope voice-mode clients per user (#13702)
* fix(security): scope voice-mode clients per user Voice mode held an ElevenLabs client in a process-global singleton (ElevenLabsClientManager) that cached the FIRST caller's API key and returned it to every subsequent user - billing all tenants' TTS to one account and exposing that account's voice library. The OpenAI/voice config caches were keyed only by the client-supplied session_id, so two users sharing a session_id reused each other's TTSConfig (and its OpenAI client built from the other user's key). - Replace the singleton: get_or_create_elevenlabs_client now builds a fresh client from the requesting user's own key on each call. - Key voice_config_cache / tts_config_cache by (user_id, session_id); thread the authenticated user id through get_voice_config/get_tts_config/get_create_response and resolve voice config only after authentication. * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) * fix(security): guard failed auth in flow_tts_websocket; type cache keys as UUID Address review feedback on #13702: - Add current_user/openai_key None guard after authenticate_and_get_openai_key in flow_tts_websocket, mirroring flow_as_tool_websocket, so a failed auth returns early instead of raising AttributeError on current_user.id. - Annotate the voice/TTS config cache keys (and get_voice_config/get_tts_config user_id params) as UUID | None, matching the User.id passed by every caller. * fix: migrate voice_mode off deprecated websockets legacy client API websockets.connect() resolves to the asyncio implementation under the pinned 15.x, so the SendQueues annotations were both deprecated and the wrong runtime type (the object is a ClientConnection, not the legacy WebSocketClientProtocol). On the same path the legacy extra_headers kwarg was a latent runtime bug: the asyncio connect takes additional_headers, and the old name is forwarded to BaseEventLoop.create_connection() and raises TypeError at connect time. - Import websockets.asyncio.client.ClientConnection and use it for the openai_ws annotations on SendQueues. - Change extra_headers -> additional_headers at both connect() call sites. Swept src/ -- both patterns were isolated to voice_mode.py. --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> |