Commit Graph

18137 Commits

Author SHA1 Message Date
599878da1d fix(docker): pre-create LANGFLOW_CONFIG_DIR so named volumes inherit uid=1000 ownership (#13212)
* fix: pre-create LANGFLOW_CONFIG_DIR in docker images to fix named volume permissions

The Langflow runtime stage runs as uid=1000 (non-root user), but the
official docker_example compose file mounts a named volume at
/app/langflow (LANGFLOW_CONFIG_DIR). When Docker creates a fresh named
volume, it copies the ownership and permissions of the mount-point
directory from the image. Because /app/langflow did not exist in the
image, Docker materialized the mount point as root:root, and the
non-root container user could not write secret_key, profile_pictures,
or per-user subfolders. The container crashed during initialization
with PermissionError on /app/langflow/secret_key.

Pre-create /app/langflow in every runtime stage and chown it to 1000:0
so a fresh named volume inherits the correct ownership and the
container starts cleanly on a rootful Docker daemon.

Podman's rootless user-namespace mapping masked this for anyone who
validated the compose file on Podman; on a rootful Docker daemon the
container exits in a restart loop.

Fixes #10437

* revert: drop docker_example/README.md changes

The Troubleshooting note will live in the user-facing docs site
instead of the in-repo README.
2026-05-21 18:14:28 +00:00
0663fce847 fix(guardrails): add message pass-through outputs (#13096)
* fix(guardrails): add message pass-through outputs

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

* [autofix.ci] apply automated fixes

* feat(guardrails): unify data output payload

* fix(guardrails): add message pass-through outputs

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

* [autofix.ci] apply automated fixes

* feat(guardrails): unify data output payload

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

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes

* fix(guardrails): set result data default to null

* [autofix.ci] apply automated fixes

* [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-05-21 18:10:39 +00:00
02e1b402e5 fix(frontend): blur upload button so "Upload File" tooltip doesn't persist after file picker closes (#13178)
fix(frontend): blur upload button so tooltip doesn't persist after file picker closes

On the My Files page, clicking the "Upload Files" button kept the
"Upload File" tooltip visible after the OS file picker closed. The
button is wrapped in a Radix tooltip, which shows on focus; when the
file dialog closed, focus was restored to the button and re-triggered
the tooltip. Blur the button on click so focus doesn't return to it.

Also tighten pre-existing `any` types in FilesTab to satisfy the
no-explicit-any lint rule on the staged file.
2026-05-21 17:02:11 +00:00
b5db6fd0b2 fix(memory-base): Product Review Improvements (#13248)
* fix(memory-base): rename tool method to "retrieve_memory" and pad trace status badges

- Rename MemoryBaseComponent.retrieve_data to retrieve_memory so the
  derived tool label in tool mode reads "Retrieving Memory" instead
  of "Retrieve Data". Output.name kept as "retrieve_data" to preserve
  saved flow edges.
- Add vertical padding to status badges in TraceAccordionItem and
  SpanDetail so the success/failure label has breathing room inside
  the badge.

Added guardrails to MB system prompt.

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-05-21 15:20:22 +00:00
838175a132 fix(composio): handle Python keywords and non-identifier field names in action schemas (#13139)
* fix: coerce Message and Data to primitives before Composio API execution

* fix: coerce Message and Data to primitives before Composio API execution

* [autofix.ci] apply automated fixes

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

* Fix Ruff line

* fix(composio): handle Python keywords and non-identifier field names in action schemas

* [autofix.ci] apply automated fixes

* fix(composio): remap Python keyword fields to original names in execute_act

* ruff changes fixed

---------

Co-authored-by: Olayinka Adelakun <olayinkaadelakun@Olayinkas-MacBook-Pro.local>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-05-21 13:14:31 +00:00
288bc8efe4 fix(ci): use local bundle wheels in nightly install tests (#13257) 2026-05-20 19:46:40 -07:00
49de16c015 fix(ci): use local bundle wheels in nightly install tests (#13256) 2026-05-20 19:40:00 -07:00
2df2032443 fix: Expose Message connection handle on Run Flow's exposed text inputs (LE-1233) (#13180)
* fix: Expose input handle for run flow

* Update test_run_flow.py
2026-05-20 21:50:02 +00:00
8ee70279f8 feat(memories): surface memory config in summary card with popover (#13209)
* feat(memory): improve memory UI with empty states, toasts, and model filtering

* [autofix.ci] apply automated fixes

* fix: capture memory id/name before setTimeout to prevent stale closure null access

* error improvement and testcase addition

* fix: surface refresh errors, restore API error messages, flatten sidebar ternary, and add keyboard access to batch-size tooltip

* [autofix.ci] apply automated fixes

* biome fix

* fix types

* [autofix.ci] apply automated fixes

* fix: UI polish for Memories and Traces sidebar sections

* [autofix.ci] apply automated fixes

* feat(memories): surface memory config in summary card with popover details

* fix: replace magic index, template literals, and inline noops in Memories/Traces sidebar

* [autofix.ci] apply automated fixes

* ruff fix

* fix testcases

* improve testcase cov import clean up

* [autofix.ci] apply automated fixes

* fix import order

* accessibility fix

* [autofix.ci] apply automated fixes

---------

Co-authored-by: Olayinka Adelakun <olayinkaadelakun@mac.war.can.ibm.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Olayinka Adelakun <olayinkaadelakun@Olayinkas-MacBook-Pro.local>
2026-05-20 20:16:25 +00:00
dfb34c7be4 fix(agent): scope chat history retrieval by flow_id to prevent cross-flow leak (#13087)
* fix(agent): scope chat history retrieval by flow_id to prevent cross-flow leak (#13059)

AgentComponent.get_memory_data filtered chat history by session_id only, so
the playground's default session names (e.g. "New Session 0") leaked
conversation history across unrelated flows whenever two flows happened to
use the same default name.

The fix replaces the ad-hoc MemoryComponent spawn (whose internal _vertex is
None and therefore cannot see the running flow's flow_id) with a direct
aget_messages call that passes flow_id from the agent's own graph context.
MemoryComponent.retrieve_messages is also updated to scope by self.graph.flow_id
when available, so the standalone Message History component does not have
the same leak.

Adds regression tests covering: UUID coercion of string flow_ids, isolation
of two flows that share a session name, graceful fallback when flow_id is
missing or non-UUID, untouched external-memory path, and filtering out the
current input message.

* fix(agent): preserve n_messages=0 disable-memory contract and apply scoping to Cuga

Review follow-up on #13087:

- AgentComponent.get_memory_data regressed n_messages=0: before this PR the
  call went through MemoryComponent.retrieve_messages which short-circuits to
  [] when n_messages==0. The direct aget_messages call lost that contract,
  because `if self.n_messages:` is falsy for 0 and `messages[-0:]` returns
  every message fetched under limit=10000.

- CugaComponent.get_memory_data had the same leak pattern (issue #13059)
  because it also spawned an ad-hoc MemoryComponent whose _vertex is None.

Extract aget_agent_chat_history(session_id, flow_id, context_id, n_messages)
into memory.py:
  * returns [] when n_messages == 0
  * coerces flow_id to UUID (gracefully falls back on invalid values)
  * applies n_messages slicing

Both AgentComponent and CugaComponent now route through it.

Tests:
  * aget_agent_chat_history: passes flow_id as UUID, short-circuits on
    n_messages=0, slices to most-recent N, returns all on n_messages=None,
    falls back to unscoped query on invalid flow_id.
  * AgentComponent integration: routes through helper with flow_id, filters
    out current input, n_messages=0 disables memory (asserts aget_messages
    is never awaited so a future regression resurfaces as a real DB call).
  * CugaComponent integration: scopes by flow_id, n_messages=0 disables
    memory. Module-import gated for envs without optional Cuga deps.

* [autofix.ci] apply automated fixes

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

* [autofix.ci] apply automated fixes

* Template update

* [autofix.ci] apply automated fixes

* Update .secrets.baseline

* [autofix.ci] apply automated fixes

* Update starter projects

* [autofix.ci] apply automated fixes

* Update .secrets.baseline

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes

* fix(memory): address PR #13087 review — symmetric flow_id, DESC fetch, loud fallback

Addresses the review comments on PR #13087:

I1 (symmetric flow_id handling): `store_message` now routes the
internal-memory write through `_coerce_flow_id_to_uuid(_safe_graph_flow_id(self))`
instead of accessing `self.graph.flow_id` directly, so an ad-hoc /
custom-subclass MemoryComponent without `_vertex` no longer crashes
on the write half before reaching the safe read half. Both calls share
a single `flow_id_scope` variable.

I2 (>10k row ordering bug): both `aget_agent_chat_history` and
`MemoryComponent.retrieve_messages` (internal-memory branch) now query
in `DESC` order with `limit=n_messages` (falling back to
`MAX_CHAT_HISTORY_FETCH_LIMIT` when no explicit limit is set), then
reverse to the caller's preferred order. The previous ASC + slice shape
returned the chronological FIRST window once sessions exceeded 10k
rows, silently serving stale history.

I3 (loud fallback observability): when `_coerce_flow_id_to_uuid` is
forced to return `None` for a malformed `flow_id`, the log call is now
`logger.error` (was `warning`) and carries a structured `event` tag
(`memory_flow_id_unscoped`). The unbounded-fetch ceiling-hit path
emits a parallel `memory_chat_history_limit_reached` warning. Both
events are explicit alert hooks for observability pipelines so a
regression cannot silently re-enable the cross-flow leak that motivated
the PR.

R1 (magic number): extracted `MAX_CHAT_HISTORY_FETCH_LIMIT = 10_000` as
a module-level constant; reused in both call sites and the test suite.

R2 (signature): tightened `_coerce_flow_id_to_uuid` and
`_safe_graph_flow_id` from `Any` to `str | UUID | None`; tightened
`aget_agent_chat_history`'s `flow_id` argument accordingly.

Tests: extended `test_memory_flow_id_scoping.py` with coverage for
each of the above — symmetric store/read flow_id (I1), DESC+reverse
ordering on retrieve_messages (I2), ceiling-warning emission (I2),
structured error log on flow_id fallback (I3), and explicit-limit
suppression of the warning. All 26 tests pass.

* [autofix.ci] apply automated fixes

* Template update

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-05-20 19:46:51 +00:00
ec3aea7ff8 fix(extensions): discover editable bundles; clean up ticket refs and reload UX (#13219)
* fix(extensions): discover editable installs via langflow.extensions entry-point

`lfx extension list` was silently dropping bundles installed via `uv pip
install -e` / `pip install -e`. Editable installs surface only
`dist-info/` entries in `dist.files`, so the wheel-shaped scan in
`_distribution_manifest_path` finds no `extension.json`. The bundle
pyproject.toml comment already promised an entry-point fallback for this
case, but it was never implemented in discovery.py.

Add `_distribution_manifest_path_via_entry_points` that resolves the
package via `importlib.util.find_spec` (no module body execution) and
locates the manifest under the resulting package directory. The fallback
runs only when `dist.files` produces no manifest, so wheel installs are
unaffected.

Tests:
- editable distribution with dist-info-only files is discovered
- entry-point pointing at an unimportable module yields no record
- wheel-install path never consults find_spec (guards against
  re-importing every bundle package on startup)

* chore(extensions): drop internal ticket refs; relax reload --bundle and --all

Two related changes:

1. Strip internal `LE-NNNN` ticket references from the extension source,
   bundles, tests, and public docs. The references were not actionable
   outside the project and surfaced in user-visible CLI messages and the
   author guide.

2. Relax `lfx extension reload` CLI ergonomics now that local discovery
   (`discover_all_extensions`) gives us the install map without needing
   an HTTP list endpoint:

   - `lfx extension reload <ext_id>`: `--bundle` is now optional; when
     omitted, the bundle name is resolved from local discovery. Explicit
     `--bundle` still wins for cases where the local install isn't
     visible to the running server.
   - `lfx extension reload --all`: iterates over every locally-discovered
     bundle, POSTs reload to each, renders per-bundle status, and exits 1
     if any reload fails. Previously hard-errored as "not yet wired".
   - `--all` is mutually exclusive with a positional id / `--bundle`
     (exit 2 with a clear message).
   - Missing both `extension_id` and `--all` exits 2 pointing at both.

Updated `test_reload_requires_explicit_bundle` (which enforced the old
"--bundle is mandatory" behavior) to cover the new resolution paths.

* docs(bundle-api): changelog entries for editable-install discovery + reload CLI

Satisfies the BUNDLE_API surface-change gate for the editable-install
discovery fallback (entry-point lookup in discovery.py) and the relaxed
`lfx extension reload` CLI (`--bundle` optional, `--all` implemented).

No additional behavior change in this commit -- it only documents the
two changes already shipped in this branch.

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Debojit Kaushik <Kaushik.debojit@gmail.com>
2026-05-20 18:10:59 +00:00
651d9e71a8 fix: coerce numpy scalars in Memory Base metadata to Python primitives (#13211)
* fix: coerce numpy scalars in Memory Base metadata to Python primitives

Chroma persists integer/float metadata as numpy.int64 / numpy.float64
scalars. When the Memory Base component is used as an Agent tool, the
LangChain tool-output path calls vars() on and iterates these values
during serialization, raising:

    TypeError("'numpy.int64' object is not iterable")
    TypeError('vars() argument must have __dict__ attribute')

Coerce metadata values (and the derived _score) to Python primitives in
_format_results so the emitted DataFrame is JSON-safe regardless of how
downstream consumers serialize it.

* [autofix.ci] apply automated fixes

* Update component_index.json

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-05-20 15:40:21 +00:00
c5816cef34 fix: upgrade dependencies for security fixes (#13218)
* chore: upgrade dependencies for security fixes

- Upgrade authlib from 1.7.0 to 1.7.2
- Update Docker build to use Node.js 22.x (includes npm 10.9+)
- Update uv.lock with latest dependency versions

* chore: run npm audit fix

run npm audit fix

---------

Co-authored-by: Janardan S Kavia <janardanskavia@Janardans-MacBook-Pro.local>
Co-authored-by: Adam Aghili <Adam.Aghili@ibm.com>
2026-05-20 11:18:50 -04:00
4915126efb feat(i18n): globalization pipeline — frontend i18n with backend locale support (#12933)
* feat(i18n): globalization pipeline — frontend + backend i18n support

Squash of PR #12825 (feat/gp-frontend-i18n-batch-c) onto release-1.10.0.

- IBM Globalization Pipeline integration with GP REST API client
- react-i18next setup with 7 language support (en, fr, ja, es, de, pt, zh-Hans)
- Lazy-load non-English locale files via Vite dynamic imports
- Language selector in Settings > General page
- Backend: serve translated component metadata via Accept-Language header
- Translate canvas node field labels, outputs, templates on language change
- Translate note nodes via dedicated endpoint
- Translate all UI modals and pages (MCP, Knowledge Base, Settings, etc.)
- CI: GP upload/download workflows, auto-bake note keys
- Content-hash component keys for stable GP translations
- Preserve user-customized node display_name/description on language switch

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

* revert: restore scripts/gp to release-1.10.0 state (no functional changes)

* revert: restore package-lock.json and flows_helpers.py to release-1.10.0

package-lock.json: npm noise, not part of this feature
flows_helpers.py: unrelated blank-line change

* revert: remove out-of-scope backend changes

- starter_projects/*.json: trivial trailing newline only, not i18n content
- api/v1/flows.py: unrelated deployment guard removal and flow update refactor
- tests/unit/test_i18n_note_translation.py: minor non-functional changes

* revert: restore Pokédex Agent.json to release-1.10.0

* fix(i18n): remove unused import and fix loop variable shadowing in i18n.py

Resolves ruff F401 (content_hash imported but unused) and PLW2901
(for-loop variable `node` overwritten by assignment).

* [autofix.ci] apply automated fixes

* fix(i18n): fix ruff I001 import sort in i18n.py

* fix: remove duplicate useTranslation hook declaration in recentFilesComponent

* fix: restore deployment components overwritten by squash merge and fix i18n test mock

The squash merge brought in older versions of deployment page components,
stripping functionality added by PRs that landed on release-1.10.0 after
our feature branch diverged (duplicate tool name validation, deploy choice
dialog update flow, step-type/step-attach-flows logic).

Restored 14 files to their release-1.10.0 state. Also added __esModule: true
to the i18n mock in flowStore.test.ts to fix the i18n_1.default.t TypeError.

* fix: restore addMcpServerModal, SaveSnapshotButton, KnowledgeBaseUploadModal to release-1.10.0

These files had functionality stripped by the squash merge (buildKeyPairPayload/
buildArgsPayload helpers, save version dialog logic, modal height/validation
constants). Restored to release-1.10.0 state.

* fix: restore flowSidebarComponent ShadTooltip wrappers lost in squash merge

* fix(i18n): revert node_copy rename back to translated_node in translate_flow_notes

The rename was unnecessary — release-1.10.0 already used translated_node
(not reassigning the loop variable node), so ruff PLW2901 doesn't apply.

* chore: sync frontend locale files from GP [skip ci]

* chore: sync backend locale files from GP [skip ci]

* chore: sync backend locale files from GP [skip ci]

* fix(i18n): add missing en.json keys and sync frontend locale files from GP

Added 7 missing keys: alerts.modelsRefreshed, mcp.servers.toolsCount,
misc.fetchErrorDescription/Message, misc.timeoutErrorDescription/Message,
store.results.

* [autofix.ci] apply automated fixes

* ci: add frontend i18n key coverage check with PR report

Adds a script and GitHub Actions workflow that:
- Scans all .ts/.tsx source files for t("key") calls
- Verifies every key exists in en.json
- Posts a markdown report as a sticky PR comment explaining:
  - Missing keys (actionable failures)
  - The expected gap between en.json total and detected keys
    (pluralization suffixes, dynamic keys, test files excluded)

Fails the check if any hardcoded t("key") call has no en.json entry.

* [autofix.ci] apply automated fixes

* feat(i18n): translate remaining hardcoded trace column headers and sync locale files

* fix: resolve ruff style check failures in check_frontend_i18n_keys.py

- Use set membership for multiple comparisons (PLR1714)
- Replace ambiguous Unicode character with plain text (RUF001)
- Split long lines to comply with 120 char limit (E501)
- Replace magic number with named constant (PLR2004)

* [autofix.ci] apply automated fixes

* fix(i18n): narrow zh-* mapping to Simplified Chinese variants only

zh-TW and zh-HK (Traditional Chinese) now fall back to English instead
of incorrectly receiving Simplified Chinese (zh-Hans) translations.

* fix(i18n): use canonical folder names instead of translated strings in use-add-flow

Folder names are stored identifiers used by backend MCP setup and
useGetFolders — translating them caused non-English users to get
unrecognized folder names, breaking starter-project flows and MCP wiring.

* [autofix.ci] apply automated fixes

* fix(i18n): use hardcoded English name for new project to avoid translated MCP server names

* fix(i18n): truncate long sidebar labels and show full text in tooltip

Long translated strings (e.g. Japanese) were wrapping inside the
fixed-height get-started panel. Labels now truncate with ellipsis
and reveal the full text via ShadTooltip on hover.

* [autofix.ci] apply automated fixes

* feat(i18n): translate Deploy button label and sync locale files

* fix(i18n): sync SUPPORTED_LANGUAGES with actual locale files

Remove ru and ko (no locale files exist) and add de (de.json exists).

* fix(i18n): fall back to English for null and empty string translations

Add returnNull and returnEmptyString options so partially translated
locale files with null or empty placeholder values still show English.

* fix(i18n): prevent crash on Chinese browser locale by routing through normalizeLanguage

index.tsx had its own detectedLang using navigator.language.split("-")[0],
which stripped "zh-CN" to "zh" and bypassed normalizeLanguage, causing
loadLanguage to attempt importing a non-existent zh.json and crash.

Now index.tsx imports detectedLang from i18n.ts so all locale detection
goes through normalizeLanguage — zh/zh-CN/zh-TW all resolve safely.

* feat(i18n): translate knowledge base table headers, status labels, and context menu

- Wire useTranslation into FilesPanel (Sources label, file/files count)
- Pass t() into createKnowledgeBaseColumns for all column headers and menu items
- Convert STATUS_CONFIG labels to i18n keys so Ready/Ingesting/etc. translate
- Upload updated en.json to GP and download machine translations for all 6 locales

* feat(i18n): translate run component tooltip and export modal button

* feat(i18n): wrap hardcoded strings with t() across deployment stepper, flow version sidebar, knowledge base, and account pages

Adds 53 new translation keys to all 7 locale files and wraps previously hardcoded English strings with t() calls in 17 source files: deployment stepper components (connection search, connection panel, flow list panel, step-type, step-review, success content, footer, delete dialog), flow version sidebar (save dialog, snapshot button, sidebar, delete confirm), knowledge base drawer and chunk card, delete account page, and store API key form.

* feat(i18n): translate edit node modal, table modal, and AG Grid strings

Wraps hardcoded strings in editNodeModal (Field Name, Description, Value,
Show, Expose Input, Close), textAreaModal (Finish Editing), intComponent
placeholders, tableModal Save button, ColumnConfig Open Table button, and
AG Grid built-in overlays (No Rows To Show, pagination strings) with t().

* feat(i18n): translate playground chat, file management, user modal, notifications, deployment dialogs, crash page, and toolbar shortcuts

Wraps hardcoded strings with t() across ~50 files and adds ~156 new keys to all 7 locale files (en, de, es, fr, ja, pt, zh-Hans).

Areas covered:
- Playground: sessions sidebar, voice input tooltips/errors, chat input placeholder, drop zone, file attachment tooltip
- File management: context menu (rename/download/duplicate/delete/remove), confirmation modals, CRUD notifications
- User management modal: username, password, confirm password, active, superuser fields
- Notifications panel: title
- MCP server modal: ja.json fix for Streamable HTTP/SSE URL field label and placeholder
- Messages table: "files" column header
- Deployment dialogs: Select Deployment phase, Flows step (available/attached/removed badges, connection panel, flow list panel)
- Crash error page: all text regions including split description around GitHub Issues link
- Toolbar shortcuts: shortcutDisplay renders translated name via shortcuts.name.* keys (keys already existed in all locales)

* feat(i18n): translate node toolbar action tooltips, update bar labels, and confirmation modal strings

* feat(i18n): translate note toolbar, inspection panel, sidebar config trigger, and table pagination strings

* feat(i18n): translate header GitHub/Discord tooltips, playground disabled tooltip, and session options tooltip

* feat(i18n): translate IOModal new chat, edit/copy message, and helpful/not-helpful tooltips

* feat(i18n): translate voice assistant audio settings, labels, tooltips, and API key inputs

* translations of previous entries

* feat(i18n): translate hardcoded strings across error messages, JSX text, and aria/title attributes

Migrated ~55 new keys to en.json and updated ~45 source files: error toasts (streaming,
build failures, file ops, shortcuts), modal titles (session logs, flow details, view text,
restore version, CSV separator, chunk preview), node/output labels (incompatible with, no
output, component output, no tools), model panel (configure provider, not enabled), JSON
editor validation messages, Langflow logo title attributes, and various aria-labels.

* feat(i18n): translate remaining hardcoded strings in alt texts, placeholders, and step titles

- Add useTranslation + translate alt text in ProfileIcon, assistant-empty-state, CanvasControls, assistant-message, assistant-no-models-state
- Translate assistant welcome text and suggestion labels in assistant-empty-state
- Fix dropdownComponent search placeholder to use existing input.searchOptions key
- Fix mcp-server-notice image alt text
- Fix empty-page Langflow logo alt texts (light/dark)
- Add useTranslation + translate file upload step titles in code-tabs via STEP_TITLE_KEYS lookup map
- Add 9 new keys to en.json: sidebar.mcpNoticeImageAlt, common.langflowLogoLight/Dark/userProfileAlt, assistant.welcomeText, assistant.suggestion.*, apiModal.uploadFilesStep/executeFlowStep

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

* feat(i18n): translate remaining hardcoded strings across modals, panels, and components

- codeAreaModal: Done button, Check & Save, Discard Changes, Caution confirmation
- shareModal: Share header, Replace confirmation, status public label, Export button, footer labels, success message, attention text
- mustachePromptModal: Edit Prompt title, Prompt Variables label, Check & Save button
- updateComponentModal: Standard label, backup checkbox, update button, warning paragraphs
- update-phase: Close/Test buttons, sr-only dialog title/description strings
- provider-phase: Cancel/Continue buttons, provider description
- assistant-component-result: Inputs/Outputs headings, Approved/Add to Canvas/View Code actions
- message-options: Edit message, Helpful, Not helpful, Copied, Copy message tooltips
- storeCardComponent: Private, Components, Likes, Downloads, Like, Install Locally, error liking
- ProviderList: Loading providers text
- outputModal: Outputs/Logs tabs, inspect description
- io-field-view: No node found, Enter text placeholders
- InspectionPanelFields: No editable fields, No advanced settings
- InspectionPanelOutputs: No output data message
- FlowVersionSidebar: No saved versions yet
- step-attach-flows-version-panel: Select flow/version, loading/no versions, Created date
- StepReview: No files, generating preview, could not generate, Summary, labels, not selected
- VisibilityToggleButton: Hide field / Show field aria-labels
- ModelSelection: Disable/Enable model aria-labels
- MemoizedSidebarTrigger: Components label
- file-card, file-preview: File label

Adds 63 new keys to en.json (1,500 total).

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

* fix(i18n): add missing useTranslation hook to SidebarTrigger component

SidebarTrigger used t("ui.toggleSidebar") without calling useTranslation(),
causing a ReferenceError crash when the TemplatesModal opened (which renders
a SidebarTrigger with no children prop, hitting the sr-only span).

* feat(i18n): translate visibility tooltips, connect other models, global variables placeholder, and status filter truncation

- Translate 4 visibility toggle tooltips in tableAdvancedToggleCellRender
- Fix "Connect other models" always showing English by using t() directly instead of backend display_name fallback
- Translate Global Variables search placeholder in inputGlobalComponent
- Add SelectTrigger truncation for status filter dropdown in FlowInsightsContent

* feat(i18n): resolve ModelSelection conflict — merge Deprecated badge with translated aria-labels

Combines changes from both branches:
- Keep translated aria-labels for enable/disable model toggles using existing keys
- Add Deprecated badge from release-1.10.0 with t("modelProvider.deprecated")
- Add modelProvider.deprecated key to en.json

* [autofix.ci] apply automated fixes

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

* feat(i18n): fix missing en.json keys and translate hardcoded strings across Knowledge Base, Memory, and component files

Adds 53 new translation keys (knowledge.*, memory.* namespace — new, and misc) and wires up t() calls in 19 source files to eliminate hardcoded English strings in placeholders, labels, modal titles, aria-labels, and empty states.

* chore: remove check-frontend-i18n CI workflow

* chore: remove check_frontend_i18n_keys.py script

* [autofix.ci] apply automated fixes

* chore: revert component_index.json version bumps from release-1.10.0 merge

* chore: sync component_index.json to release-1.10.0

* feat(i18n): update backend locale translations from GP

* fix(i18n): fix frontend jest test failures from i18n migration

- Fix paginator empty state: strip "of " from translation keys and
  add it inline in the non-empty branch so 0 items shows "0 items"
  instead of "of 0 items"
- Revert text renames back to original English copy: "Ingest Content",
  "Add Files", "DB Provider", "1 VERSION", and the full deploy-from-draft
  message so existing tests keep passing without modification
- Fix punctuation mismatches: remove trailing "!" from changesSaved and
  "..." from loadingProviders
- Add i18next plural form resolution (_one/_other) to the jest t() mock
  so pluralised strings resolve correctly in tests
- Default t param in createKnowledgeBaseColumns to en.json fallback
  instead of key passthrough so the function works correctly without
  a translation function argument

* test(frontend): fix deploy version assertion

* fix(i18n): add missing useTranslation import to MemoizedComponents

* translation updated

* [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>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Viktor Avelino <viktor.avelino@gmail.com>
2026-05-20 13:38:53 +00:00
f1ecb51319 fix: UI polish for Memories and Traces sidebar sections (#13205)
* feat(memory): improve memory UI with empty states, toasts, and model filtering

* [autofix.ci] apply automated fixes

* fix: capture memory id/name before setTimeout to prevent stale closure null access

* error improvement and testcase addition

* fix: surface refresh errors, restore API error messages, flatten sidebar ternary, and add keyboard access to batch-size tooltip

* [autofix.ci] apply automated fixes

* biome fix

* fix types

* [autofix.ci] apply automated fixes

* fix: UI polish for Memories and Traces sidebar sections

* [autofix.ci] apply automated fixes

* fix: replace magic index, template literals, and inline noops in Memories/Traces sidebar

* [autofix.ci] apply automated fixes

* ruff fix

* fix testcases

---------

Co-authored-by: Olayinka Adelakun <olayinkaadelakun@mac.war.can.ibm.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Olayinka Adelakun <olayinkaadelakun@Olayinkas-MacBook-Pro.local>
2026-05-19 23:47:16 +00:00
4f9ef2af37 refactor: Move File System component to Files & Knowledge category (#13223)
* change fs category

* component index

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-05-19 23:26:54 +00:00
cb10f1d10f docs: file system component (#13183)
* docs-file-system-component

* delete-internal-feature-doc

* docs-filesystem-component

* fix-links

* Apply suggestions from code review

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

* clarify-metadata

* fix-merge

---------

Co-authored-by: April I. Murphy <36110273+aimurphy@users.noreply.github.com>
2026-05-19 18:46:30 +00:00
bd54c1b952 feat(memory): improve memory UI with empty states, toasts, and model (#13116)
* feat(memory): improve memory UI with empty states, toasts, and model filtering

* [autofix.ci] apply automated fixes

* fix: capture memory id/name before setTimeout to prevent stale closure null access

* error improvement and testcase addition

* fix: surface refresh errors, restore API error messages, flatten sidebar ternary, and add keyboard access to batch-size tooltip

* [autofix.ci] apply automated fixes

* biome fix

* fix types

* [autofix.ci] apply automated fixes

---------

Co-authored-by: Olayinka Adelakun <olayinkaadelakun@mac.war.can.ibm.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Olayinka Adelakun <olayinkaadelakun@Olayinkas-MacBook-Pro.local>
2026-05-19 18:33:51 +00:00
bc656b44e8 fix(openrouter): validate against /api/v1/auth/key and disconnect all variables (#13214)
Two follow-ups to PR #13185 surfaced during release-1.10.0 QA.

BUG-1 (high) — OpenRouter API key validation never rejected invalid keys.
``validate_model_provider_key`` probed OpenRouter's ``/api/v1/models``
endpoint, which is *public* and returns 200 for any bearer token (including
missing/invalid). So ``POST /api/v1/variables/`` accepted any string as an
OPENROUTER_API_KEY with 201, the catalog swapped to the live 356-model list,
and runs failed at request time. Route the probe through
``/api/v1/auth/key`` instead — auth-required, returns 401 on invalid, the
existing ValueError → HTTP 400 mapping in ``api/v1/variable.py`` does the
rest. Same fix lifts BUG-3 since the generalized validation path is shared.

BUG-2 (medium) — clicking Disconnect on OpenRouter in Settings → Model
Providers did nothing. ``handleDisconnect`` resolved the variable name via
the static ``PROVIDER_VARIABLE_MAPPING`` constant, which doesn't include
"OpenRouter" (and the constant is deprecated in favour of the dynamic
``GET /api/v1/models/provider-variable-mapping`` API). Source the variable
keys from ``providerVariables`` instead and delete every configured one in
parallel so multi-variable providers (OpenRouter's API key + attribution
headers, IBM WatsonX's apikey + project_id + url) are fully removed. The
static mapping is kept as a fallback for the brief window before the
provider-variable API resolves.

Tests
- ``test_openrouter_provider.py``: update happy/regression cases to pin the
  new ``/api/v1/auth/key`` URL and add an explicit assertion that the public
  ``/api/v1/models`` endpoint is never used for validation.
- ``test_variable.py``: add ``POST /api/v1/variables/`` integration tests
  covering both a 401 (returns 400 with "Invalid OpenRouter API key") and a
  200 (returns 201), and assert the auth-required URL is used.
- ``useProviderConfigurationDisconnect.test.tsx``: new suite — disconnect
  for OpenRouter deletes all three variables in one batch, fall-back to the
  static map for Anthropic still works, no-ops when nothing is configured,
  surfaces an error toast when a delete fails.

Fixes the issues reported against release-1.10.0 OSS QA of PR #13185.
2026-05-19 12:27:12 -07:00
09bd68d349 docs: text operations component (#13152)
docs-add-text-operations-component
2026-05-19 18:25:50 +00:00
5a8c17b22d ci: backport bundle/nightly CI fixes from main (#13206, #13207, #13208) (#13210)
* ci: stop publishing -nightly bundle variants; release bundles on demand (#13206)

Bundles (lfx-arxiv, lfx-duckduckgo) change infrequently enough that a
nightly cadence is overkill. Drop the rename-to-`-nightly` and bundle
publish lanes from the nightly pipeline and add a dedicated
workflow_dispatch-only release_bundles.yml for purposeful releases.

- nightly_build.yml: drop update_bundle_versions.py invocation and the
  src/bundles/*/pyproject.toml git-add guard.
- release_nightly.yml: remove build-nightly-bundles and
  publish-nightly-bundles jobs; untether test-cross-platform and
  publish-nightly-main from them.
- release_bundles.yml (new): build all src/bundles/* wheels, run a
  cross-OS install smoke test, then publish to PyPI under their stable
  names. Tolerates already-published versions so re-runs without a
  version bump are no-ops.

release.yml still owns bundle publishing as part of main releases.

Manual follow-up (PyPI admin): delete the lfx-arxiv-nightly and
lfx-duckduckgo-nightly projects from PyPI.

* ci(release_bundles): build lfx wheel locally for smoke test (#13207)

The release_bundles.yml smoke test installs bundle wheels into a fresh
venv, which makes uv resolve their `lfx>=X.Y,<Z` pin against PyPI. When
bundles are released ahead of a matching lfx (typical case — bundles
ship more often than lfx bumps land on PyPI), the resolve fails:

  Because only lfx<=0.4.3 is available and lfx-arxiv==0.1.0 depends on
  lfx>=0.5.0,<0.6.0, we can conclude that lfx-arxiv==0.1.0 cannot be
  used.

Build the lfx wheel from the current ref in the build-bundles job and
install it alongside the bundle wheels in the smoke test. The lfx
wheel ships as a separate `dist-lfx-smoketest` artifact and is NOT
included in the publish-bundles step — only the `dist-bundles`
artifact gets pushed to PyPI.

* ci(nightly): re-pin bundle lfx deps to lfx-nightly during nightly build (#13208)

The nightly pipeline renames the workspace `lfx` package to `lfx-nightly`
in `src/lfx/pyproject.toml` and the root workspace dep, but leaves each
`src/bundles/*/pyproject.toml`'s `"lfx>=X.Y,<Z"` pin unchanged. With the
workspace `lfx` gone and PyPI still on lfx 0.4.3, the create-nightly-tag
job's `uv lock` fails:

  × No solution found when resolving dependencies for split [...]
  ╰─▶ Because only lfx<=0.4.3 is available and lfx-arxiv depends on
      lfx>=0.5.0,<0.6.0, we can conclude that lfx-arxiv's requirements
      are unsatisfiable.

`update_lfx_version.py` now also rewrites the `lfx` (or already-rewritten
`lfx-nightly`) specifier in every `src/bundles/*/pyproject.toml` to
`lfx-nightly==<dev version>`, mirroring how `update_lf_base_dependency`
re-pins the lfx dep inside `langflow-base`. The lookahead in the regex
prevents matching sibling packages like `lfx-arxiv` / `lfx-duckduckgo`.

No-op when no bundle pyprojects exist (e.g. on main today), so this is
safe to merge ahead of the bundle extraction landing on main. Restored
the guarded `git add src/bundles/*/pyproject.toml` step so the modified
bundle pyprojects ride the same commit/tag as the rest of the nightly
version bumps.

Cherry-pick to release-1.10.0 to unblock nightlies cut from that branch.
2026-05-19 10:12:54 -07:00
500b5e9af9 ci(nightly): re-pin bundle lfx deps to lfx-nightly during nightly build (#13208)
The nightly pipeline renames the workspace `lfx` package to `lfx-nightly`
in `src/lfx/pyproject.toml` and the root workspace dep, but leaves each
`src/bundles/*/pyproject.toml`'s `"lfx>=X.Y,<Z"` pin unchanged. With the
workspace `lfx` gone and PyPI still on lfx 0.4.3, the create-nightly-tag
job's `uv lock` fails:

  × No solution found when resolving dependencies for split [...]
  ╰─▶ Because only lfx<=0.4.3 is available and lfx-arxiv depends on
      lfx>=0.5.0,<0.6.0, we can conclude that lfx-arxiv's requirements
      are unsatisfiable.

`update_lfx_version.py` now also rewrites the `lfx` (or already-rewritten
`lfx-nightly`) specifier in every `src/bundles/*/pyproject.toml` to
`lfx-nightly==<dev version>`, mirroring how `update_lf_base_dependency`
re-pins the lfx dep inside `langflow-base`. The lookahead in the regex
prevents matching sibling packages like `lfx-arxiv` / `lfx-duckduckgo`.

No-op when no bundle pyprojects exist (e.g. on main today), so this is
safe to merge ahead of the bundle extraction landing on main. Restored
the guarded `git add src/bundles/*/pyproject.toml` step so the modified
bundle pyprojects ride the same commit/tag as the rest of the nightly
version bumps.

Cherry-pick to release-1.10.0 to unblock nightlies cut from that branch.
2026-05-19 09:55:09 -07:00
d5e5e46176 ci(release_bundles): build lfx wheel locally for smoke test (#13207)
The release_bundles.yml smoke test installs bundle wheels into a fresh
venv, which makes uv resolve their `lfx>=X.Y,<Z` pin against PyPI. When
bundles are released ahead of a matching lfx (typical case — bundles
ship more often than lfx bumps land on PyPI), the resolve fails:

  Because only lfx<=0.4.3 is available and lfx-arxiv==0.1.0 depends on
  lfx>=0.5.0,<0.6.0, we can conclude that lfx-arxiv==0.1.0 cannot be
  used.

Build the lfx wheel from the current ref in the build-bundles job and
install it alongside the bundle wheels in the smoke test. The lfx
wheel ships as a separate `dist-lfx-smoketest` artifact and is NOT
included in the publish-bundles step — only the `dist-bundles`
artifact gets pushed to PyPI.
2026-05-19 09:54:56 -07:00
feec57e16f docs: merge knowledge base components (#13150)
* docs-merge-kb-components

* Apply suggestions from code review

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

* use-tabs-for-params-table

---------

Co-authored-by: April I. Murphy <36110273+aimurphy@users.noreply.github.com>
2026-05-19 16:23:22 +00:00
baceda7cc1 ci: stop publishing -nightly bundle variants; release bundles on demand (#13206)
Bundles (lfx-arxiv, lfx-duckduckgo) change infrequently enough that a
nightly cadence is overkill. Drop the rename-to-`-nightly` and bundle
publish lanes from the nightly pipeline and add a dedicated
workflow_dispatch-only release_bundles.yml for purposeful releases.

- nightly_build.yml: drop update_bundle_versions.py invocation and the
  src/bundles/*/pyproject.toml git-add guard.
- release_nightly.yml: remove build-nightly-bundles and
  publish-nightly-bundles jobs; untether test-cross-platform and
  publish-nightly-main from them.
- release_bundles.yml (new): build all src/bundles/* wheels, run a
  cross-OS install smoke test, then publish to PyPI under their stable
  names. Tolerates already-published versions so re-runs without a
  version bump are no-ops.

release.yml still owns bundle publishing as part of main releases.

Manual follow-up (PyPI admin): delete the lfx-arxiv-nightly and
lfx-duckduckgo-nightly projects from PyPI.
2026-05-19 09:22:59 -07:00
b9e6fff677 feat: Redesign deployment environment cards (#13109)
* feat(deployments): redesign env cards

* refactor(deployments): split env ui

* test(e2e): fix provider delete selectors
2026-05-19 16:13:26 +00:00
aefd8acb37 docs: directory component is legacy (#13186)
* remove-and-redirect-component-page

* update-tutorials-that-used-directory-component

* peer-review

* update-tutorial-screenshot
2026-05-19 15:33:50 +00:00
9882f9f381 chore(ci): timeout bump + harden langflow grep + build/publish src/bundles/* wheels (#13199)
* chore: bump backend unit test timeout from 30 to 40 minutes

* ci: anchor langflow version grep so new workspace packages can't break it

The pattern `uv tree | grep 'langflow' | grep -v 'langflow-base' | grep -v 'langflow-sdk'`
was used in 8 places to pluck the root `langflow` (or `langflow-nightly`)
line out of `uv tree`. It relied on enumerating every other workspace
package that starts with `langflow-`. As soon as a new one was added
(`langflow-stepflow`, via #12015) the filter let two lines through and
the downstream `awk` / `cut` produced a multi-line string, e.g.:

    Name langflow-stepflow
    langflow-nightly does not match langflow-nightly. Exiting the workflow.

Replace the enumeration with an anchored regex
`grep -E '^langflow(-nightly)?[[:space:]]'` so only the exact `langflow`
or `langflow-nightly` root line matches, regardless of how many other
`langflow-*` workspace packages exist now or in the future.

* ci: build, test, and publish src/bundles/* wheels

Langflow main pins exact versions of every bundle (e.g.
`lfx-arxiv-nightly==0.1.0.dev38`, `lfx-arxiv==0.1.0`), but neither the
nightly workflow nor the release workflow built or published those
bundle wheels. So when cross-platform tests (or downstream consumers)
ran `pip install langflow-nightly`, the resolver couldn't find
`lfx-arxiv-nightly` / `lfx-duckduckgo-nightly` on PyPI and bailed:

    Because lfx-arxiv-nightly was not found in the package registry and
    langflow-nightly==1.10.0.dev38 depends on lfx-arxiv-nightly==0.1.0.dev38,
    we can conclude that langflow-nightly==1.10.0.dev38 cannot be used.

Add a build → test → publish lane for every package under
`src/bundles/*/` in both workflows, plus a matching bundle install step
in cross-platform-test.yml.

`release_nightly.yml`
- `build-nightly-bundles`: iterates `src/bundles/*/pyproject.toml`,
  builds each wheel, uploads as `dist-nightly-bundles`.
- `test-cross-platform` now passes `bundles-artifact-name` so the
  cross-platform test installs bundles before installing main.
- `publish-nightly-bundles`: publishes each bundle wheel to PyPI after
  cross-platform passes and lfx is already published.
- `publish-nightly-main` now waits on `publish-nightly-bundles` so main
  reaches PyPI only after its bundle deps do.

`release.yml`
- New `release_bundles` input (false by default).
- `validate-dependencies` rejects `release_package_main=true` without
  `release_bundles=true`, mirroring the existing lfx/sdk gate.
- `build-bundles`, `publish-bundles` mirror the nightly jobs.
- `publish-main` now needs `publish-bundles`.

`cross-platform-test.yml`
- New optional `bundles-artifact-name` input; download step is gated on
  it being set.
- `build-if-needed` (workflow_dispatch path) also builds bundles and
  publishes the artifact, so adhoc runs stay self-contained.
- Each of the four "Install main package from wheel" steps gets a
  preceding "Install bundle packages from wheel" step that runs only
  when the artifact is present, leaving non-bundle callers
  (e.g. pre-bundle release branches) unaffected.

* ci: drop mapfile from bundle install (macOS Bash 3.2 compat)

`mapfile` is a Bash 4+ builtin and the macOS runners use the system
Bash (3.2). The "Install bundle packages from wheel (Unix)" step
failed on `Install & Run - macos amd64 3.12` with:

    mapfile: command not found
    find: stdout: Broken pipe

Replace with the `shopt -s nullglob` + glob-array pattern already used
in the bundle build/publish steps, which works on Bash 3.2. All
artifact wheels live at the top level of ./bundles-dist, so the simple
glob is equivalent to the previous `find` (which also wasn't recursing
anywhere useful).

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-19 08:11:53 -07:00
487a2402ba fix: Model handling for tool calling in agents and update IBM models (#13201)
fix: Model handling for tool calling in agents and update IBM models (#13191)

* Model handling for tool calling in agents and update IBM models

* [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-05-19 08:02:35 -07:00
ae45d3ad8e fix: File upload toast showing only after successful upload (#12984)
* File upload toast showing only ater successful upload

* [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>
2026-05-19 12:21:07 +00:00
e1d79bbaed fix(ci): gate nightly release on successful tag creation (#13198) 2026-05-19 05:14:10 -07:00
2be85b167e feat: promote OpenRouter to a first-class unified model provider (#13185)
* feat: promote OpenRouter to a first-class unified model provider

Adds OpenRouter to the unified Model Providers system so users can
configure it once in Settings rather than per-flow. Reuses ChatOpenAI
with base_url=https://openrouter.ai/api/v1, supports a live model
catalog (fetched from /api/v1/models with the user's bearer key), and
forwards the optional HTTP-Referer / X-Title attribution headers when
the user provides a site URL or app name.

Validation is a cheap GET against /api/v1/models that raises
ValueError("Invalid OpenRouter API key") on a 401. The existing
flow-level OpenRouterComponent is left untouched for backward
compatibility.

Implements the opinionated direction proposed in #12839 (single
curated provider instead of a generic OpenAI-compatible field).

* Update Structured Data Analysis Agent.json

* [autofix.ci] apply automated fixes

* fix(openrouter): address review feedback

- Per-model tool_calling derived from OpenRouter's `supported_parameters`,
  not blanket-True. The Agent component calls
  `get_language_model_options(tool_calling=True)` so this fixes a real bug
  where non-tool-calling models (e.g. perceptron/perceptron-mk1) would have
  appeared in agent flows and failed at runtime.
- Default-model picks intersect with the curated seed list instead of
  taking the first 5 alphabetical ids (which were ai21/jamba-large-1.7,
  aion-labs/aion-1.0, etc.). Falls back to first MIN_DEFAULT_MODELS when
  the seed list shares nothing with the live catalog (stale-seed safety).
- Broadened the live-fetch except to include ValueError/TypeError (covers
  malformed JSON, non-list `data`, non-dict entries) and added defensive
  isinstance checks so a 200 with a bad payload degrades to `[]` instead
  of crashing on AttributeError.
- Bumped the live-fetch failure log from `debug` to `warning` with URL +
  status code, so an empty live catalog leaves a server-side breadcrumb.
- Wrapped the credentials probe in try/except → ValueError so a 5xx /
  timeout during `POST /validate-provider` surfaces as a user-facing 400
  via api/v1/variable.py (which only catches ValueError), not an
  unhandled 500.
- Tightened `os.environ.get(var["variable_key"])` (was
  `var.get("variable_key", "")` → `os.environ.get("")` masking misconfig).
- Documented `is_header` / `header_name` and the settings-only
  (no-`component_metadata`) pattern in the MODEL_PROVIDER_METADATA header
  comment.
- Updated seed slugs against the current OpenRouter catalog
  (anthropic/claude-3.7-sonnet was missing); curated list is now
  Claude Opus/Sonnet/Haiku 4.x + GPT-4o pair + Gemini 2.5 Pro + Llama 3.3.
- Added OPENROUTER_API_KEY / OPENROUTER_SITE_URL / OPENROUTER_APP_NAME to
  VARIABLES_TO_GET_FROM_ENVIRONMENT so users with env vars set see the
  provider as configured in Settings → Model Providers (parity with the
  other providers, and makes the "Falls back to env" copy honest).
- Tests: pinned `MIN_DEFAULT_MODELS` instead of magic 3; added cases for
  per-model tool_calling derivation, seed-intersection defaults,
  stale-seed fallback, httpx.HTTPStatusError, malformed payload, transient
  network → ValueError validation, env-var attribution headers, and env
  auto-import list. Replaced `patch.dict("sys.modules", ...)` with
  `patch.object(requests, "get", ...)` so the test stays correct if the
  lazy import is hoisted.

* Update Structured Data Analysis Agent.json

* feat(model-providers): search bar, models.dev metadata override, capability tags

Three follow-ups to the OpenRouter PR, requested by @ogabrielluiz in Slack:

1. **Search bars in the model providers modal.** ProviderList accepts a
   `query` prop and filters case-insensitively on the provider name; an
   `<Input icon="Search">` in ModelProvidersContent drives it. Inside the
   per-provider panel, ModelSelection grows its own search input that
   filters both LLM and embedding lists and resets when the selected
   provider changes (so OpenRouter → Anthropic doesn't carry stale text).
   Empty-state copy in both lanes; new i18n keys.

2. **models.dev metadata override (source of truth for covered providers).**
   New `lfx.base.models.models_dev_catalog` module:
     * `fetch_models_dev_snapshot()` — httpx GET against
       https://models.dev/api.json, returns None on transport / status /
       payload errors with a warning log.
     * `load_models_dev_snapshot()` / `save_models_dev_snapshot()` — disk
       cache at `<user_cache_dir>/langflow/models_dev/snapshot.json` with
       atomic temp-file write.
     * `apply_models_dev_overrides()` — translates each covered provider's
       models.dev entries into `ModelMetadata` (tool_call → tool_calling,
       reasoning passthrough, modalities.input has "image" → vision,
       limit.context → context_window, cost.input/output →
       cost_per_million_in/out). Providers not covered (IBM WatsonX, Azure
       OpenAI, Groq) pass through unchanged. Live-fetched providers
       (Ollama, IBM WatsonX, OpenRouter) keep their behavior because
       `replace_with_live_models` runs downstream at read time.
   `provider_queries.get_models_detailed()` now consults the in-memory
   snapshot via `get_active_snapshot()` and is `cache_clear()`-friendly so
   snapshot installs are picked up on the next call. The non-blocking
   refresh task in `get_lifespan` hydrates from disk first, then attempts a
   live fetch on a 24h interval; startup never blocks on models.dev.

3. **Capability tags next to the model name.** ModelRow renders small
   Badge pills (tool / reasoning / vision / embedding / search / preview)
   driven by metadata flags, in a stable left-to-right order, with the
   existing Deprecated badge kept at the end. The embedding tag only
   surfaces in the `all` view because the dedicated embeddings section
   header already conveys the same info.

Tests:
  - 14 new backend cases in `test_models_dev_catalog.py` (fetch / load /
    save / override / cache invalidation paths).
  - Search + capability-tag cases added to ProviderList and ModelSelection
    Jest suites (44 total, all green).
  - lfx tests/unit/base/models + tests/unit/inputs: 114 pass, no
    regressions.
  - ruff check clean on all touched files.

Out of scope: manual UI refresh button, surfacing pricing in the UI,
auto-adding providers from models.dev without a LangChain client.

* fix(openrouter): derive reasoning from supported_parameters

OpenRouter exposes "reasoning" in each model's supported_parameters array
exactly like it does "tools" (verified against /api/v1/models — Claude
Opus 4.x, o1/o3, DeepSeek R1 all carry it). The live fetcher was only
deriving tool_calling; reasoning was left at the create_model_metadata
default of False, so the new reasoning badge never lit up for OpenRouter
models even though the rest of the wiring was in place.

Now derives both flags from the same array. Test renamed to
…_derives_tool_calling_and_reasoning_per_model and pins all four
combinations (tools+reasoning, tools only, reasoning only, neither, plus
the missing-supported_parameters case).

* feat(model-providers): sort by recency, hide deprecated by default

OpenAI's list looked jumbled because the catalog rendered in raw
constants-file order — reasoning models interleaved with chat models,
deprecated and preview rows scattered throughout. Three changes:

1. **Thread `created` (Unix epoch) through ModelMetadata.** New optional
   field; populated by:
     - OpenRouter live fetch (existing `created` field on every model;
       defensive int-coercion handles bad payload values).
     - models.dev override (parses `release_date` YYYY-MM-DD → UTC
       midnight epoch; invalid / missing → 0).
   Static `*_constants.py` lists leave it at 0 → "unknown" tier.

2. **Sort each provider's group in `get_unified_models_detailed`.**
   Stable sort by `(is_deprecated, -created_epoch)`:
     - Non-deprecated dated rows: newest first.
     - Untimed rows (current static fallback): stable sort keeps their
       hand-curated constants-file order intact within the same tier.
     - Deprecated rows: bottom.
   The "first 5 = default" stamping now runs AFTER the sort so the
   highlighted defaults reflect the new ordering. For OpenAI's static
   list this changes nothing today (constants are already newest-first);
   once models.dev hydrates it picks up genuinely-recent dates from
   `release_date`.

3. **Collapse deprecated into a `<details>` disclosure.** Each section
   renders active rows normally, then a `Show N deprecated model(s)`
   summary that's closed by default. Resets on provider change. The rows
   stay in the DOM so existing flows referencing deprecated models still
   resolve; they're just out of the way.

`not_supported` is already filtered at the API layer (the modal doesn't
pass `includeUnsupported`), so those rows remain hidden as before.

Tests:
  - Backend: `created` propagation from `release_date` (+ malformed/empty
    cases), OpenRouter `created` passthrough (+ invalid-value
    degradation), and a full sort-order assertion covering
    [newest, mid, undated-a, undated-b, deprecated] ordering.
  - Frontend: deprecated disclosure default-closed, singular pluralization,
    no-disclosure-when-nothing-deprecated, reset on provider change via
    real user click → onToggle (the right pattern; the prior draft of
    this test mutated `details.open` directly and missed React's prop
    reconciliation).
  - 117 lfx tests + 48 frontend tests pass; ruff clean.

* [autofix.ci] apply automated fixes

* fix(models.dev override): preserve static deprecated flag + auto-deprecate dated snapshots

Two issues surfaced in the OpenAI/Anthropic screenshots:

1. **gpt-3.5-turbo wasn't in the deprecated disclosure** even though
   openai_constants.py has it flagged ``deprecated=True``. models.dev
   exposes no deprecated/lifecycle field (verified: only
   ``release_date`` and ``last_updated``), so the override threw away
   our static curation. ``apply_models_dev_overrides`` now builds a
   provider→{deprecated model names} lookup from the static lists and
   forwards the flag through ``_translate_model_entry`` for any name
   that matched a static deprecated row. ``gpt-4.5-preview`` and
   ``gpt-4-turbo-preview`` ride the same path.

2. **Anthropic dated-snapshot ids** (``claude-opus-4-5-20251101``,
   ``claude-haiku-4-5-20251001``, etc.) crowded the picker alongside
   their moving aliases. These are pinned-date snapshots of the
   alias — useful for production pins, noisy as defaults. Same
   pattern exists for OpenAI (``gpt-4o-2024-05-13``).
   ``_translate_model_entry`` now matches ``-YYYYMMDD$`` (Anthropic)
   and ``-YYYY-MM-DD$`` (OpenAI) and auto-flags those rows deprecated
   so they collapse into the disclosure tier rather than dominating
   the list. The 4-digit-only ``-0314``-style suffixes
   (``gpt-4-0314``, ``gpt-4-0613``) are deliberately *not* matched —
   those are pre-dated-naming snapshots that need static curation if
   we want to mark them.

Tests:
  - test_apply_overrides_marks_dated_snapshot_ids_deprecated pins:
      * alias rows stay active (claude-opus-4-5, gpt-4o)
      * Anthropic 8-digit and OpenAI hyphenated suffixes flip to
        deprecated
      * 4-digit-only suffixes don't false-positive
  - test_apply_overrides_preserves_static_deprecated_flag pins:
      * gpt-3.5-turbo / gpt-4.5-preview keep their static deprecation
      * a new model not in the static list (gpt-6) stays active

* fix(models.dev override): classify embeddings + age-based auto-deprecation

Two follow-ups from screenshot review on the OpenAI provider list.

**Bug 1 — embedding models showed in the Language Models section.**
models.dev mixes embeddings into a provider's ``models`` dict (e.g.
``text-embedding-3-large`` lives in the OpenAI block). The translator
was defaulting every entry to ``model_type="llm"``, so embeddings
polluted the LLM lane in the modal. The clean discriminator is the
``family`` field (``"text-embedding"`` for OpenAI's current embeddings,
verified for ada-002 / 3-small / 3-large); a name-substring check
("embedding") covers providers that fill ``family`` inconsistently.

**Bug 2 — old OpenAI models (gpt-4, gpt-4-turbo, text-embedding-ada-002)
stayed active even though they're functionally legacy.** models.dev has
no deprecation field, and OpenAI doesn't formally deprecate gpt-4 /
gpt-4-turbo in their catalog, but at ~924 days from release they're
clearly out of rotation. New ``_is_aged_out`` helper auto-deprecates
models older than ~30 months (900 days), preferring ``release_date``
over ``last_updated`` (the latter tracks catalog-curator edits like
typo fixes, not new model versions — gpt-4 has last_updated=2024-04
which would falsely keep it active).

Threshold tuned conservatively against today's catalog:
  - 1250d ada-002 → deprecated
  - 924d gpt-4, gpt-4-turbo → deprecated
  - 844d text-embedding-3-large/small → ACTIVE (still OpenAI's current
    embeddings; threshold deliberately above this point)
  - 735d gpt-4o → active
  - 261d Claude 4.x → active

``now`` is injected through ``_translate_model_entry`` and
``apply_models_dev_overrides`` for testability so the unit tests don't
depend on wall-clock time.

Tests:
  - test_apply_overrides_marks_embedding_family_as_embeddings_model_type
    pins ``family="text-embedding"`` → embeddings, generic LLMs stay
    llm, and a name-substring case (``voyage-3-embedding``) still
    classifies correctly when family is missing.
  - test_apply_overrides_auto_deprecates_stale_models pins both sides
    of the 900-day line and the no-date case (insufficient signal →
    stays active).

* fix(models.dev override): drop duplicate static groups + Google dated-preview + gemini-1.5

Three issues from screenshot review.

**1. Duplicate embedding rows in the OpenAI section.** The override
iterates ``static_lists`` and replaces the first OpenAI group
(``OPENAI_MODELS_DETAILED``, LLMs) with the combined override (which
holds both LLMs and embeddings from the single models.dev block). The
second OpenAI group (``OPENAI_EMBEDDING_MODELS_DETAILED``) then fell
through to the else branch and got appended unchanged, so every
embedding row rendered twice. Fix: when a provider's override has
already been applied, drop any subsequent static groups for the same
provider — they would only re-introduce duplicates.

**2. Google's dated-preview suffix wasn't auto-deprecated.** Each
vendor uses a different shape for snapshot ids:

| Vendor    | Shape                | Example                                   |
| --------- | -------------------- | ----------------------------------------- |
| Anthropic | ``-YYYYMMDD``        | ``claude-opus-4-5-20251101``              |
| OpenAI    | ``-YYYY-MM-DD``      | ``gpt-4o-2024-05-13``                     |
| Google    | ``-preview-MM-DD``   | ``gemini-2.5-pro-preview-05-06``          |
| Google    | ``-preview-MM-YYYY`` | ``gemini-2.5-flash-preview-09-2025``      |

``_DATED_SNAPSHOT_RE`` extended to match the Google variants. The
non-dated ``-preview-tts``-style suffix stays active (tts is a real
variant, not a snapshot).

**3. gemini-1.5-* stayed active despite being long out of rotation.**
At ~600-820 days these names sit under the 900-day age threshold, so
the heuristic alone wouldn't catch them. Added explicit
``deprecated=True`` entries for ``gemini-1.5-pro`` /
``gemini-1.5-flash`` / ``gemini-1.5-flash-8b`` to
``google_generative_ai_constants``; the override's static-deprecation
preservation flows them through.

Tests:
  - test_apply_overrides_drops_subsequent_static_groups_for_overridden_provider
    pins the no-duplicates invariant.
  - test_apply_overrides_marks_dated_snapshot_ids_deprecated grows to
    cover the Google preview-MM-DD / preview-MM-YYYY patterns and the
    non-dated -preview-tts negative case.
  - test_apply_overrides_preserves_gemini_1_5_static_deprecation pins
    that the static curation actually reaches the override output
    (and includes a sanity assert on the static list itself, so we
    don't drift the test off of the constants file by accident).

57 lfx model tests pass; ruff clean.

* [autofix.ci] apply automated fixes

* fix(ollama): stop 10s frontend poll + parallelize /api/show + cache

Closes #13137.

Two layered fixes for the runaway Ollama model fetch:

**Frontend — drop the 10-second refetchInterval.** The previous code
polled ``/api/v1/models`` every 10s as long as the Ollama provider card
was selected. Each backend call fanned out to ``GET /api/tags`` + ``POST
/api/show`` per model serially, so with many local models the request
overran the 10s interval and requests accumulated. The catalog already
refreshes on credential save / disconnect via
``invalidateProviderQueries`` and the backend cache (below) keeps a
on-demand window-focus refresh cheap; the timer was unnecessary.

**Backend — parallelize ``/api/show`` and cache.**

* ``get_ollama_models`` now fans out per-model capability probes via
  ``asyncio.gather``, so total latency is bounded by the slowest single
  request rather than ``N * avg``. Per-model failures are absorbed at
  debug-level — one broken model no longer poisons the whole catalog
  response.
* In-process TTL cache keyed by ``(base_url, capability)`` with a 30s
  window in front of the fan-out. Overlapping ``/api/v1/models``
  requests (which happen on every modal interaction, agent picker,
  embed picker, etc.) now share one upstream round-trip. Test-only
  ``_ollama_cache_clear`` helper for isolation.

Tests:
  - test_get_ollama_models_returns_only_matching_capability — capability
    filter still works.
  - test_get_ollama_models_runs_show_requests_in_parallel — pins
    parallelism via an asyncio.Event that would deadlock under serial.
  - test_get_ollama_models_tolerates_single_show_failure — one bad model
    doesn't drop the whole list.
  - test_get_ollama_models_caches_result_for_ttl_window — second call
    within the TTL hits the cache (zero upstream calls).
  - test_get_ollama_models_cache_keyed_by_base_url_and_capability —
    different base URLs and capability filters stay isolated.
  - test_get_ollama_models_refetches_after_ttl_expires — TTL expiry
    triggers a fresh fetch.

* Update Structured Data Analysis Agent.json

* [autofix.ci] apply automated fixes

* feat(model-input): declarative filters propagate to picker + sticky-default

Agent's Language Model dropdown was still showing tool-incompatible
models like ``gemini-3.1-flash-image-preview`` (``tool_call=false``
per models.dev). Root cause: the tool-calling constraint lived as a
hardcoded lambda at the Agent component's call site, but
``update_model_options_in_build_config``'s sticky-default
re-injection path didn't know about it — so a previously-saved
selection that no longer passed the filter got re-injected with
``not_enabled_locally=True`` and kept appearing in the picker.

Make the filter declarative so the helper, the cache key, and the
sticky-default path all read it from the same source.

* ``ModelInputMixin`` gains a ``filters: dict[str, Any] | None`` field.
  Any keys/values forward to ``get_unified_models_detailed`` and are
  matched against each model's metadata. Example: the Agent declares
  ``ModelInput(name="model", filters={"tool_calling": True})``
  instead of passing a lambda at the call site.

* ``get_language_model_options`` accepts ``filters: dict | None``;
  the legacy ``tool_calling`` kwarg is preserved and merged into the
  filter dict for backward compatibility.

* ``handle_model_input_update`` reads ``filters`` from the
  build_config and, when no explicit ``get_options_func`` is supplied,
  builds one that applies them. The options-cache key prefix is
  namespaced by sorted filter pairs so different constraint sets
  don't share cached options.

* The sticky-default block in ``update_model_options_in_build_config``
  validates the saved selection against the same filters dict via
  ``_saved_model_passes_filters``. When the saved row doesn't satisfy
  every constraint, the row is dropped and the value cleared so the
  downstream auto-default picks a compatible model. Conservative
  fallback: an unknown model (not in the catalog) still passes the
  filter so today's "inject and let the user configure" UX is
  unchanged for genuinely-unknown selections.

* Agent component: drop the inline lambda in ``update_build_config``;
  the filter now lives on the ModelInput declaration.

* Replaces the ``cache_key_prefix`` sniff for tool-calling intent —
  prefix is now derived from filters, not pattern-matched.

Tests cover the helpers, both sticky-default branches (drop vs
inject), and the conservative fallback for unknown saved models.

* [autofix.ci] apply automated fixes

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

* fix(google): tool_calling=False on image-output Gemini models

Even after the declarative filters={"tool_calling": True} shipped on
the Agent component's ModelInput, the picker still showed
gemini-3.1-flash-image-preview and friends. Root cause: the static
google_generative_ai_constants.py catalog defaulted every Gemini
model to tool_calling=True. Image-generation models can't actually
run with tools (verified in models.dev — these rows have
tool_call: false and modalities.output includes image), so the
static-only fallback (before the models.dev refresh hydrates) was
wrong upstream of the filter and leaked them through.

Four image-output models flipped to tool_calling=False in the static
catalog:
  * gemini-2.5-flash-image
  * gemini-2.0-flash-preview-image-generation
  * gemini-3-pro-image-preview
  * gemini-3.1-flash-image-preview

Models.dev's correct flag still wins via the override path once the
snapshot hydrates; this fix matters most for cold starts and
air-gapped deployments where only the bundled static lists are in
play.

Tests (test_static_catalog_tool_calling.py):
  - Parametrized over the four known image models; each must declare
    tool_calling=False in the static catalog (and remain listed — we
    need the static curation so the override preserves the flag by
    name match).
  - Heuristic regression guard: any Google model with "image" in its
    name that defaults to tool_calling=True will fail this test.
    Future image variants are caught before they reach the picker;
    legitimate exceptions opt in via an allowlist literal in the test.

* [autofix.ci] apply automated fixes

* fix(model-input): source filters from class declaration, not round-tripped build_config

The Agent picker was STILL showing gemini-3.1-flash-image-preview even
after the static catalog flag flip and the declarative filters work.
Reproduced and traced to a saved-flow round-trip: flows persisted
before the filters field shipped don't carry it in their stored
template. The frontend POSTs that stale template back, my helper read
build_config["model"]["filters"] (None), and the filter path
short-circuited to the unfiltered branch.

Fix: source filters from the ModelInput's class-level declaration
(canonical, always current with the server build), falling back to
build_config only when the component happens to expose no inputs
attribute. The build_config is also patched in place when the
declaration overrides it, so the next round-trip carries the active
filter back to the frontend.

* New helper ``_filters_from_component_inputs`` walks the component's
  inputs list and returns the matching ModelInput's filters dict (with
  None values dropped, same hygiene as the build_config path).
* New ``_resolve_filters`` orchestrates: class declaration > build_config,
  with an in-place write back to build_config when the class wins.
* Both call sites in ``handle_model_input_update`` and
  ``update_model_options_in_build_config`` now call ``_resolve_filters``;
  the old direct call to ``_filters_from_build_config`` is gone from the
  hot paths (kept exported because it's still useful as a defensive
  fallback when no component is available).

Tests (4 new in test_build_config_sticky_default.py):
  - _filters_from_component_inputs reads class declaration / returns
    empty when no matching input.
  - _resolve_filters prefers class over build_config and patches
    build_config in place.
  - _resolve_filters falls back to build_config when component has no
    inputs attribute (defensive fallback for non-standard callers).

* fix(frontend): honor ModelInput.filters in dropdown augment loop

The Agent picker kept showing tool-incompatible models like
gemini-3.1-flash-image-preview even after every backend filter and
static-catalog fix landed. Root cause was on the frontend, in the
ModelInputComponent's groupedOptions memo:

  - The first loop iterates over the backend-supplied options (which
    are filtered).
  - The augment loop then iterates over every enabled model in
    useGetModelProviders × useGetEnabledModels — both of which
    are filter-unaware — and pushes the union into the dropdown.

So even though the backend correctly excluded
gemini-3.1-flash-image-preview from options, the augment loop happily
re-added it because providersData listed it and enabledModelsData
flagged it as enabled.

Fix: read filters off nodeClass.template.model.filters (the same
declaration that drives the backend filter) and apply the same metadata
match to every option that flows through groupedOptions. Two
checkpoints:

  1. Defensive pass in the existing-options loop. The backend should
     have filtered already, but stale saved flows could surface a
     build_config that hasn't been corrected yet — same self-healing
     story as the backend _resolve_filters helper.
  2. New pass in the augment loop, after the model_type check.
     Every enabled-model candidate must satisfy the filter or it's
     skipped.

Conservative behavior matches the backend: when a filter key (e.g.
tool_calling) is declared but the candidate's metadata doesn't
carry that key at all, the candidate fails the check — better drop an
undeclared row than surface one that crashes at run time.

Tests (4 new): hides tool-incompatible augmented models, drops
saved options that fail the filter, conservative drop on missing
metadata key (with a known-good sibling so the assertion isn't
paper-thin), no-op when no filters are declared. Suite-local
beforeEach restores the default hook mocks since jest.clearAllMocks
only resets call history, not implementations.

* Fix tests

* Update .secrets.baseline

* Fix starter projects

* Update .secrets.baseline

* [autofix.ci] apply automated fixes

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

* Update component_index.json

* Update Structured Data Analysis Agent.json

* [autofix.ci] apply automated fixes

* fix frontend tests

* fix remaining CI errors

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-05-18 21:39:06 -07:00
b7235548d4 fix(ci): backport bundle rename step to nightly workflow on main (#13195)
* ci: call update_bundle_versions in nightly workflow

* ci: guard bundle steps against missing script or folder
2026-05-18 18:42:53 -07:00
c901602bc2 fix(ci): rename bundles to -nightly during nightly build (#13193)
fix: rename bundles for nightly build
2026-05-18 18:32:44 -07:00
23f2edf327 chore: Clean up the startup warnings for Python 3.14 (#13156)
* fix: backport policies ToolGuard lazy imports (#13144)

fix: backport policies toolguard lazy imports

* chore: Clean up the startup warnings for Python 3.14

* Update base.py

* Update component_index.json

* [autofix.ci] apply automated fixes

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

* Update component_index.json

* [autofix.ci] apply automated fixes

* Update Structured Data Analysis Agent.json

* [autofix.ci] apply automated fixes

* Update component_index.json

* [autofix.ci] apply automated fixes

* Update component_index.json

* Update starter projects

* Update .secrets.baseline

* [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-05-18 16:52:13 -07:00
e08080507b feat: Add langflow-stepflow package (#12015)
* feat: Add langflow-stepflow package

Introduces a new workspace package `src/langflow-stepflow/` that ports
the Stepflow integration from the Stepflow repository into the Langflow
codebase.

The package has two submodules:
- `translation/`: translates Langflow flow JSON to Stepflow flow
  definitions (ported from integrations/langflow/converter/)
- `worker/`: a Stepflow worker that executes individual Langflow
  components via lfx (ported from integrations/langflow/executor/)

Entry point: `python -m langflow_stepflow.worker` starts the HTTP worker
server for use by a Stepflow orchestrator.

No changes to existing Langflow code in this commit.

* fix: Address review feedback on langflow-stepflow package

- Widen Python version to >=3.10,<3.14 to match langflow-base
- Fix import sorting (ruff I001) and unused variable (ruff F841)
- Replace sk- prefixed test strings to avoid Gitleaks false positives
- Remove placeholder step reference resolution in component_tool
  (orchestrator resolves these before reaching the worker)
- Let exceptions propagate from component_tool_executor instead of
  returning error dicts that look like successful results
- Skip secret/password field defaults in tool input schemas
- Use LRU-style bounded cache (128) for compiled components
- Use asyncio.to_thread for sync component methods to avoid blocking
- Fix mutable NamedTuple default (list→tuple) in PlaceholderGraph
- Warn and skip deps with missing field mappings instead of silently
  falling back to "input" (which overwrites on multiple deps)
- Tighten _is_data_list to check __class_name__ == "Data" specifically
- Add comment explaining intentional teardown-before-init sequence
- Add integration test for example flows

* chore: Upgrade to Stepflow SDK 0.12.0 and adapt to lfx 0.3+ renames

- Bump stepflow-py and stepflow-orchestrator from >=0.10.0 to >=0.12.0
- Remove Python 3.11 environment markers (SDK now supports 3.10+)
- Replace server.run() with gRPC pull transport (run_grpc_worker)
  matching the upstream stepflow-langflow integration pattern
- Update Flow serialization from Pydantic model_dump to msgspec.to_builtins
- Remove Pydantic ValueExpr/actual_instance unwrapping (now plain dicts)
- Add _langflow_type_name() to map lfx 0.3+ class renames (JSON→Data,
  Table→DataFrame) back to canonical Langflow type names
- Fix DataFrame isinstance checks for lfx Table subclass
- Add missing README.md, tests/__init__.py, helpers package
- Add skip conditions for missing fixture files

* change to lfx schema

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes

* chore: Align langflow-stepflow ruff config with repo and format

- Set line-length = 120 to match root pyproject.toml (was 88, causing
  conflicts between pre-commit format hook and check hook)
- Run ruff format across all source and test files
- Add pragma: allowlist secret on test fixtures with fake API keys

* [autofix.ci] apply automated fixes

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

* chore: Rebuild component index

* [autofix.ci] apply automated fixes

* fix(langflow-stepflow): allow Python 3.14 in requires-python

Lift requires-python from <3.14 to <3.15 to match the rest of the
workspace and the lockfile. Without this, uv sync on Python 3.14
fails the Unit/LFX/Integration test jobs and the Docker build.

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: ogabrielluiz <gabriel@langflow.org>
Co-authored-by: Jordan Frazier <jordan.frazier@datastax.com>
2026-05-18 21:47:20 +00:00
5620ce1236 docs: rewrite AGENTS.md as hub with topical project philosophy docs (#13038)
Splits monolithic agent instructions into a slim AGENTS.md hub plus
docs/agents/ topic files (philosophy, architecture, components, contracts,
testing, anti-patterns) so AI coding agents have a single source of truth
for how the project thinks. Removes the .cursor/rules/ directory; AGENTS.md
is now the only agent-facing doc.

Fixes the long-standing pointer to the empty src/backend/base/langflow/components/
stubs (real components live in src/lfx/src/lfx/components/) and corrects
the v2 API description from 'future' to its actual mounted state.
2026-05-18 21:46:08 +00:00
2ff17eca24 fix: Model handling for tool calling in agents and update IBM models (#13191)
* Model handling for tool calling in agents and update IBM models

* [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-05-18 21:35:37 +00:00
d1da768192 feat(knowledge): merge ingestion + retrieval into mode-driven Knowledge component (#13120)
* feat(knowledge): merge ingestion + retrieval into mode-driven Knowledge component

Merge KnowledgeIngestionComponent and KnowledgeBaseComponent into a single
KnowledgeComponent driven by a TabInput mode selector ("Ingest" / "Retrieve"),
mirroring the PoliciesComponent pattern.

- New: src/lfx/src/lfx/components/files_and_knowledge/knowledge.py holds the
  full merged component. update_build_config and update_outputs hide the
  inputs and the output that do not apply to the current mode.
- Legacy KnowledgeIngestionComponent / KnowledgeBaseComponent become thin
  subclasses pinned to their respective modes with class-level inputs and
  outputs aligned to the legacy shape, so saved flows continue to load.
- Output names ("dataframe_output", "retrieve_data") preserved for back-compat.
- New tests cover mode-switch input/output visibility and subclass pinning;
  existing test_ingestion / test_retrieval suites rerouted to patch the new
  knowledge module path and continue to pass.

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

* chore(knowledge): drop emoji from mode labels, mark legacy subclasses

- Mode tab labels: "📥 Ingest" / "🔍 Retrieve" → "Ingest" / "Retrieve" so the
  selector blends with the rest of Langflow's TabInput palette (see MemoryComponent
  for the same convention).
- Set `legacy = True` on KnowledgeIngestionComponent and KnowledgeBaseComponent
  so the back-compat shims stay loadable by saved flows but no longer clutter
  the new-component palette — users should reach for the merged Knowledge
  component instead.

* [autofix.ci] apply automated fixes

* fix(knowledge): declare both outputs at class level so retrieve mode dispatches correctly

Saved flows in retrieve mode were hitting
``'NoneType' object has no attribute 'to_dataframe'`` because the merged
``KnowledgeComponent`` only declared ``dataframe_output`` (build_kb_info)
at the class level. The runtime resolves outputs from the class-level
``outputs`` list at __init__ time, so ``retrieve_data`` was never in
``_outputs_map``; the runtime fell back to ``build_kb_info``, which ran
``convert_to_dataframe(self.input_df)`` against a hidden, None-valued
input.

Fix: declare both outputs at the class level (TypeConverterComponent +
MemoryComponent already use this pattern). ``update_outputs`` keeps
filtering the canvas-visible output by mode, but the runtime can now
dispatch to either method based on which one the saved flow has wired up.
Also wire ``update_frontend_node`` to re-run the mode-driven output
filter on canvas load so saved flows render the correct output even
before the user clicks the tab.

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

* fix(knowledge): dispatch by mode in output methods to survive stale edges and legacy labels

Two failure modes were still triggering the
``'NoneType' object has no attribute 'to_dataframe'`` crash even with both
outputs declared at the class level:

1. User drops the Knowledge component (default INGEST), wires the
   ``dataframe_output`` edge, then switches to Retrieve. The saved edge
   still points at ``build_kb_info``; the runtime calls it against a
   None ``input_df`` and crashes inside ``convert_to_dataframe``.
2. Flows saved with the original emoji mode labels ("📥 Ingest" /
   "🔍 Retrieve") carry those strings on disk. The post-emoji-removal
   equality checks (``mode == MODE_RETRIEVE``) failed, so even though
   the user was conceptually in retrieve mode, the component fell
   through the ingest path.

Both methods now check the mode at entry and delegate to the other one
when the wired output doesn't match. Mode matching is lenient
(substring-based) so the legacy emoji labels still resolve.

``update_build_config`` and ``update_outputs`` also normalize legacy
labels onto the current canonical values so visibility toggles correctly
for flows pulled from old saves.

* [autofix.ci] apply automated fixes

* feat(knowledge): point legacy shims at the merged component and migrate starters

- Add ``replacement = ["files_and_knowledge.Knowledge"]`` to both
  ``KnowledgeIngestionComponent`` and ``KnowledgeBaseComponent`` so the
  canvas legacy-component banner offers the merged component as the
  one-click upgrade target.
- Rewrite the ``KnowledgeBase`` nodes in the two starter projects that
  shipped them (``Knowledge Retrieval`` and ``Vector Store RAG``) to use
  the new ``Knowledge`` component with mode pre-set to ``Retrieve``. All
  edges are re-pointed at the new node id, the embedded module path is
  updated to ``lfx.components.files_and_knowledge.knowledge.KnowledgeComponent``,
  and the mode value is normalised away from the old emoji label.

* [autofix.ci] apply automated fixes

* Update knowledge.py

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

* [autofix.ci] apply automated fixes

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

* Update component_index.json

* [autofix.ci] apply automated fixes

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

* Update src/lfx/src/lfx/components/files_and_knowledge/knowledge.py

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

* [autofix.ci] apply automated fixes

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

* fix: Restore knowledge creation functionality

* Template update

* Update component_index.json

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

* [autofix.ci] apply automated fixes

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

* fix: Linting errors

* Update knowledge.py

* [autofix.ci] apply automated fixes

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

* [autofix.ci] apply automated fixes

* fix: Address @rafaelgiln comments

* Update test_knowledge.py

* Update component_index.json

* Template updates

* Update .secrets.baseline

* [autofix.ci] apply automated fixes

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

* Update Structured Data Analysis Agent.json

* Update component_index.json

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes

* Fix edge connection with parser

* Update Structured Data Analysis Agent.json

---------

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>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-05-18 20:05:01 +00:00
7993ed3b70 fix: avoid pickling vertex component instances (#13172)
* Avoid pickling vertex component runtime state

* Add docstrings for vertex serialization regression helper

---------

Co-authored-by: Gabriel Luiz Freitas Almeida <gabriel@logspace.ai>
2026-05-18 19:40:42 +00:00
c78e10e85a docs: add directory depth limitation warning to custom components (#13104)
* docs: add directory depth limitation warning to custom components

Add a warning admonition to the custom components documentation
explaining the MAX_DEPTH=2 limitation for component discovery.

The warning clarifies:
- Components must be at most 2 levels deep (category/component.py)
- Each first-level directory becomes an independent category in the UI
- How to modify MAX_DEPTH for advanced use cases

This addresses confusion around subdirectory support mentioned in issue #13102.

* docs: simplify directory depth warning per review feedback

Co-Authored-By: Antonio <antonio@oriontech.me>

---------

Co-authored-by: Antonio <antonio@oriontech.me>
2026-05-18 18:26:49 +00:00
cc009c9133 feat(lfx): Bundle Separation and LFX Extension Framework (#13043)
* feat(lfx): installed-package + seed-directory discovery for production install (LE-1022)

Adds the read-only production install path for Modes A, B, and C of the
Bundle Separation iteration. Manifest-shipping pip-installed
distributions and seed-directory subdirectories are discovered at server
startup and registered as Extensions at @official.

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

* feat(lfx): add single-Bundle loader and LANGFLOW_COMPONENTS_PATH discovery (#12967)

* feat(lfx): add extension manifest schema, validate CLI, and error formatter (LE-1014)

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

What's in lfx.extension:

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

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

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

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

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

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

Fixes ModuleNotFoundError seen in CI on the 3.10 job.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

The test suite is split to mirror the package:

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

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

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

* fix: LangflowCompat -> LfxCompat

* fix: Remove references to ticket

* fix: encode maxItems constraint in schema

* fix: deferred fields and schema version

* Fix ruff errors

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

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

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

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

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

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

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

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

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

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

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

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

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

Also makes _distribution_canonical_name defensive against MagicMock test seams.

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

Addresses the latest review of PR #12967:

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

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

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

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

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

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

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

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

Addresses the latest review feedback on PR #12967:

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

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

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

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

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

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

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

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

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

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

Closes the remaining nits on PR #12967:

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

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

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

The test suite is split to mirror the package:

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

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

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

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

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

Adds:

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

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

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

---------

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

* [autofix.ci] apply automated fixes

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

* feat(lfx): add extension manifest schema, validate CLI, and error formatter (LE-1014)

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

What's in lfx.extension:

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

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

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

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

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

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

Fixes ModuleNotFoundError seen in CI on the 3.10 job.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

The test suite is split to mirror the package:

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

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

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

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

The two scaffolding CLIs an Extension author types:

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

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

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

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

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

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

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

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

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

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

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

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

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

Plus three MEDIUMs:

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

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

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

* fix: LangflowCompat -> LfxCompat

* fix: Remove references to ticket

* fix: encode maxItems constraint in schema

* fix: deferred fields and schema version

* Fix ruff errors

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

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

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

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

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

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

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

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

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

* feat(lfx): add extension manifest schema, validate CLI, and error formatter (LE-1014)

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

What's in lfx.extension:

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

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

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

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

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

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

Fixes ModuleNotFoundError seen in CI on the 3.10 job.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

The test suite is split to mirror the package:

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

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

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

* fix: LangflowCompat -> LfxCompat

* fix: Remove references to ticket

* fix: encode maxItems constraint in schema

* fix: deferred fields and schema version

* Fix ruff errors

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

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

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

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

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

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

What landed:

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

What is deliberately deferred:

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

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

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

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

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

* feat(lfx): add extension manifest schema, validate CLI, and error formatter (LE-1014)

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

What's in lfx.extension:

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

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

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

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

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

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

Fixes ModuleNotFoundError seen in CI on the 3.10 job.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

The test suite is split to mirror the package:

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

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

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

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

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

Adds:

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

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

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

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

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

What landed (frontend only):

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

Deferred:

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

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

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: End to end bundle installation

* fix: Review comments addressed

* Update component_index.json

* Update component_index.json

* Update router.py

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

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

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

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

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

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

This reverts commit 5aa008a3cd.

* feat: DuckDuckGo as Extension in new Bundle System (#13044)

* feat: DuckDuckGo Extension for bundles

* fix ruff errors

* Update test_pilot_duckduckgo_upgrade.py

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

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

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

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

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

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

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

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

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

---------

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

* fix: Review sweep

* fix: More review comments addressed

* Ruff check

* docs: add bundle porting guide

Step-by-step recipe for extracting a provider package from the in-tree
``src/lfx/src/lfx/components/<provider>/`` directory into a standalone
Extension Bundle distribution under ``src/bundles/<provider>/``.

The DuckDuckGo bundle is the reference; every section maps to a single
copy-pasteable change and a verification command.

Doc surfaces a forthcoming ``scripts/migrate/port_bundle.py`` automation
helper for the mechanical bits; that script lands on the next branch
together with the second-pilot port that validates the recipe end-to-end.

* fix(extension): accept Output(method=...) in build-method validator

The validator's static AST check looked for a literal ``build`` method
on every Component subclass and emitted ``build-method-missing`` when
absent.  Real Langflow components -- including the production
``DuckDuckGoSearchComponent`` and the in-tree ``ArXivComponent`` -- do
not declare a literal ``build``; they declare entry-points
declaratively via ``outputs = [Output(name=..., method=<name>)]`` and
the named method is what gets called.  Result: every clean port hit a
spurious ``build-method-missing`` error from ``lfx extension validate``.

Fix: extend ``_has_build_method`` to also accept any class that names a
method via ``Output(method="X")`` AND defines that method in the class
body.  The negative case ("Output(method=...) without a matching def")
still fires -- a typo'd method name would crash at runtime, so the
static check should keep flagging it.

Two new tests in ``test_validate.py`` lock the contract: positive case
mirroring duckduckgo / arxiv, negative case for typo'd method names.
All 26 validator tests pass.

PORTING.md updates fall out of running the recipe live against the
duckduckgo bundle on this branch:

  * Validate path is ``src/bundles/<bundle>/src/lfx_<bundle>``, not the
    bundle root (the manifest lives next to ``__init__.py``).
  * Index regen needs ``LFX_DEV=1`` to skip the prebuilt-index fast path
    and uses ``scripts/build_component_index.py``.
  * Drop the migration-table JSON from the ruff invocation (ruff treats
    it as Python and complains about the top-level expression).

* fix: Delete outdated components

* fix(palette): wire reload kebab via DropdownMenu, not Select

The reload kebab on the palette Bundle header was using Radix ``Select``
to back its overflow menu.  ``Select`` is for picking a value, not for
firing an action: ``onValueChange`` is gated by value-equality (so a
re-click of the only item is a no-op), and the popover-portal click
semantics interact poorly with the parent disclosure-trigger button.
The combined effect: clicking ⋮ → Reload opened the popover and closed
it, but never fired the network request, with no console error to point
at.

Switch to ``DropdownMenu`` (purpose-built action menu).
``DropdownMenuItem`` exposes ``onSelect`` which fires on every
activation -- the same callback path keyboard navigation uses -- so
clicking Reload reliably invokes the mutation.

Test mocks updated to drive the DropdownMenu primitives instead of
Select; all 8 bundleHeaderActions tests + the broader 437-test sidebar
suite still pass.

* fix(extension): route ddgs through the lockfile + drop bogus list endpoint hint

Two reviewer findings, both about reproducibility / correctness of
operator-facing surfaces.

[P2] ``docker/build_and_push_base.Dockerfile`` previously installed
``ddgs`` via an unpinned ``uv pip install ddgs`` after the workspace
sync.  That made the base image non-reproducible: a future ``ddgs``
release would silently drift from the tested lock state on every
rebuild.

Fix: route ddgs through the locked sync.

  * Restore the ``duckduckgo`` extra in ``src/backend/base/pyproject.toml``
    (``ddgs>=9.0.0``) as an internal "image-build sidecar".  Public
    consumers still install ``lfx-duckduckgo`` directly; the extra
    exists only to keep ``ddgs`` resolved in the lockfile.
  * The Dockerfile now passes ``--extra duckduckgo`` to ``uv sync
    --frozen``, picking up the locked ``ddgs==9.14.1``.
  * The follow-on bundle install keeps ``--no-deps`` so it does not
    duplicate the now-locked ``ddgs`` / ``lfx`` / ``langchain-community``.

[P3] ``langflow/api/v1/extensions.py`` returned a fix hint that pointed
operators at ``GET /api/v1/extensions`` -- a route that does not exist
in this PR (it lands with the LE-1019 list endpoint).  Replaced with a
reference to ``lfx extension list``, which is shipped here.

* fix(compat): bridge langflow.components.* dynamically via meta path finder

Commit 45552cd1df ("fix: Delete outdated components") removed the
physical shim files under ``src/backend/base/langflow/components/``
that forwarded saved-flow imports like
``from langflow.components.processing.converter import convert_to_dataframe``
or ``import langflow.components.knowledge_bases.retrieval`` to their
new ``lfx.components.*`` homes.  Without those shims, dotted imports
into ``langflow.components.<sub>.<leaf>`` failed at flow-load time --
the existing ``LangflowCompatibilityModule`` registers ``langflow.components``
itself in ``sys.modules`` but does not bridge submodules, so Python's
import machinery falls through to the now-empty langflow.components
directory and raises ``ModuleNotFoundError``.

Replace the deleted physical-shim stack with a single ``MetaPathFinder``
in ``langflow/__init__.py`` that dynamically resolves every
``langflow.components.<rest>`` import to ``lfx.components.<rest>`` and
registers the loaded lfx module in ``sys.modules`` under both names.
The langflow- and lfx-prefixed imports share a single underlying module
object, so class identity is preserved across the bridge -- ``isinstance``
checks against types resolved through either path keep working.

The finder also carries a small first-segment override map for the few
subpackages whose name diverged during the move; the only entry today is
``knowledge_bases`` -> ``files_and_knowledge``, matching the deleted
shim's intent.

Why a meta finder instead of restoring the physical shim files (option
1 from the scope analysis): the meta finder scales without per-bundle
maintenance.  Every future bundle extraction landed under ``lfx.components``
becomes reachable via the legacy ``langflow.components.<bundle>`` path
immediately; nobody has to remember to add a parallel langflow shim.

Six new unit tests in ``test_langflow_components_compat_shim.py`` lock
the contract: dotted submodule resolution, helpers re-export, the
``knowledge_bases`` override, class identity preservation, top-level
aliasing, and the "arbitrary extracted bundle" case that prevents
regression for future ports.

Verified pre-existing tests:
  * 17 dynamic-import integration tests pass.
  * The reload-route-guard suite still passes.
  * The Research Translation Loop starter-project test (which loads
    ArXivComponent) passes.

* Update index.tsx

* fix: Review pass on delivery

* fix: Second review sweep, bare names

* fix: Third review sweep

* Resolve dotted imports and attribute chains

Record import shape and flatten attribute chains so router references can be resolved across dotted imports and re-exports. Added ImportTarget dataclass, _attribute_chain helper, and switched IncludeCall/DecoratorRef to store attribute tuples. parse_file now records ImportTarget entries for imports and captures dotted parent/child chains for include_router and decorators. Replaced _resolve_var with _resolve_chain which handles "from" vs "module" imports, various import shapes (including import x.y and aliased imports), and prevents infinite recursion on re-export cycles.

* One more sweep

* Add seed-directory extension loading and docs

Introduce filesystem "seed directory" extension support and authoring docs. Adds three documentation pages (quickstart, manifest reference, author guide) and wires them into the docs sidebar. Implement load_seed_extensions to discover/load bundles from $LANGFLOW_SEED_DIR (default /opt/langflow/bundles), export it from loader/__init__ and extension package, and integrate it into the components import pipeline. Handle installed-vs-seed shadowing by preferring installed distributions and appending a typed seed-bundle-shadowed ExtensionError; add the corresponding error code and message. Update schema doc link and add unit/integration tests to cover seed loading, determinism, shadowing behavior, and migration-target resolution.

* Fix claims

* Fix docasaurus build

* Clean up the manual checklist

* Update test_pilot_duckduckgo_upgrade.py

* fix: Some wording issues with dogfooding

* fix: Comments addressed

* Update port_bundle.py

* Update component_index.json

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

* fix: pytest testpaths glob and accurate scaffold output in port_bundle

testpaths now uses ``src/bundles/*/tests`` so future bundle tests are
picked up automatically without hardcoding each bundle at the root.
pytest expands the glob via ``glob.iglob(..., recursive=True)``.

Rewrote port_bundle.py's ``_render_migration_entries`` and
``_render_test_scaffold`` to match the actual conventions:

* Migration entries now use ``bare_class_name``/``import_path``/
  ``legacy_slot`` + ``target`` + ``added_in`` (the keys
  ``lfx.extension.migration.loader`` actually reads) instead of the
  invented ``from``/``to``/``release`` shape.
* Test scaffold now imports ``load_migration_table`` and
  ``migrate_flow_payload`` -- the APIs the real tests use -- instead of
  a fictitious ``resolve_legacy_id``.  Includes the ``migration_table``
  fixture, ``_saved_flow``/``_saved_flow_node`` helpers, and the
  distribution-importable + manifest-shipped checks that mirror
  ``test_pilot_duckduckgo_upgrade.py``.

Verified by rendering the scaffold for duckduckgo with synthetic plan
data, dropping it into ``src/lfx/tests/integration/extension/``, and
running pytest -- ``3 passed, 2 skipped`` (skips are the wheel-only
checks, by design when run inside the lfx isolation venv).

* [autofix.ci] apply automated fixes

* fix: Review comments

* Update test_discovery.py

* feat: Port Arxiv to the Extension Framework (#13047)

* feat(bundles): port arxiv as the second-pilot Bundle + porting helper

Validates ``src/bundles/PORTING.md`` end-to-end by following its recipe
against a clean candidate (``ArXivComponent``: no third-party runtime
deps, no langflow-base extra, no deactivated duplicate).  Touchpoints
exercised:

  * Bundle skeleton at ``src/bundles/arxiv/`` mirroring duckduckgo.
  * In-tree provider directory removed from ``src/lfx/src/lfx/components/``
    along with its three references in ``components/__init__.py``.
  * Workspace wiring: dep, ``[tool.uv.sources]``, ``[tool.uv.workspace]``
    members, lockfile.
  * Migration table: bare-name + two import-path forms + legacy_slot
    entry.
  * Component index regenerated via ``LFX_DEV=1`` (forces dynamic
    discovery; without it the script reproduces stale entries).
  * Integration test ``test_pilot_arxiv_upgrade.py`` mirroring the
    duckduckgo pilot suite; 5 tests pass against the workspace install.

PORTING.md updates fall out of running the recipe live:
  * Validate path is ``src/bundles/<bundle>/src/lfx_<bundle>``, not the
    bundle root (the manifest lives next to ``__init__.py``).
  * Index regen needs ``LFX_DEV=1`` to skip the prebuilt-index fast path.
  * Drop the migration-table JSON from the ruff invocation (ruff treats
    it as Python and complains about the top-level expression).

``scripts/migrate/port_bundle.py`` is the mechanical helper referenced
from § Automation: stdlib-only, dry-run by default, refuses on invalid
input, and intentionally leaves migration-table edits + integration-test
authoring to a human (release version + bare-name uniqueness require
judgement).  Three guard rails verified: invalid bundle name,
existing-target-bundle, missing in-tree provider.

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

* Update Research Translation Loop.json

* Update .secrets.baseline

* fix: Move bundle test for arxiv

* Update PORTING.md

* Update component_index.json

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

* Update component_index.json

* [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>

* [autofix.ci] apply automated fixes

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

* Update component_index.json

* Update component_index.json

* Update component_index.json

* fix: empty cache dict on reload

* Lint fixes

* Update test_components_cache_integration.py

* Update test_reload.py

* Hardening pass

* Update test_init_template.py

* fix: resolve cross-source bundle-name shadowing before registry population

The reload pipeline reads ``live.source_path`` from the registry on every
call.  Previously, the registry-population loop in
``import_extension_components`` only special-cased installed-shadows-seed;
for every other pair (seed/dev, seed/inline, dev/inline,
installed/dev, installed/inline) it silently overwrote earlier records
via last-wins iteration order.  A stale dev registration whose
``source_path`` pointed at a different filesystem location could clobber
the seed record's ``source_path``; reload would then walk the dev path
while the operator edited the seed copy on disk -- producing 200 OK with
empty deltas on every reload, including for syntactically broken edits.

Generalize the dedup pass to every (earlier, later) pair using the
explicit precedence ``installed > seed > dev > inline``, applied before
both registry population AND palette template construction so the two
read from the same winning source.  Add a new generic ``bundle-shadowed``
typed error code for the pairs the existing ``seed-bundle-shadowed`` did
not cover; keep ``seed-bundle-shadowed`` for the documented installed-
over-seed pair so existing CLI exit-code logic and snapshot tests stay
intact.  Both codes are warn-only in ``lfx extension list``.

Also add an INFO log line in reload Stage 1 with the resolved
``source_path`` so the next "200 OK with empty deltas" repro can be
triaged from the server log alone -- if the path logged is not the path
the operator was editing, this dedup is what to look at.

Includes a regression test
(``test_seed_bundle_shadows_dev_emits_generic_bundle_shadowed``) that
asserts both halves: seed wins in the BundleRegistry AND the registry's
``source_path`` is the seed path, not the stale dev path.

* Update component_index.json

* fix: widen lfx-* bundles' requires-python to <3.15 to match langflow root

The two pilot bundles (``lfx-arxiv``, ``lfx-duckduckgo``) were scaffolded
by ``scripts/migrate/port_bundle.py`` whose template hard-coded
``requires-python = ">=3.10,<3.14"``.  Because both bundles are uv
workspace members of the langflow root, that cap leaked into every
workspace-level resolve: ``cd src/lfx && uv sync`` (the path
``make lfx_tests`` takes) refuses to pick a Python 3.14 interpreter even
though lfx itself, langflow, and langflow-base all advertise
``>=3.10,<3.15``.  Hosts with 3.14 installed see:

    error: The requested interpreter resolved to Python 3.14.5, which
    is incompatible with the project's Python requirement: `>=3.10, <3.14`.

Fix at the source so future ``port_bundle.py`` runs do not regress this:
template emits ``<3.15``, and the two existing emitted pyprojects are
bumped in lockstep.  ``uv.lock`` regenerates with the wider range.

Verified ``make lfx_tests`` resolves cleanly on a host with both 3.13.12
and 3.14.5 installed; the focused extension slice runs to completion.

* Update __init__.py

* Update src/lfx/tests/unit/extension/migration/test_rewrite.py

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

* Update src/lfx/src/lfx/extension/loader/_plugins.py

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

* Update docs/docs/Deployment/deployment-extensions-production.mdx

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

* Update src/lfx/src/lfx/extension/loader/_orchestrator.py

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

* Update docs/docs/Develop/extensions-author-guide.mdx

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

* Update docs/docs/Develop/extensions-manifest.mdx

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

* fix: Coderabbit review suggestions

* Template updates

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes

* Tweaks to bundle hot reload

* [autofix.ci] apply automated fixes

* chore: Address review comments by @Cristhianzl

* [autofix.ci] apply automated fixes

* Update BUNDLE_API.md

* Update component_index.json

* Update component_index.json

* Update Structured Data Analysis Agent.json

* fix: Next round of review comments

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-05-18 17:36:56 +00:00
f3ad468c13 ci: stop transient Windows npm/cache flakes from failing frontend tests (1.10.0) (#13175)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 13:40:21 -03:00
50625dce9c fix: update embedded chat CDN URL to langflow-ai org and v1.0.8 (#13118)
* fix: update embedded chat CDN URL to langflow-ai org and v1.0.8

* test: update widget code tests for langflow-ai CDN URL and v1.0.8

- Update CDN URL expectations from logspace-ai to langflow-ai
- Bump version assertions from v1.0.7 to v1.0.8
- Fix stale multi-line URL assertion removed by the implementation fix

* fix(types): add isAuth to GetCodeType to fix Biome no-explicit-any lint errors

isAuth was already used in getWidgetCode and passed from EmbedModal but was
missing from the type definition, forcing test assertions to use `as any`.
2026-05-18 15:47:38 +00:00
d017e156cd fix: add stream toggle back to agent (#13155)
* add stream toggle back to agent

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

* ci: stop transient Windows npm/cache flakes from failing frontend tests

The nightly frontend job fails almost every day on a single Windows
Playwright shard, never on actual test logic. With fail-fast:false over
~70 Windows shards each making several cache/artifact-service calls, a
transient GitHub Actions cache/artifact hiccup on any one shard flips
needs.setup-and-test.result to failure and reds the whole nightly
(observed steps: Setup Node.js Environment, Cache Playwright Browsers,
Upload Playwright Coverage Artifact).

- Decouple npm caching from actions/setup-node (its internal cache step
  can't be made non-fatal) and replace it with a standalone
  actions/cache@v5 step marked continue-on-error.
- Mark Cache Playwright Browsers and the artifact-upload steps
  continue-on-error so an infra blip can no longer fail a test shard.

Only a real test/browser failure can fail a shard now.

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

* [autofix.ci] apply automated fixes

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

* [autofix.ci] apply automated fixes

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-05-18 13:30:08 +00:00
851a48401b fix: Auto-create write parents and reject unsafe glob patterns (#13075)
* Auto-create write parents and reject unsafe glob patterns

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-05-18 12:20:58 +00:00
ec924b1af4 fix: Remove duplicate Lock flow control from bottom-center menubar (#13088)
* remove flow lock button form canvas controls

* [autofix.ci] apply automated fixes

* fix testcaes

* fix testcaes

---------

Co-authored-by: Olayinka Adelakun <olayinkaadelakun@Olayinkas-MacBook-Pro.local>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-05-16 16:24:27 +00:00
682c97846f fix(schema): resolve nested $ref objects to named models instead of dict (#13128)
Two-level nested $defs/$ref schemas (e.g. payload → Outer.inner → Inner)
were collapsing the inner model to a bare `dict` because of three issues
in `create_input_schema_from_json_schema`:

1. `parse_type` resolved `$ref` before calling `_build_model`, so named
   refs lost their identity and always went through the anonymous path.
2. Anonymous models were named using `len(model_cache)`, but the cache is
   only populated after a build completes, so concurrent in-progress
   models collided on `AnonModel0` and falsely triggered the
   self-reference guard.
3. The `$ref` branch in `_build_model` pre-added the refname to the
   `building` set before recursing, so the recursive call immediately
   saw itself as self-referential and fell back to `dict`.

The Pydantic model still required `inner` (Outer.required = ["name",
"inner"]), but the LLM only saw a generic dict schema for `inner` and
omitted the field, producing `payload.inner: Field required` 500s
before the MCP call ever reached the server.

Fix:
- Preserve the original $ref-bearing subschema in `parse_type` and route
  it through `_build_model`'s $ref branch.
- Replace `len(model_cache)` with a monotonic anonymous-name counter.
- Drop the redundant `building.add/discard` and model_cache assignment
  from the `$ref` branch; the recursive `_build_model` call already
  handles both correctly.

Adds four regression tests covering the reporter's repro, missing-field
validation, no-spurious-warning, and sibling inline-object distinctness.
2026-05-16 01:26:04 +00:00
4dc9e28eb0 fix(initial_setup): upsert by (user_id, name) when loading flows from disk (#13132)
* fix(initial_setup): upsert by (user_id, name) when loading flows from disk

Langflow fails to start with IntegrityError on the unique_flow_name
constraint when LANGFLOW_LOAD_FLOWS_PATH is used in CI/CD pipelines and
the flow file's id differs from the DB record's id (e.g. nightly
upgrade, regenerated UUIDs, fresh DB). Manual DB intervention was the
only workaround.

Root cause: find_existing_flow only looked up by endpoint_name and id,
never by (user_id, name). The unique constraint is on (user_id, name),
so when those lookups missed, the loader fell through to INSERT and
PostgreSQL rejected it.

Fix:
- find_existing_flow now takes optional keyword args (user_id, name)
  and falls back to a (user_id, name) lookup when endpoint_name/id
  don't match. The endpoint_name branch is also scoped by user_id when
  supplied so it can't accidentally return another user's flow.
- upsert_flow_from_file passes name and user_id to find_existing_flow.
  When the match is by name (existing.id != file's flow_id), the DB id
  is preserved during the setattr loop -- rewriting it from under FK
  referrers was unsafe and was never required by the original logic.

Tests: 8 regression tests cover lookup precedence (id wins, endpoint
scoped by user, name fallback fires only when id misses), the
reporter's scenario (no IntegrityError on repeated import with
differing file ids), DB-id preservation on name-match, and a
no-regression case where matched-by-id behavior is unchanged.

Fixes the CI/CD upgrade failure reported in IBM Cloud Slack
(C06SUKEN3LJ thread 1777992833.170099).

* fix(initial_setup): fix matched_by_id on SQLite, improve update log message

Normalize existing.id to UUID before the matched_by_id comparison.
On SQLite, SQLAlchemy can return ids as strings; str == UUID is always
False, so matched_by_id would silently be wrong and the file id would
be dropped even on a genuine id-match.

Improve the update log to include the DB id and flow name alongside the
file's UUID so the CI/CD upgrade scenario (name-matched upsert with
differing UUIDs) is debuggable from logs.

* [autofix.ci] apply automated fixes

* fix: Preserve db id

* [autofix.ci] apply automated fixes

* feat: Add flag to control name matching overwrite

* [autofix.ci] apply automated fixes

* Flip the default value for the flag

* Update test_upsert_flow_from_file.py

* fix: Sqlalchemy-based crash on startup

---------

Co-authored-by: Jordan Frazier <jordan.frazier@datastax.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-05-16 01:20:14 +00:00
c493c817d9 fix(api): parse multipart/form-data on /run so session_id is preserved (#13092)
* fix(api): parse multipart/form-data on /run so session_id is preserved

parse_input_request_from_body always parsed the request body as JSON,
so multipart/form-data requests to /api/v1/run/{flow_id} (e.g. uploading
a file inline alongside text and a session_id) silently failed to parse
and returned a default SimplifiedAPIRequest. The flow then executed with
no input_value, no files, and routed the conversation into the default
session keyed on the flow_id rather than the caller-supplied session_id.

Detect multipart bodies and parse them via request.form() instead, mapping
string-valued fields (input_value, input_type, output_type,
output_component, session_id) and JSON-decoded tweaks onto
SimplifiedAPIRequest. Non-string entries (e.g. UploadFile values) are
ignored so inline file uploads do not crash the parser. JSON requests
continue to go through the existing fast path.

Fixes #9859.

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-05-15 21:31:39 +00:00