Commit Graph

18251 Commits

Author SHA1 Message Date
d4c8ffd9ea Update version for nightly v1.11.0.dev21 2026-06-26 02:20:25 +00:00
d2d1bdc971 fix(telemetry): use SQLAlchemy AsyncSession to stop deprecation-warning log flood (#13845)
The telemetry writer configured its dedicated sessionmaker with SQLModel's
AsyncSession but only ever issues Core-style bulk statements via
session.execute() (Table.insert(), delete(), select().scalars()) — it never
uses SQLModel's exec(). SQLModel marks execute() as deprecated (PEP 702
@deprecated), so every batch flush and retention sweep emitted a multi-line
"🚨 use session.exec()" DeprecationWarning at runtime, flooding the WebServer
logs (visible all over the starter-projects Playwright shard CI output).

Switch the sessionmaker to SQLAlchemy's plain AsyncSession, which is what the
service actually needs. No behavioral change: the bulk insert/delete/select +
.scalars()/.commit() paths are session-class-agnostic and the existing
telemetry_writer test fixture already runs against a plain AsyncSession.

- 38 telemetry_writer unit tests pass
- ruff clean
- verified execute() now emits zero SQLModel deprecation warnings
2026-06-25 17:19:12 -07:00
dc3992158d fix: release QA component follow-ups for bundles (#13834)
* fix: clean up release QA component follow-ups

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

* chore: trigger release QA follow-up CI

* docs: update bundle API changelog

* [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>
2026-06-25 15:44:25 -07:00
93e5472a34 feat: make content_blocks the source of truth for Message content (#13364)
* feat: make content_blocks the source of truth for Message content

Migrate Message.text from a Pydantic field to a @computed_field over
content_blocks, and unify ContentBlock into the discriminated ContentType
union so a Message's payload is one uniform shape.

Schema changes:
- Add 7 new content types (Image, Audio, Video, File, Reasoning, Usage,
  Citation) with validators for media sources, non-negative tokens, and
  ordered citation indices
- Promote 'contents: list[ContentType]' to BaseContent so any node can
  nest (multimodal tool outputs, multi-step reasoning, grouped errors)
- Fold ContentBlock into BaseContent and into the ContentType union with
  tag 'group'; content_blocks is now 'list[ContentType]' everywhere
- Fix Data.__setattr__ to route through property descriptors via MRO walk

Setter / serialization:
- text setter appends a single TextContent at the end of content_blocks,
  preserving non-text blocks in chronological order (tool calls first,
  final text last)
- model_post_init preserves explicit None in data['text'] when no
  TextContent exists in content_blocks, so callers can still distinguish
  'text was never set' from 'text was set to empty string'

from_lc_message:
- Handle AIMessage tool_calls and usage_metadata regardless of whether
  content is a string or a list (tool-calling agents commonly emit
  content='' alongside tool_calls)
- Tolerate explicit source=None in multimodal image payloads

MessageResponse.from_message / MessageTable.from_message:
- Accept any of (data['text'] set, text_stream pending, content_blocks
  non-empty) as 'content present', so tool-call-only and media-only
  messages persist rather than getting rejected as missing required
  fields

Tests cover all new content types, the unified ContentType union, the
text/content_blocks contract, the setter's chronological append, and the
required-fields gate.

(cherry picked from commit 3b92500349)

* feat: stable id on content blocks + plumb LangChain tool_call_id

Adds an optional 'id: str | None' field to BaseContent for stable
identity across re-emissions of the same logical block. Producers
that have a natural id (LangChain tool_call_id, external API id, a
UUID stamped before the first emission) set it; consumers use it for
dedup and cross-frame correlation. Without an id, consumers fall back
to position-derived dedup, which assumes content_blocks is append-only
within a message lifetime.

Plumbs LangChain's 'tool_call_id' through 'Message.from_lc_message'
into 'ToolContent.id'. The same logical tool call across start, args
streaming, and result lifecycle now carries the same id, so a
re-fired add_message dedups to one ToolContent instead of producing
duplicates.

Tests cover id default/round-trip/inheritance across every concrete
content type, plus tool_call_id stability across repeated conversion,
multiple tool calls each keeping their own id, and tool_calls
alongside string content.

(cherry picked from commit a3e8b40811)

* fix(schema): MessageResponse parses microsecond timestamps and ContentBlock partial updates preserve unset fields

Two schema regressions surfaced in QA across the content-blocks chain:

1. MessageResponse.timestamp was typed as a bare datetime, but
   Message.timestamp default is a string with microsecond precision and
   a UTC timezone label ('%Y-%m-%d %H:%M:%S.%f %Z') that Pydantic's
   default datetime parser rejects. Any freshly built Message routed
   through MessageResponse.from_message raised ValidationError. Reuse
   the shared str_to_timestamp_validator so MessageResponse accepts
   every format Message itself recognises.

2. ContentBlock.__init__ marked every field as model_fields_set, not
   just the discriminator. The override defeated exclude_unset for the
   group content type: a patch like ContentBlock(title='new') dumped
   every defaulted field and, when merged onto an existing block by
   aupdate_messages, overwrote fields the caller never touched. Mark
   only 'type' (the discriminator) so partial updates carry the variant
   tag without clobbering the rest.

Adds regression tests in test_message_content_blocks.py: from_message
round-trips Message.timestamp without crashing, and ContentBlock
exclude_unset stays narrow to the explicit fields plus the
discriminator.

(cherry picked from commit 6f6639374f)

* fix(schema): address content_blocks review feedback

- sync langflow-base ContentBlock.__init__ with the lfx copy (model_fields_set
  parity) so exclude_unset no longer clobbers type; add cross-module regression test
- route MessageResponse content_blocks discriminator-first so stored flat blocks
  with contents=[] validate instead of raising
- move Message SecretStr coercion into model_post_init and drop the dead
  validate_text before-validator
- drop the no-op _fold_text_into_content_blocks validator
- log a shape-only debug line when from_lc_message drops an undecodable image
- type MessageResponse.content_blocks as list[ContentType] | None

(cherry picked from commit 6c7cec8a529c38d2f9eb7a35f94277611c8696cc)

* fix(agents): stop duplicating the final answer in content_blocks

With content_blocks as the source of truth, Message.text is a computed
field whose setter appends a trailing top-level TextContent. handle_on_chain_end
also appended the same answer into the Agent Steps group, so the final
answer rendered twice (assert 2 == 1 in test_multiple_events). The streaming
path already relies on the setter alone; make the non-streaming path match.

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

* feat(schema): project new content_blocks back to the v1 wire shape

The in-memory Message and the v2 (AG-UI) path use the new content_blocks
union (groups tagged "group", every node carries id/contents, the agent
answer is a trailing top-level TextContent). The v1 API keeps emitting the
pre-1.11.0 shape via a pure legacy_render projection that runs only at the
v1 boundaries: the v1 read/response models, the memories endpoint, the v1
build SSE stream, the webhook events SSE stream, and the /run response and
stream. The build, webhook, and /run projections recurse so the Data mirror
(data.data.content_blocks) is projected alongside the top-level copy. v2
serializes the live Message and keeps the new shape.

* [autofix.ci] apply automated fixes

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

* fix(api): keep simple_run_flow returning RunResponse, project v1 at the HTTP boundary

simple_run_flow is a shared helper, so wrapping its return in a JSONResponse to
apply the v1 content_blocks projection broke internal callers that call
.model_dump() on the result (the streaming run_flow_generator and
get_build_results). Return the RunResponse object from the helper and apply the
projection at the non-stream HTTP boundary in _run_flow_internal instead. The
streaming path already projects the end event via _project_run_event.

---------

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>
2026-06-25 13:39:55 -07:00
89711298e5 ci: push nightly .devX Docker images to langflowai/langflow and fix db-migration-validation image references (#13836)
* ci: push nightly .devX docker images to langflowai/langflow in addition to langflowai/langflow-nightly

Mirrors PyPI behaviour where .devX releases are published under the
'langflow' package name (not 'langflow-nightly'). The Docker nightly
workflow now tags and pushes every nightly image to both repos:

- langflowai/langflow-nightly:{ver}  (existing)
- langflowai/langflow:{ver}          (new — matches PyPI)
- langflowai/langflow-nightly:latest (existing)
- langflowai/langflow:latest         (new)

Same mirroring applied to the base and main-all variants, and to GHCR.

The arch-specific intermediate tags (used to assemble multi-arch
manifests) remain under the '-nightly' repos; only the final merged
manifests are additionally published to the canonical 'langflow' repos.

Fixes: LE-1667

* ci: update db-migration-validation to use canonical langflowai/langflow image

The db-migration-validation workflow was still referencing the old
langflowai/langflow-nightly image for its nightly upgrade step.
Now that .devX images are also pushed to langflowai/langflow (canonical
repo, matching PyPI), update all consumer-side references:

- nightly_build.yml: nightly_tag now passes langflowai/langflow:<tag>
- db-migration-validation.yml: default and shell fallbacks updated to
  langflowai/langflow:latest; inline comments updated to match

The langflowai/langflow-nightly images remain in use as intermediate
arch-specific staging tags inside docker-nightly-build.yml only.

Relates to: LE-1667

* ci: fix stale langflow-nightly package name reference in comment

The nightly is published as canonical .devN pre-releases of langflow /
langflow-base (not as separate langflow-nightly / langflow-base-nightly
packages). Update the generate-shared-tag step comment to accurately
reflect this since the rename that happened in the 1.10 release cycle.

Relates to: LE-1353, LE-1667

* fix(ci): do not set :latest on canonical langflow repo for nightly .devX images

Nightly .devX images should not overwrite the stable :latest tag on
langflowai/langflow. The :latest (and :base-latest) floating tags are
only set on the -nightly repos (langflowai/langflow-nightly), which
are explicitly nightly-only. The canonical langflowai/langflow repo
only receives the versioned tag (e.g. :1.11.0.dev20250522), matching
PyPI behaviour where langflow==1.11.0.dev20250522 is published but
never becomes the 'latest' release index entry.

Addresses PR review comment from @jordanrfrazier.

Relates to: LE-1667

* fix(ci): prevent shell injection from inputs.nightly_tag in db-migration-validation

Pass inputs.nightly_tag through an env: block and reference it as
$INPUT_NIGHTLY_TAG in the shell script rather than expanding the
GitHub Actions expression directly inside run:. Direct expansion of
${{ inputs.* }} in shell scripts allows an attacker-controlled input
to inject arbitrary shell commands (CWE-78).

Flagged by CodeRabbit security review.

Relates to: LE-1667
2026-06-25 13:39:39 -07:00
49fc1f862f feat(lfx): introduce pluggable executor seam (#12981)
* feat(execution): scaffold execution package with test isolation fixture

* feat(execution): add core types (Unit, StepResult, RunComplete)

* feat(execution): add Executor abstract base class

* feat(execution): add identity partition function

* feat(execution): add executor registry (register/get only)

* feat(execution): add InProcessExecutor (streaming + legacy passthrough)

* feat(execution): add Coordinator with streaming run + run_to_completion

* feat(execution): default singleton with in-process pre-registered + setter

* feat(execution): route Graph.arun through coordinator (seam #1)

* feat(execution): route flow_executor through coordinator (seam #2)

* feat(execution): route CLI and run/base through coordinator (seam #3)

* feat(execution): route Loop subgraph through coordinator (seam #4)

* fix(execution): explicit legacy dispatch flag, forward fallback_to_env_vars, real-graph concurrency tests

* refactor(execution): add Coordinator.stream helper, lift imports, replace em-dash

* test(execution): shared simple_graph fixture, reset_default_coordinator helper, real-executor loop tests, behavior assertions

* refactor(execution): wrap default singletons in _Defaults holder, drop noqas

* feat(execution): make executor seam pluggable via lfx services

Introduce an ExecutorService that owns the registry and the default coordinator,
discovered through the standard lfx ServiceManager (parity with cache, database,
storage, etc). Add an executor_kind setting so the default kind can be configured
without touching code, and an lfx.executors entry-point group for third-party
executors. Entry-point discovery refuses to overwrite an existing kind so an
installed package cannot silently replace the built-in in-process executor;
explicit replacement still works through ExecutorService.register().

The lfx.execution public API (get_default_coordinator, get_default_registry,
set_default_coordinator, reset_default_coordinator) now resolves through the
service manager but keeps the same surface, so existing call sites in Graph.arun,
flow_executor, the CLI, run/base, and loop_utils are unchanged.

Also surface root-cause tracebacks in lfx.services.deps.get_service: the helper
still returns None on failure (callers like get_db_service rely on that), but it
now logs the exception so init failures stop disappearing into the void.

* test(execution): contract suite, cancellation guarantees, seam docstrings

Robustness pass on the execution seam after using it to integrate an external
executor end-to-end. Four things this pins down:

1. Reusable executor contract suite
   - tests/unit/execution/test_executor_contract.py
   - ExecutorContract with 7 universal seam guarantees: kind shape, execute()
     returns an AsyncIterator, stream ends with exactly one RunComplete,
     instance is reusable across runs, concurrent runs are isolated, consumer
     cancellation does not hang or leak, execute() signature is stable.
   - TestInProcessExecutorContract subclasses and provides fixtures. Downstream
     executors (stepflow, future remote/sandbox) get the same battery by
     subclassing and overriding two fixtures.

2. Cancellation/cleanup correctness
   - tests/unit/execution/test_cancellation.py
   - InProcessExecutor wraps graph.async_start() in try/finally with explicit
     aclose so consumer aclose cascades to the underlying graph generator.
     Previously the inner generator was abandoned and only finalized on a GC
     pass: a real leak for executors holding subprocesses or sockets.
   - Coordinator.run and Coordinator.stream propagate aclose the same way so
     the full chain (consumer -> coordinator -> executor -> graph) cleans up
     deterministically.

3. Documented seam contracts
   - Unit.runtime_options: free-form bag, "_"-prefixed keys reserved for
     executor-internal flags, common in-process keys listed.
   - StepResult.payload: untyped on purpose; each executor defines its own
     event vocabulary; consumers normalize at consumption site.
   - RunComplete.outputs: only the legacy in-process path populates it;
     streaming consumers should collect from StepResult.payload.
   - Executor ABC: lifecycle (shared instance, must be reusable, must tolerate
     concurrent execute() calls and consumer aclose).

4. Coordinator-routed E2E suite
   - tests/unit/execution/test_coordinator_e2e.py
   - Real Graph through Coordinator -> registry -> InProcessExecutor chain.
   - Asserts payload shape, RunComplete never leaks to stream() consumers,
     dispatch routes to configured kind, registry returns same instance.

Full execution suite: 63 passing. flow_executor coordinator test: passing.

* fix(execution): close async generators on early exit and harden seam tests

- run_to_completion and Coordinator.stream wrap iteration in contextlib.aclosing
  so the underlying generator is finalized when the loop returns or raises early
- execute_loop_body wraps its stream in aclosing for the same mid-iteration guarantee
- drop the unused inputs param from Coordinator.stream; the streaming path reads
  initial_inputs from runtime_options, so inputs was a silent no-op, and document it
- test_coordinator_uses_settings_executor_kind uses monkeypatch + a finalizer so a
  mid-test failure can't poison session settings
- test_registry_returns_same_executor_instance_across_runs builds a fresh graph per run
- test_set_default_coordinator_overrides_singleton restores the singleton afterward

* fix(execution): register ExecutorService factory and update seam test mocks

- register ExecutorServiceFactory in register_all_service_factories so
  EXECUTOR_SERVICE resolves at app startup; without it the seam raised
  NoFactoryRegisteredError across integration and mcp tests
- test_base.py graph.async_start mocks now default the leading positional
  param (the seam calls async_start with inputs as a keyword), fixing the
  'missing 1 required positional argument' failures
- add executor_kind to the settings composition EXPECTED_FIELDS set

* fix(execution): reject non-Executor entry-point plugins; doc corrections

- ExecutorService discovery skips a loaded entry-point object that is not an
  Executor instance instead of registering it and failing only at execute() time
- Coordinator.run docstring notes the single-Unit / single-RunComplete assumption
  that identity_partition currently guarantees
- correct two stale docstrings: Unit's session_id is honored only on the legacy
  passthrough path, and ExecutorService.teardown no longer claims the service
  manager keeps the cached instance

* fix(lfx): reset factory-registered flag on ServiceManager teardown

teardown() emptied self.factories but left factory_registered set, so
get_service()'s lazy re-registration was skipped and later lookups raised
NoFactoryRegisteredError. Once Graph.arun started routing through the
executor service, that surfaced as intermittent flow-execution 500s in the
backend unit suite whenever a sibling test had torn the global manager down.

* test(loop): accept executor kwargs in subgraph async_start mock

The loop body now runs its subgraph through get_default_coordinator().stream,
so InProcessExecutor calls async_start with inputs/max_iterations/config/
reset_output_values/fallback_to_env_vars alongside event_manager. The mock
only accepted event_manager and raised TypeError on the extra kwargs. Widen it
to **_kwargs while still capturing event_manager for the assertion.
2026-06-25 13:39:11 -07:00
7dd634790a feat: a11y (#13561)
* chore: add accessibility action plan and gap report for IBM Level 1 compliance

* feat: integrate accessibility checker for automated a11y scans

- Added `accessibility-checker` package to the project dependencies.
- Enhanced test fixtures to include accessibility scanning capabilities.
- Implemented `runA11yScan` method in the `LangflowPage` type for running accessibility checks.
- Created utility functions for building accessibility scan labels, formatting failure messages, and generating summary attachments.
- Updated test setup to manage accessibility sessions and assert compliance based on scan results.

* feat: add accessibility test usage documentation for IBM scans

* feat: enhance accessibility testing with JSON report aggregation and new tests

* feat: add GitHub Actions workflow for IBM accessibility scans

* feat: update a11y scan workflow to allow testing from feat/a11y branch

* feat: skip Puppeteer download during npm install in Dockerfiles for accessibility testing

* feat: enhance a11y scan workflow to resolve and scan latest release branches

* test: add component a11y unit tests (jest-axe) (#13613)

* test: add component a11y unit tests (jest-axe)

Cover 14 components in two waves. 16 tests fail by design - each
encodes a known gap from a11y-action-plan (phases 0-3) and flips to
a regression lock once the component fix lands. 23 tests pass as
regression locks for already-correct semantics.

* ci: split a11y unit tests into separate workflow

Main jest CI excludes *.a11y.test.* so known-gap failures don't
block PRs; new a11y-unit-tests.yml runs them as informational check.

* fix(a11y): resolve component gaps flagged by a11y unit tests

Fixes all 16 failing jest-axe gap tests:
- TableHead defaults scope=col
- alert display area gets aria-live region
- app header becomes <header> landmark; bell button labeled
- icon wrapper decorative by default (aria-hidden), ariaLabel opt-in
- Input drops wrapping <label> (name pollution) and hides placeholder span
- password toggle back in tab order with aria-label/aria-pressed
- CheckBoxDiv exposes checkbox role + state
- accordion trigger renders native Radix button
- dialogs focus container on open instead of suppressing autofocus
- full-screen modal exposes dialog role/aria-modal/ariaLabel
- flow list card focusable with keyboard activation

Also wraps test focus calls in act() and mocks the icon loader so
the a11y suite runs without React warnings.

* fix: skip Puppeteer Chrome download in remaining Dockerfiles

frontend and base images still ran bare npm install; puppeteer (via
accessibility-checker, test-only) tried to fetch Chrome and broke the
Docker image build in CI.

* test: extend a11y unit test coverage (jest-axe)

Adds axe + semantics tests for untested primitives (button, alert,
textarea, radio-group, dropdown-menu, tooltip, popover) and two
known-gap tests that fail by design until fixes land:
- copyFieldAreaComponent copy action is a bare div onClick (plan 2.4)
- DialogContentWithouFixed renders an empty fragment (plan 1.6)

* fix: make webhook copy field a real button (a11y)

Copy action was a bare div onClick - no role, name, or keyboard
support (a11y-action-plan 2.4). Drops the no-longer-needed
DialogContentWithouFixed known-gap test.

* feat: update a11y unit tests and improve accessibility semantics in components

* feat: enable pull request trigger for a11y scan workflow

* feat: Add a11y regression scan suite (#13663)

* test: add component a11y unit tests (jest-axe)

Cover 14 components in two waves. 16 tests fail by design - each
encodes a known gap from a11y-action-plan (phases 0-3) and flips to
a regression lock once the component fix lands. 23 tests pass as
regression locks for already-correct semantics.

* ci: split a11y unit tests into separate workflow

Main jest CI excludes *.a11y.test.* so known-gap failures don't
block PRs; new a11y-unit-tests.yml runs them as informational check.

* fix(a11y): resolve component gaps flagged by a11y unit tests

Fixes all 16 failing jest-axe gap tests:
- TableHead defaults scope=col
- alert display area gets aria-live region
- app header becomes <header> landmark; bell button labeled
- icon wrapper decorative by default (aria-hidden), ariaLabel opt-in
- Input drops wrapping <label> (name pollution) and hides placeholder span
- password toggle back in tab order with aria-label/aria-pressed
- CheckBoxDiv exposes checkbox role + state
- accordion trigger renders native Radix button
- dialogs focus container on open instead of suppressing autofocus
- full-screen modal exposes dialog role/aria-modal/ariaLabel
- flow list card focusable with keyboard activation

Also wraps test focus calls in act() and mocks the icon loader so
the a11y suite runs without React warnings.

* fix: skip Puppeteer Chrome download in remaining Dockerfiles

frontend and base images still ran bare npm install; puppeteer (via
accessibility-checker, test-only) tried to fetch Chrome and broke the
Docker image build in CI.

* test: extend a11y unit test coverage (jest-axe)

Adds axe + semantics tests for untested primitives (button, alert,
textarea, radio-group, dropdown-menu, tooltip, popover) and two
known-gap tests that fail by design until fixes land:
- copyFieldAreaComponent copy action is a bare div onClick (plan 2.4)
- DialogContentWithouFixed renders an empty fragment (plan 1.6)

* fix: make webhook copy field a real button (a11y)

Copy action was a bare div onClick - no role, name, or keyboard
support (a11y-action-plan 2.4). Drops the no-longer-needed
DialogContentWithouFixed known-gap test.

* feat: update a11y unit tests and improve accessibility semantics in components

* feat: enable pull request trigger for a11y scan workflow

* test: add a11y regression scan suite

* test: make a11y scans non-blocking

* test: discover a11y scan specs

* ci: group a11y workflow checks

* fix(a11y): restore focus-visible indicators (#13664)

* fix(a11y): restore focus-visible indicators

* [autofix.ci] apply automated fixes

---------

Co-authored-by: Olayinka Adelakun <olayinkaadelakun@Olayinkas-MacBook-Pro.local>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Viktor Avelino <64113566+viktoravelino@users.noreply.github.com>

* a11y: improve auth accessibility coverage (#13724)

* fix: improve auth accessibility coverage

* fix(a11y): restore delete trigger tab focus

* test(dialog): cover nested description

* fix(a11y): add accessible names and hide decorative icons in LangflowCounts (#13731)

* fix(a11y): add accessible names and hide decorative icons in LangflowCounts

* [autofix.ci] apply automated fixes

---------

Co-authored-by: Olayinka Adelakun <olayinkaadelakun@mac.war.can.ibm.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>

* fix(a11y): add accessible names to icon-only buttons, fix placeholder (#13666)

* fix(a11y): add accessible names to icon-only buttons, fix placeholder contrast, and enforce touch target size on /flows

* [autofix.ci] apply automated fixes

* fix issues

* [autofix.ci] apply automated fixes

* fix(a11y): fix aria-allowed-attr, invalid sidebar list structure, and session filter label mismatch on flow editor (#13668)

* fix(a11y): fix aria-allowed-attr, invalid sidebar list structure, and session filter label mismatch on flow editor

* [autofix.ci] apply automated fixes

* improve translations

* remove app-header-a11y

---------

Co-authored-by: Olayinka Adelakun <olayinkaadelakun@Olayinkas-MacBook-Pro.local>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Olayinka Adelakun <olayinkaadelakun@mac.war.can.ibm.com>

* label get-started close button and fix dark-mode placeholder contrast

* remove testing change

* improve the lint of the testcase

---------

Co-authored-by: Olayinka Adelakun <olayinkaadelakun@Olayinkas-MacBook-Pro.local>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Viktor Avelino <64113566+viktoravelino@users.noreply.github.com>
Co-authored-by: Olayinka Adelakun <olayinkaadelakun@mac.war.can.ibm.com>

* fix: update package dependencies and versions in package-lock.json

* chore: Remove accessibility documentation and reports

- Deleted the accessibility action plan, gap report, level 1 requirements, and test usage documentation.
- This cleanup is part of a broader effort to streamline accessibility resources and focus on actionable items.

* chore: Add accessibility scan reports (#13812)

* feat: add accessibility scan reports

* docs: add a11y scan route map

* test(a11y): add manifest-backed scans

* ci(a11y): summarize scans by route

* fix: update Playwright version to 1.60.0 and enhance a11y scan options

* fix(test): keep fixture args destructured

* test(a11y): bootstrap asset route scans

---------

Co-authored-by: olayinkaadelakun <olayinka.adelakun@ibm.com>
Co-authored-by: Olayinka Adelakun <olayinkaadelakun@Olayinkas-MacBook-Pro.local>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Olayinka Adelakun <olayinkaadelakun@mac.war.can.ibm.com>
2026-06-25 18:33:53 +00:00
776b9e68c7 fix: share policy refresh and owner permissions (#13830)
Fix share policy refresh and owner permissions
2026-06-25 18:21:12 +00:00
ffd119cf3f test: remove duplicate Playwright e2e coverage (#13819)
* test: remove duplicate test coverage (safe dedup)

LE-1620 Tier 3 (phase A) - three duplicate-coverage removals, each verified to lose no unique assertion:

Delete outdated-message.spec.ts: its only assertion (the "components need updates" banner) is identical to the first assertion of outdated-actions.spec.ts.

output-modal-copy-button.spec.ts: drop the "JSON output from API Request component" sub-test (duplicated by copy-button-in-output.spec.ts); keep the text-output icon-state test.

general-bugs-move-flow-from-folder.spec.ts: drop the happy-path "move flow from folder" test (covered more thoroughly by folders.spec.ts change-flow-folder); keep the unique stale-cache regression test.

No coverage loss: each removed assertion has a home in a surviving test.

Refs LE-1620 (Tier 3 / phase A)

* test: fold minimize handle-visibility check into the 5-cycle test

minimize.spec.ts checked a single minimize/expand cycle and uniquely asserted that the node connection handles get the no-show class. general-bugs-minimize-state-error already runs 5 cycles but only checked hide-node-content; this migrates the no-show handle assertion into its toggle helper, then deletes minimize.spec.ts.

Refs LE-1620 (Tier 3 / C1)

* test: merge store share-button test into generalBugs-shard-13

store-shard-2 and generalBugs-shard-13 both exercised share-on-canvas but checked different things: store-shard-2 validated the share modal contents (labels, public checkbox, category tags, name/description inputs); generalBugs-shard-13 checked idempotency (re-share + replace). Move the modal-validation test into generalBugs-shard-13 (guard adapted to skipIfMissing.storeApiKey + loadDotenvIfLocal), preserving every modal assertion. store-shard-2 keeps its filter-by-tag test.

Refs LE-1620 (Tier 3 / M4)

* test: actually trigger share before asserting success toast

The store share test (moved here from store-shard-2 in this PR) asserted the "Flow shared successfully" toast without ever clicking the share confirm button, so it never exercised the real share. It went unnoticed because the test is STORE_API_KEY-gated and skipped in CI. Add the share-modal-button-flow click + replace handling after the modal validation, matching the existing share test.

Refs LE-1620 (Tier 3 / M4 review)

* test: assert modal checks instead of ignoring their booleans

The API-keys warning and the public checkbox in the moved store share test called .isVisible()/.isChecked() but ignored the returned booleans, so they asserted nothing. Make them real web-first assertions. Pre-existing from store-shard-2; flagged in review.

Refs LE-1620 (Tier 3 / M4 review)
2026-06-25 13:28:28 +00:00
ce71c72868 chore: restore bundles that are now published (#13825)
* chore: restore bundles that are now published

* fix: torchvision

* flip torchvision sign
2026-06-24 17:16:46 -07:00
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)
2026-06-24 21:17:45 +00:00
5cb0edde5a fix: keep nightly prerelease wheels explicit (#13815) 2026-06-24 13:14:56 -07:00
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.
2026-06-24 19:35:15 +00:00
d5005244f0 Revert "fix: keep tests and the starter-projects endpoint green while bundles are temporarily unpublished (#13809)"
This reverts commit b565a14bdc.
2026-06-24 11:52:31 -07:00
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>
2026-06-24 11:51:30 -07:00
b565a14bdc fix: keep tests and the starter-projects endpoint green while bundles are temporarily unpublished (#13809) 2026-06-24 15:25:15 -03:00
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
2026-06-24 13:59:57 -03:00
cf9cabdd5a fix: guard temporarily-disabled bundle deps across backend + frontend tests (#13801)
fix: guard temporarily-disabled bundle deps in backend tests and Cuga component
2026-06-24 12:00:16 -03:00
9121161cda fix: skip provider-component tests while bundles are temporarily disabled (#13797)
fix(frontend): skip provider-component tests when bundles temporarily disabled
2026-06-24 09:45:14 -03:00
ecd7888161 fix: temporarily disable unpublished bundle deps 2026-06-23 20:56:43 -07:00
e7bf0da0da fix: include sdk wheel in bundle smoke test 2026-06-23 20:41:26 -07:00
3f4c1a0a55 fix: unblock nightly bundle install resolution 2026-06-23 20:37:27 -07:00
1cb31f5153 fix: cap composio client version 2026-06-23 18:20:40 -07:00
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>
2026-06-23 17:45:07 -07:00
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
2026-06-23 17:22:13 -07:00
6453fa8a75 ci: throttle bundle publishes to PyPI 2026-06-23 16:45:34 -07:00
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>
2026-06-23 16:24:10 -07:00
a66b75ac26 chore: require bumped sdk for lfx release 2026-06-23 16:15:24 -07:00
f85901e49d chore: bump sdk release version 2026-06-23 16:12:43 -07:00
4c8fb6b954 fix: gate release rc setup for stable releases 2026-06-23 16:01:04 -07:00
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>
2026-06-23 13:53:13 -07:00
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 14dfa7f268, backend side: since the
partner graduation, extension components register in /api/v1/all under
their namespaced ids (ext:openai:OpenAIModelComponent@official), so the
bare 'OpenAIModel' key these tests hardcoded no longer exists on any
Python version (the 'py3.14-only' Group 2 failure was the only leg that
ran; 3.10 was cancelled by fail-fast). The MCP feature itself is fine —
search returns the namespaced ids and add_component accepts them.

- Use LanguageModelComponent (in-tree, stable bare key; SecretStr
  api_key, real_time_refresh fields, advanced 'stream', LanguageModel
  output) for the redaction/configure/search/describe/spec tests.
- configure_dynamic_field: model_name -> api_key (the refresh field on
  LanguageModelComponent).
- prompt-template-variables spec: drop the model node — server-side
  validation builds the graph and a model without an API key fails its
  build; the test's subject is the dynamic {var} fields.

Full file: 69 passed.

* Update test_mongodb_atlas.py

* Frontend tests

* Frontend test updates

* fix: restore loading.py and tableAutoCellRender to base-branch state

Commit 9df9475615 ('Frontend test updates') accidentally reverted both
files to their pre-#9902 state, removing the no_env_fallback contract in
load_from_env_vars / update_table_params_with_load_from_db_fields, the
defensive params.pop('code') in build_custom_component, and the
localValue/updateGlobalVariableCell handling in tableAutoCellRender.
The base branch's regression tests (test_loading_no_env_fallback.py,
test_loading_custom_component_code_param.py, tableAutoCellRender
index.test.tsx) were kept, so the reversion failed them.

* ci(bundles): make freeze gate reachable on PRs; review follow-ups

- Move the freeze-components job from lint-py.yml (workflow_call/
  workflow_dispatch only -- never invoked, so the gate could not block a
  PR) into extension-migration-checks.yml, which already triggers on
  pull_request for src/lfx/src/lfx/components/**. Add the gate's script
  and baseline to the path filter.
- Add timeout-minutes to the cross-bundle-test jobs so a hung install or
  test leg cannot occupy a runner for the 360-minute default.
- _discover_shimmed_component_dirs: read only the first line of each
  __init__.py instead of the whole file -- the shim marker contract is
  line 1 (enforced by test_shim_source_contract), and this prevents a
  non-shim file with the marker after leading blank lines from being
  misclassified and silently skipped from the in-tree walk.

* fix(bundles): restore legacy-name resolution for ext components; sync starter projects

Saved flows reference moved providers by their legacy palette identity --
either the bare class name (TavilySearchComponent) or the component's
name attribute (AstraDB, Chroma, needle, OllamaModel). After the provider
move neither resolved to a current template on the backend:

- lfx.utils.component_aliases now derives the bare class name (and its
  Component-stripped form) from ext:<bundle>:<Class>@<slot> keys,
  mirroring the frontend's getTemplateAliases. Ext templates carry
  name=None / _type='Component', so the key is the only source.
- _decorate_template_with_extension now stamps template['name'] with the
  component's legacy identity (name attr, falling back to class name).
  In-tree palette entries expose it as the dict key; ext entries are
  keyed by namespaced id, so without this both alias maps lose the only
  bridge from node types like 'AstraDB' to the current template.

Without these, the starter-project updater could not match moved-provider
nodes, so their embedded code stayed one whitespace change behind the
bundle sources -- every such flow showed the update-all banner, and
because tool-mode nodes' saved outputs ([component_as_tool]) differ from
the template's natural outputs the update was flagged breaking, opening
the confirmation modal instead of toasting 'successfully updated':
deterministic timeouts in the starter-project Playwright specs (shards
29/30/36/37). Regenerated the 13 affected starter projects with
scripts/ci/update_starter_projects.py (idempotent; code_hash hex strings
in the regenerated JSONs are detect-secrets false positives).

Also converts test_groq_integration.py to the captured-module
patch.object pattern (same module-identity trap as test_mongodb_atlas:
dotted-name @patch can land on a different materialization of the moved
module than the one that defined the class, leaving the real
get_groq_models in the instance's globals -- order-dependent failure in
unit-test Group 5).

* fix(bundles): keep importable module paths in starter projects

The starter-project updater syncs node metadata from the live templates
(NODE_FORMAT_ATTRIBUTES includes 'metadata'), and ext components report
their runtime sys.modules namespace there (_lfx_ext.official.<p>.<m>) --
a path that only exists inside a running extension loader. Persisting it
broke test_template_field_order_matches_component in both template jobs
('No module named _lfx_ext'): the test imports metadata.module to
instantiate the component, and the migration table likewise keys on the
legacy lfx.components.<provider> form that the bundle shims keep
importable.

_merge_node_metadata now preserves the node's stored module when the
live template's is a runtime _lfx_ext.* path, while still syncing the
rest of the metadata (e.g. dependencies). Starter projects regenerated
from the pre-pollution state with the fixed updater: the only diff vs
the previous regen is the 20 module lines reverting to the legacy
importable form. Regression tests added for both merge directions.

* feat: add Oracle integration (#13502)

* Add Oracle Integration

* [autofix.ci] apply automated fixes

* Fix styleUtils problem

* fix(oracledb): address review feedback

* fix(oracledb): apply formatting

* Fix component index

* refactor(oracledb): convert Oracle integration to lfx-oracle extension bundle

Port the in-tree lfx.components.oracledb provider into a standalone
Extension Bundle distribution (src/bundles/oracle, dist lfx-oracle,
package lfx_oracle, bundle name 'oracle') per src/bundles/PORTING.md:

- Move the four components + connection helper into the bundle; deps
  (oracledb, langchain-oracledb, langchain-community) move from the
  langflow-base[oracledb] extra into the bundle's pyproject
- Add migration-table entries mapping legacy bare class names and
  lfx.components.oracledb.* import paths to ext:oracle:<Class>@official
- Add test_pilot_oracle_upgrade.py integration coverage (mirrors ibm)
- Move backend unit tests into src/bundles/oracle/tests with bundle
  import paths; drop the empty ComponentTestBaseWithoutClient
  version-fixture scaffolding (component is new in 1.11.0)
- Declare explicit outputs on OracleVectorStoreComponent (mirrors
  LCVectorStoreComponent) so lfx extension validate passes
- Regenerate component index (oracledb category removed) and uv.lock;
  wire workspace member/dep/source in root pyproject
- Frontend sidebar entry renamed oracledb -> oracle; docs page renamed
  to bundles-oracle following the extracted-bundle convention

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Eric Hare <ericrhare@gmail.com>

* feat(lfx): fail-fast dependency preflight for `lfx run` (#13680)

Detect a flow's required third-party packages before the graph load and
fail fast with an actionable `pip install ...` message when any are
missing, instead of a deep ModuleNotFoundError mid-build. Addresses the
'export a flow, run it on a bare pip install lfx, it fails' confusion
introduced by the bundle split.

- flow_requirements: add find_missing_dependencies / _is_installed /
  format_missing_dependencies_error (best-effort, reuses the existing
  flow analyzer).
- run.base: _preflight_dependencies helper + check_dependencies gate in
  run_flow (JSON flows only; .py uses PEP 723; fail-open on analysis
  errors so a runnable flow is never blocked by the check itself).
- CLI: --check-dependencies/--no-check-dependencies on `lfx run`.
- tests: unit (find_missing_dependencies/format) + integration (run_flow
  + _preflight_dependencies).

* feat(bundles): move spider + toolguard provider components into lfx-bundles

Two provider components were still living inside otherwise-core directories
(file-level stragglers from the metapackage split). Both are portable
(lfx-only imports) and gated by dedicated optional SDKs, so they belong in
the lfx-bundles metapackage rather than lfx core:

- SpiderTool (langchain_utilities/spider.py) -> lfx_bundles/spider/, dep
  spider-client (`spider` extra).
- PoliciesComponent + policies/ submodule (models_and_agents) ->
  lfx_bundles/toolguard/, dep toolguard (`toolguard` extra).

Changes:
- Move the files into lfx_bundles/<name>/; rewrite policies' absolute
  self-imports (lfx.components.models_and_agents.policies -> lfx_bundles.toolguard.policies).
- Drop SpiderTool / PoliciesComponent from the core langchain_utilities /
  models_and_agents lazy-import registries.
- Add `spider` and `toolguard` extras to lfx-bundles pyproject + the generated
  `all` aggregate; uv.lock re-resolved (uv lock --check clean).
- Append migration entries (4 per class) mapping the historical
  lfx.components.* paths to ext🕷️SpiderTool@official /
  ext:toolguard:PoliciesComponent@official so saved flows migrate.
- Regenerate component_index.json (both classes drop out of core; 263
  components / 45 categories).
- Repoint the toolguard policies tests to lfx_bundles.toolguard (60 pass).

The toolguard deadlock-warmer (_warm_circular_imports) is unaffected: it warms
toolguard.runtime directly and runs single-threaded before the unified
component fan-out that loads bundle components.

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

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

* feat(bundles): consolidate cleanlab, twelvelabs, jigsawstack into lfx-bundles

First directory-level batch of the core-tail consolidation (validates the
consolidate_bundles.py path end-to-end after the spider/toolguard file-level
extractions). All three are flat, lfx-only, single-SDK providers:

- cleanlab    -> lfx_bundles/cleanlab    (cleanlab-tlm)   3 components
- twelvelabs  -> lfx_bundles/twelvelabs  (twelvelabs)     7 components
- jigsawstack -> lfx_bundles/jigsawstack (jigsawstack)   11 components

Via scripts/migrate/consolidate_bundles.py --apply: moves each dir into the
metapackage + leaves a fail-soft shim, merges per-provider extras and regen
`all`, appends the 4-entry migration block per class (84 entries, 0 ambiguous).
Component index regenerated (45->42 categories, 263->242 core components);
uv.lock re-resolved; twelvelabs test repointed to lfx_bundles.twelvelabs (passes).

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

* feat(bundles): consolidate vector/datastore + community-wrapper providers into lfx-bundles

Second directory-level batch (8 providers, 12 components) via
consolidate_bundles.py --apply:

- baidu (qianfan + langchain-community), redis (redis + community),
  elastic (elasticsearch + langchain-elasticsearch + opensearch-py)
- bing, cloudflare, maritalk, searchapi, vectara (langchain-community REST wrappers)

Moves each dir into lfx-bundles + fail-soft shim, merges per-provider extras
and regen `all`, appends 4-entry migration block per class (48 entries, 0
ambiguous). Component index regenerated (42->34 categories, 242->230 core
components); uv.lock re-resolved.

Test fixups:
- Repoint baidu/elastic/searchapi backend tests to lfx_bundles.<provider>.
- test_dynamic_imports (lfx isolated suite) used the now-bundled searchapi as
  its fixture; bundles aren't installed in the engine-only lfx env, so switch
  it to the core langchain_utilities.FakeEmbeddingsComponent (same
  langchain_community optional-import path). 131 shim tests + 21 dynamic-import
  tests pass.

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

* [autofix.ci] apply automated fixes

* feat(bundles): consolidate homeassistant, olivya, agentql REST providers into lfx-bundles

Third batch (3 providers, 4 components) via consolidate_bundles.py --apply.
These are thin REST wrappers with no dedicated vendor SDK:

- homeassistant (requests) -> lfx_bundles/homeassistant   2 components
- olivya (httpx, lfx core)  -> lfx_bundles/olivya          1 component
- agentql (httpx, lfx core) -> lfx_bundles/agentql         1 component

Moves + shims + per-provider extras + `all` regen; 16 migration entries (0
ambiguous). Component index 34->31 categories, 230->226 components; uv.lock
re-resolved. No tests reference these providers.

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

* feat(bundles): consolidate google, vertexai, altk, codeagents into lfx-bundles

Fourth directory-level batch (4 providers, 14 components) via
consolidate_bundles.py. google/vertexai (langchain-google-*), altk
(agent-lifecycle-toolkit), codeagents (smolagents + OpenDsStar); platform/py
markers copied verbatim from langflow-base extras. 56 migration entries (0
ambiguous); index 31->27 categories, 226->212 components. Backend tests
repointed; starter projects embedding moved components regenerated via the
validator. SKIP=detect-secrets: only flags are regenerated code_hash hex
(false positives).

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

* test(lfx): decouple dynamic-import unit tests from soon-to-be-bundled providers

test_dynamic_imports.py and test_import_utils.py used composio/nvidia/huggingface
as the "lazy category whose SDK is absent in the engine-only lfx env" fixture.
That couples the import-mechanism tests to specific providers — they have broken
each time a provider graduated to a bundle (openai/anthropic/chroma -> composio,
now composio et al.).

Switch to stable in-tree core categories:
- files_and_knowledge.KnowledgeComponent for the "missing-dep" path (its module
  imports langchain_chroma / langflow, absent in bare lfx) — works for both the
  import_mod direct calls and the lfx.components hierarchy access.
- helpers / processing for the "imports cleanly" and "module not found" paths.

No behavior change; 43 tests pass in the isolated lfx env. This unblocks moving
composio, huggingface, nvidia, cuga into lfx-bundles without breaking these tests.

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

* feat(bundles): consolidate composio, huggingface, nvidia, cuga into lfx-bundles

Fifth batch (4 providers, 71 components) via consolidate_bundles.py --apply,
unblocked by the prior dynamic-import test decoupling:

- composio    -> lfx_bundles/composio    (composio + composio-langchain)  63 components
- huggingface -> lfx_bundles/huggingface (langchain-huggingface[marker], huggingface-hub, community)  2 components
- nvidia      -> lfx_bundles/nvidia      (langchain-nvidia-ai-endpoints, nv-ingest-client[py3.12], gassist[win32])  5 components
- cuga        -> lfx_bundles/cuga        (cuga; platform/py markers)  1 component

Markers copied verbatim from langflow-base extras (uv lock --check clean).
284 migration entries (0 ambiguous); component index 27->23 categories,
212->141 components.

Tests: relocate the dedicated lfx-isolated tests for the now-bundled components
to the backend suite (test_nvidia_component.py, test_cuga_session_id_fallback.py)
repointed to lfx_bundles.<provider>; repoint backend composio/huggingface/cuga
imports + a cuga patch target; pragma a fake test api_key. 43 decoupled lfx
import tests pass post-move. SKIP=detect-secrets: only flags are regenerated
component_index code_hash hex (false positives).

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

* [autofix.ci] apply automated fixes

* feat(bundles): consolidate langwatch evaluator into lfx-bundles

langwatch -> lfx_bundles/langwatch (1 component). The LangWatchComponent is a
pure httpx REST wrapper (its only non-core touchpoint is lfx.base.langwatch.utils,
which is also httpx-based) — the langwatch SDK extra in langflow-base is for the
tracing service, not this component, so the bundle declares no extra deps.

Moves + shim + `all` regen; 4 migration entries (0 ambiguous). Component index
23->22 categories, 141->140 components. Backend langwatch test repointed
(16 passed). SKIP=detect-secrets: only flags are regenerated component_index
code_hash hex (false positives).

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

* feat(bundles): consolidate FAISS, Notion into lfx-bundles (lowercase-slug support)

Uppercase source dirs need lowercase bundle slugs (BUNDLE_NAME_RE is
lowercase-only). Enhance consolidate_bundles.py with bundle_slug(provider) =
provider.lower(): the bundle dir, ext id, shim target, and extra key use the
lowercased slug, while the migration import_path keeps the historical
lfx.components.<Provider> casing so saved flows still resolve. Identity for
already-lowercase providers (no change to prior entries).

- FAISS  -> lfx_bundles/faiss  (faiss-cpu[py-split] + langchain-community)  1 component
- Notion -> lfx_bundles/notion (requests + Markdown)  8 components

test_bundle_shims.py: derive the expected bundle name by lowercasing the shim
dir name (FAISS shim aliases lfx_bundles.faiss) — 159 shim tests pass.
Relocate the lfx-isolated FAISS test to backend/vectorstores (repointed to
lfx_bundles.faiss.faiss). 36 migration entries (0 ambiguous); index 22->20
categories, 140->131 components. SKIP=detect-secrets: regenerated code_hash hex.

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

* feat(bundles): fold vectorstores LocalDBComponent into the chroma bundle

The lone LocalDBComponent is Chroma-backed (eager `from langchain_chroma import
Chroma`), so it belongs in the existing `chroma` bundle rather than a new one —
its deps are already covered by the chroma extra (no new deps).

- Move vectorstores/local_db.py -> lfx_bundles/chroma/local_db.py; register
  LocalDBComponent in the chroma bundle's lazy registry.
- Replace lfx.components.vectorstores with a CROSS-BUNDLE shim aliasing
  lfx_bundles.chroma (dir != bundle).
- Migration: lfx.components.vectorstores[.local_db].LocalDBComponent ->
  ext:chroma:LocalDBComponent@official (4 entries, 0 ambiguous).
- test_bundle_shims.py: add a cross-bundle map {vectorstores: chroma} so the
  shim contract validates (161 shim tests pass).
- Repoint the backend LocalDB test (import + patch target) to lfx_bundles.chroma;
  test_all_modules_importable resolves it via the shim.

Component index 20->19 categories, 131->130 components. SKIP=detect-secrets:
regenerated code_hash hex (false positives).

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

* [autofix.ci] apply automated fixes

* feat(bundles): remove unbundleable vlmrun + notdiamond components from lfx core

Per the core-tail audit, four providers couldn't be bundled. vlmrun (imports
`from langflow`, violating the lfx engine boundary) and notdiamond (its
`notdiamond` SDK isn't in the lockfile or any extra, so it can't run) have no
engine references, no starter flows, and no migration-table entries — they're
effectively dead in every distribution, so remove them outright.

(crewai and agentics are kept: crewai is woven into 3 starter-project example
flows, and the core Agent hard-depends on agentics.helpers.model_config — both
need deliberate upstream/refactor work, not deletion.)

- Delete src/lfx/.../components/{vlmrun,notdiamond}/ and drop them from the
  lfx.components top-level registry (TYPE_CHECKING / _dynamic_imports / __all__).
- Regenerate component index (19->17 categories, 130->128 components) and backend
  locales (orphan keys removed; extract_backend_strings --check OK).
- Repoint test_dynamic_imports.test_import_error_handling off the deleted
  notdiamond to the core processing category (43 lfx import tests pass).

SKIP=detect-secrets: only flags are regenerated component_index code_hash hex.

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

* test(lfx): tie flow-builder search 'returns all' to registry size

The hardcoded `count > 100` assertion predated the bundle-extraction
work, which deliberately moves non-legacy components out of lfx core
(now 66 non-legacy of 128 indexed). Assert the empty-query count equals
the full non-legacy registry instead — the durable 'no query == every
component the registry exposes' contract that survives future bundle
moves.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(bundles): deterministic component alias resolution + sidebar slug casing after bundle move

Moving agentql/composio and the long tail into lfx-bundles made two
shared-label resolutions order-dependent and left two sidebar slugs
mis-cased, surfacing as red CI on the starter-projects template tests
and the bundles-sidebar e2e.

- flatten_components_with_aliases: register every component's identity
  aliases (registry key / ext class name / name) before any component's
  display_name alias, so a component's own class identity always wins
  its key regardless of registry iteration order. A single setdefault
  pass previously let the Composio 'AgentQL'/'SerpAPI' wrappers
  (display_name) shadow the standalone components (class name) depending
  on load order, so update_starter_projects wrote the wrong
  metadata.module into the AgentQL starter node.
- Correct the AgentQL node module in News Aggregator.json and Price Deal
  Finder.json back to lfx.components.agentql.agentql_api.AgentQL.
- SIDEBAR_BUNDLES: lowercase the Notion/FAISS name slugs to match the
  lowercase bundle category names so both bundles render in the sidebar
  again (display_name and icons unchanged).
- Add order-independence regression tests for the identity/display tiers.

* test(weaviate): patch the class's own module, not a fixed string path

The Weaviate component moved into lfx-bundles, so weaviate.py is now
reachable under several module identities (the lfx.components.weaviate
shim, lfx_bundles.weaviate.weaviate, and the runtime _lfx_ext.* copy).
On Python 3.10, unittest.mock resolves a dotted patch target via a
getattr walk that follows the shim's package alias to a *different*
module object than the one the imported class actually lives in, so
patching 'lfx.components.weaviate.weaviate.WeaviateVectorStore' missed
the object the component body reads -> 'WeaviateVectorStore called 0
times' when an earlier test in the group forced the alternate identity.

Resolve the module from the imported class (sys.modules[Cls.__module__])
and patch.object it directly, bypassing dotted-string resolution. Same
fix for AuthApiKey. Order/identity-independent on all Python versions.

* test(ollama,perplexity): patch the class's own module, not a fixed string path

The Ollama and Perplexity components moved into lfx-bundles, so ollama.py
and perplexity.py are now reachable under several module identities (the
lfx.components.<x>.<x> shim, the canonical lfx_bundles.<x>.<x>, and the
runtime _lfx_ext.* ext-loader copy). On Python 3.10, unittest.mock
resolves a dotted patch target via a getattr walk that follows the shim's
package alias to a *different* module object than the one the imported
class / component body actually use, so patching e.g.
"lfx.components.ollama.ollama.ChatOllama" missed the object the body reads
-> "mock called 0 times" depending on suite order.

Resolve the module from the imported class (sys.modules[Cls.__module__])
and patch.object it directly, bypassing dotted-string resolution:
- perplexity: ChatPerplexity -> patch.object(_PERPLEXITY_MODULE, ...)
- ollama: ChatOllama / logger -> patch.object(_OLLAMA_MODULE, ...)
- ollama: ChatOllamaComponent._parse_json_response ->
  patch.object(ChatOllamaComponent, ...) (the imported class itself)
- ollama: httpx.AsyncClient.get/post -> patched on the global httpx
  (a single class object, identity-independent), dropping the shim prefix

Same fix as the Weaviate test (064069b6bb). Order/identity-independent on
all Python versions; the langchain_ollama / lfx.utils.util / socket
patches target stable modules and are left unchanged.

* fix(locales): restore en.json keys for still-shipping spider/policies components

The spider (SpiderTool) and toolguard (PoliciesComponent) components live in
lfx-bundles and still ship in the palette, but their 44 en.json keys were
pruned while all 6 translated locales (de/es/fr/ja/pt/zh-Hans) retained them.
The extractor walks only lfx.components, so bundle-only components with no
compat shim get dropped from en.json on regeneration. Left as-is, the next GP
upload/download cycle would strip ~264 existing translations (44 keys x 6
langs) for two components that are still in the palette.

Restore the 25 spidertool + 19 policies keys to re-align en.json with the
translated locales. (Durable fix — making the extractor walk src/bundles and/or
adding lfx.components shims for these two providers — is a follow-up, since
spider.py eager-imports its SDK and a shim alone won't make it walkable.)

* chore(deps): drop dead astradb/graph-retriever extras and langchain-aws from langflow-base

After datastax/amazon graduated to lfx-datastax/lfx-amazon, langflow-base still
declared astradb (langchain-astradb), graph-retriever (langchain-graph-retriever,
graph-retriever) and langchain-aws in its aws extra -- all pulled into the
complete aggregate -- though zero core .py files import them (the consuming
components moved to the bundles, which re-declare the same deps). This duplicated
the SDKs in every pip install langflow and was inconsistent with the
openai/anthropic/cohere extras already removed in this PR.

Remove the astradb and graph-retriever extras and their complete refs, and drop
langchain-aws from the aws extra (boto3 stays -- it backs langflow-base's own S3
storage, not just the amazon components). Regenerate uv.lock; closure unchanged,
the SDKs still resolve via lfx-datastax/lfx-amazon.

* ci(extension-migration): enforce component os.environ-write lint in CI

check_component_env_writes.py guards against components writing to process-global
os.environ (which bleeds one caller's credential into other requests on a warm
worker). It was wired only as a pre-commit hook, bypassable via git commit -n,
web-UI commits, or uninstalled hooks. Add it as a stdlib-only job in
extension-migration-checks.yml next to freeze-components, so the credential-bleed
guard is enforced in CI regardless of local pre-commit.

* test(bundles): cover migration-table completeness, preflight provider path, frozen gate

Three test-coverage gaps from the metapackage split, all green on the current tree:
- migration-table completeness: parametrized sweep asserting every lfx_bundles
  provider component class (196 across 72 providers) is reachable via
  migration_table.json as an ext:<provider>:<Class>@ target, so a provider moved
  into a bundle without a table entry fails CI instead of silently breaking saved
  flows. Includes a negative control.
- dependency preflight provider path: drives find_missing_dependencies and
  _preflight_dependencies through a node with a model/agent_llm provider field so
  the _PROVIDER_PACKAGE_FALLBACKS path (the only working provider-dep source in
  engine-only installs) is exercised.
- frozen-components gate: unit tests for check_components_frozen.py covering
  additions (fail), removals (allowed), and the init/pycache/dotdir and
  comment-parsing rules.

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

* fix(locales): make spider/toolguard i18n keys survive regen via shims

The earlier restore of the 44 spider/policies en.json keys was reverted by the
gp-backend-check bot: it auto-regenerates en.json on every push to this PR (the
PR-level path filter matches the bundle moves under lfx/components) and the
bundle-blind extractor could not reproduce the keys.

Make them durable the way every other moved provider does:
- Add lfx.components.spider and lfx.components.toolguard '# lfx-bundles-shim'
  packages re-pointing to lfx_bundles.{spider,toolguard}, so the extractor (which
  walks lfx.components) emits SpiderTool/PoliciesComponent keys again.
- Lazy-import spider's SDK (from spider.spider import Spider moved into crawl())
  so SpiderTool is importable for extraction/registration without spider-client,
  matching the lazy pattern weaviate/zep already use (toolguard was already lazy).
- Add spider/toolguard to scripts/ci/frozen_component_dirs.txt so the
  additions-only freeze gate accepts the new shim dirs.
- Restore the 44 keys to en.json.

Verified the extractor now reproduces exactly the 44 keys (25 spidertool + 19
policies), so the bot keeps them instead of dropping them. Bundle components are
dynamic-only (not in component_index.json), so no index regen is needed.

* chore(bundles): declare astrapy in lfx-datastax; align lfx-ibm manifest version

- lfx-datastax imports astrapy directly (astrapy.DataAPIClient/Database in the
  astra components) but only pulled it transitively via langchain-astradb.
  Declare it explicitly, pinned to match langflow-base's astrapy extra
  (>=2.1.0,<3.0.0).
- lfx-ibm's extension.json reported version 0.1.0 while pyproject.toml is 0.1.1;
  the loader uses the manifest version, so bump it to 0.1.1 to match.
Regenerate uv.lock (closure unchanged; astrapy was already resolved
transitively).

* refactor(migrate): guard consolidate against subdir data loss; fix alias docstring

- consolidate_bundles.move_provider copies only top-level *.py but rmtree's the
  whole source tree, so a provider with a subpackage would have its subdir
  silently destroyed and never migration-mapped. Refuse with a clear error
  instead of half-moving (port_bundle.py handles nested layouts). No current
  PROVIDER_DEPS entry has a subdir, so this is a defensive guard.
- component_aliases: the order-independence docstrings overclaimed. Scope the
  guarantee to identity-beats-display (and real-key-beats-alias) and note that
  two components contributing the same identity alias remain first-wins by
  iteration order.

* ci(gp-backend): regen en.json on src/bundles/** edits

The gp-backend-check bot regenerates locales/en.json on PR changes, but its
path filter only watched src/lfx/src/lfx/components/**. Bundle component
strings reach core en.json through the lfx/components/<provider> import shims
(e.g. spider, toolguard), so editing a bundle's display_name/description/info
under src/bundles/** would not re-trigger the extractor and en.json could
silently drift from the translated locales. Add src/bundles/** to the trigger
so bundle-side string edits keep en.json aligned; regen is diff-gated, so
non-string bundle edits remain a no-op.

* fix(bundles): Policies in Models & Agents core; Spider in bundles sidebar

Policies (toolguard) component:
- Move PoliciesComponent + policies/ helpers out of the lfx-bundles
  consolidated metapackage back into lfx/components/models_and_agents core
  (restores the main/release-1.11.0 layout); register it in the category
  __init__ and restore the core-path tests.
- toolguard stays an optional langflow-base extra (langflow-base[toolguard],
  pulled in via [complete]); the component imports it lazily, so a bare lfx
  install still loads/inspects it.
- Remove the lfx-bundles toolguard provider (dir + extra + [all] entry), the
  lfx core import shim, and the frozen_component_dirs.txt entry; drop the 4
  ext:toolguard migration entries; regenerate component_index (128 -> 129)
  and uv.lock.

Spider:
- Add spider to SIDEBAR_BUNDLES so it renders in the Bundles section like
  arXiv instead of falling into the default categories (it was missing from
  the list while peer lfx-bundles providers were present).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(bundles): fix error-tier mirror claim, list-time discovery note, metapackage README

Addresses three documentation nits from the PR #13563 re-review:

- errors.py: the lfx.bundles diagnostics comment claimed a full mirror of the
  inline tier's split, but only the duplicate case matches. name-invalid and
  root-unreadable are warning-only here where their inline counterparts are
  hard errors (ok=False). Tighten the comment to match actual behavior.
- BUNDLE_API.md: note that manifest-less lfx.bundles providers are invisible to
  `lfx extension list` (discover_all_extensions walks only installed + seed;
  load_lfx_bundles_extensions runs in the startup component-loading path), so
  the precedence story isn't read as a list-time property.
- lfx-bundles/README.md: the bulk move has landed on this branch (71 providers,
  populated per-provider extras, generated `all`), so drop the "empty skeleton /
  available later" framing and document the final state.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(bundles): surface pip-install guidance for missing SDKs on the server run path

A flow loaded through the UI/API on an engine-only `lfx` (no bundles)
failed deep in the vertex build with a bare
`Error creating class. ModuleNotFoundError(No module named 'langchain_chroma')`.
The actionable install guidance only existed on the `lfx run` preflight
and the legacy import shim; the server run path
(simple_run_flow -> run_graph_internal -> graph.arun()) had neither.

Both vertex-build error formatters (lfx's `format_exception_message`, used
by graph.arun(), and langflow's, used by the build/chat endpoints) now
detect a plain ModuleNotFoundError in the cause chain and attach the same
`pip install <package>` guidance the CLI gives, mapping the missing import
to its PyPI distribution via the existing requirements resolver. Bundle-shim
"components moved to ..." messages are left untouched (their message does
not start with "No module named"), so graduated providers stay actionable.

* fix(bundles): surface curated shim message over its raw cause in exception formatter

get_causing_exception walked to the deepest __cause__, so a graduated
provider's curated "components moved to ..." ModuleNotFoundError (raised
`from` the raw "No module named 'lfx_<x>'") was unwrapped past it. The
formatter then regenerated bare "pip install lfx-<x>" guidance, dropping
the curated text — including the "or pip install langflow, which bundles
it" clause.

Both get_causing_exception copies (lfx + langflow) now stop at the first
ModuleNotFoundError whose message is not a raw "No module named ..." (the
curated shim error), and module_not_found_hint surfaces that curated
message verbatim. Plain SDK import errors are unchanged.

Adds regression tests reproducing the real 3-level cause chain on both
formatters; the old test used a truncated chain that hid this.

* fix(bundles): cover OpenRouter/IBM WatsonX in Orchestrate requirements detection (#13732)

fix(bundles): cover OpenRouter/IBM WatsonX in wxO requirements detection

The bundle split removed the langchain provider SDKs from lfx's own
dependency tree, which makes flow→requirements detection load-bearing for
Watsonx Orchestrate deployments: the runner installs only `lfx` + the
packages `generate_requirements_from_flow` emits, so a provider missing
from the resolution tables yields an empty requirements set and the
deployed flow fails to import its model class.

Two unified-selectable providers were unresolved:
- OpenRouter (runs on ChatOpenAI) was in no resolution table -> [].
- "IBM WatsonX" (catalog casing) only matched the legacy "IBM watsonx.ai"
  key; langchain-ibm landed only by an embedding-path coincidence.

Add both to _PROVIDER_PACKAGE_FALLBACKS (keyed by the exact catalog
strings) so every provider in MODEL_PROVIDER_METADATA resolves.

Tests:
- TestBundleSeparationOrchestrate in test_flow_requirements.py: catalog
  coverage guard, OpenRouter/IBM regressions, and split-contract guards
  (bundle dists and langchain imports must stay out of lfx's tree).
- End-to-end artifact tests in test_watsonx_orchestrate.py asserting a
  bundle-provider flow emits the langchain SDK (not lfx-openai/lfx-bundles)
  and that requirements.txt pins lfx as its first line.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

* Backend format

* Format again

* [autofix.ci] apply automated fixes

* fix(bundles): pin datastax ruff line-length to 120 to stop format tug-of-war

The datastax bundle's pyproject.toml declares a [tool.ruff.lint.per-file-ignores]
table, which makes ruff treat the directory as a standalone config root. It never
restated line-length, so ruff fell back to its default of 88 there. As a result
`make format_backend` (per-directory config discovery) wrapped lines to 88 while
the pre-commit ruff-format hook (run with --config pyproject.toml, forcing the
repo-wide 120) unwrapped them back, leaving the two formatters fighting forever.

Restate line-length = 120 so both config-resolution paths agree. Use the explicit
line-length rather than extend=../../../pyproject.toml to avoid pulling in the
root lint ruleset, which the bundle intentionally does not enforce.

* fix(bundles): relocate notdiamond + vlmrun into lfx-bundles

Both providers were dropped during the bulk long-tail move with no bundle
and no compatibility shim, removing them from the registry,
component_index, migration table and locales. That breaks saved flows
using the NotDiamond Router or VLM Run Transcription nodes (vlmrun ships
in langflow[complete]), contradicting the zero-user-facing-change goal.

Relocate both the same way as the other long-tail providers:
- move implementations into src/bundles/lfx-bundles/src/lfx_bundles/
- restore lfx.components.{notdiamond,vlmrun} marker shims
- re-register both in lfx/components/__init__.py
- add per-provider extras (vlmrun -> vlmrun[all]) and the 'all' aggregate
- add migration_table targets ext:{notdiamond,vlmrun}:<Class>@official
- restore the 32 en.json strings; refresh uv.lock

* docs(lfx): correct dependency-preflight scope to lfx run only

The preflight is wired into 'lfx run'; 'lfx serve' has its own
module-not-found hint at request time and does not call it.

---------

Co-authored-by: Elif Sema Balcioglu <elifsemabalcioglu@gmail.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-23 19:37:10 +00:00
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'.
2026-06-23 19:02:37 +00:00
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.
2026-06-23 18:54:38 +00:00
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>
2026-06-23 16:57:40 +00:00
f58914bc84 fix: release candidate version selection (#13776)
* fix: share release candidate version selection

* fix: review comments addressed
1.10.1rc3
2026-06-22 15:35:10 -07:00
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>
2026-06-22 14:10:12 -07:00
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>
2026-06-22 13:53:02 -07:00
0300305dc5 docs: fix empty see also sections in extensions (#13774)
* docs: fix empty see also sections in extensions

* docs: update 1.10x version
2026-06-22 13:22:53 -07:00
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>
2026-06-22 19:55:48 +00:00
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>
2026-06-22 18:48:09 +00:00
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
2026-06-22 11:44:51 -07:00
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>
2026-06-22 11:06:40 -07:00
cc398d94a6 fix(lfx): make OpenDsStar optional (#13749) 2026-06-22 09:15:05 -07:00
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>
2026-06-22 08:40:30 -07:00
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.
2026-06-22 08:40:10 -07:00
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>
2026-06-19 16:15:42 -07:00
9bd47b601f Template and comp update 2026-06-19 15:38:27 -07:00
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>
2026-06-19 15:29:51 -07:00
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>
2026-06-19 15:28:38 -07:00