Commit graph

243 commits

Author SHA1 Message Date
Danny Avila
01e9d119bf
🛻 ci: Move the ESLint Config Sweep Into Its Own Job (#14742)
The full-sweep regression gate lints api+client+packages twice — once under
the PR's config and once under the base ref's — inside the same job as ~20
later steps (data-provider/data-schemas/api builds, config migration tests,
unused-i18n scan, and four depcheck passes), all sharing one 30-minute budget.

Two type-aware sweeps of the whole tree cost more than everything else in that
job combined. When they run long the job hits its timeout mid-sweep, so every
step behind the gate never executes and Static checks reports no result at all
— strictly worse than not running the gate. continue-on-error: true hides this,
because the step never fails; it simply never finishes.

Move the gate to its own job with its own budget so it cannot starve the other
checks, and bound each sweep so an over-budget run degrades to a notice rather
than a failure — an unfinished sweep is no evidence of a regression, and the
gate is advisory about config scope. Behaviour on a sweep that completes is
unchanged: coverage loss and new (file, rule, severity) diagnostics still fail.
2026-08-11 08:26:42 -04:00
Danny Avila
dfd4d9dd81
🧩 ci: Close Workflow Path-Filter Gaps (#14728)
* 🧩 ci: Close Workflow Path-Filter Gaps

Six trigger-filter gaps found by reading all workflows against the live
dependency graph (AI-1755, codegraph FINDINGS §6l):

- backend-review/frontend-review: root package.json/package-lock.json now
  trigger unit tests — a lockfile-only dependency bump previously ran zero
  backend or frontend unit tests while every test job installs from it
- agents-integration-tests: widen to the three package src trees it builds
  and imports (was only src/agents/**)
- cache-integration-tests: same shape — verified live that
  packages/api/src/flow/manager.ts (imported by mcp/oauth) matched neither
  integration filter
- docker-smoke: plain Dockerfile had no PR-time validation despite shipping
  via dev-images/tag-images; new node-image-smoke job builds it, gated by
  paths-filter to Dockerfile/.dockerignore changes
- dev-images/dev-branch-images: add config/**, skill/**, .dockerignore —
  the single-stage image COPYs the full build context
- static-checks: eslint.config.mjs now re-triggers the lint job (gap carried
  over from eslint-ci.yml in the #14716 consolidation)
- delete generate_embeddings.yml: fired on docs/**, which no longer exists,
  and its docs-root-path pointed at the same missing directory

* 🧩 ci: Address Codex Review Findings

- Build caches: all 26 build-* keys across 8 workflows now lead with
  root package.json + package-lock.json so manifest-only bumps cannot
  restore stale dists (data-provider embeds the root version); unifies
  the split key families (playwright already hashed the lockfile)
- static-checks: config changes now gate on the ESLint config loading
  and applying to representative files, plus a report-only full-tree
  sweep (70 pre-existing errors at dev HEAD block a hard gate for now)
- docker-smoke: the workflow file itself triggers the plain-Dockerfile
  build so job edits are validated
- dev-images/dev-branch-images: re-include skill/**/*.md after !**.md
  so shipped deployment-skill Markdown rebuilds images

* 🧩 ci: Gate Config Lint Sweep on Regression vs Base Config

Second-round codex finding: the report-only sweep swallowed config-wide
breakage in scoped blocks the representative files don't exercise. The
sweep now lints the same tree under the PR's config and the base ref's
config and fails only when the PR's config produces more diagnostics for
some (file, rule) pair — pre-existing debt never fails the gate, and
fixes are never penalized. Base-config unavailability degrades to the
load gate with a notice. Outcome surfaced in the failure summary.

* 🧩 ci: Harden Config Lint Gate per External Review

- Coverage direction: fail when the PR config stops linting files the
  base config covered (set difference on linted files) — a mis-scoped
  ignores previously only removed diagnostics and passed both gates
- Severity-aware fingerprints: (file, rule, severity) so warn->error
  escalations gate on a clean tree for that rule; downgrades still free
- Hard-fail when the base commit is missing so a future shallow-checkout
  change cannot silently disable the gate; annotate fetch-depth: 0
- Tab-separated fingerprint keys (space-in-path proof), --config on
  both sweeps, EXIT trap for the base config copy, comment on why it
  must live at the repo root (flat-config pattern base paths)
- Narrow skill md re-include with !skill/README.md: top-level README is
  documentation-only; runtime skill Markdown still rebuilds images
2026-08-10 17:26:50 -04:00
Danny Avila
c3a429ddcd
🎨 feat: Add Versioned Theme Foundation (#14709)
* 🎨 feat: Add Versioned Theme Foundation

* 🧩 fix: Keep Theme-Aware Chip Actions Consistent

* 🎛️ fix: Preserve Default Theme Geometry

* 🪪 fix: Keep Theme Identity in Sync

* 🧭 docs: Define Theme Styling Policy

* 🧹 chore: Sort Theme Imports

* 🧵 fix: Preserve Theme Compatibility Contracts

* 🛡️ fix: Harden Theme Compatibility Boundaries

* 🧵 fix: Publish Theme Appearance Preset

* 🐳 fix: Include Theme Preset in Docker Build

* 🪢 fix: Preserve Legacy Theme Compatibility

* 🧭 fix: Harden Theme Lifecycle Boundaries

* 🧱 fix: Align Theme Appearance Defaults

* 🧬 fix: Record Persisted Theme Provenance

* 🧭 fix: Preserve Theme Transition State

* 🧷 fix: Preserve Legacy Theme Contracts
2026-08-10 13:41:03 -04:00
Ravi Kumar L
7cf4c3f73f
🧪 test(e2e): add Bombadil property exploration (#14462)
* test(e2e): add Bombadil property exploration

* fix(e2e): address Bombadil review feedback
2026-08-10 02:11:06 +02:00
Marco Beretta
82de3bc422
🚦 ci: Reduce GitHub Actions Runner Pressure (#14716)
* ci: reduce GitHub Actions runner pressure

* ci: enforce static check path exclusions
2026-08-09 07:41:00 -04:00
Danny Avila
1bccc2bc18
📡 fix: Refresh MCP Tools After List-Changed Notifications (#14686)
* fix(mcp): handle dynamic tool list changes

Co-authored-by: Pascal Garber <pascal@artandcode.studio>

* test(mcp): fix CI validation

* fix(mcp): keep dynamic tool catalogs live

* fix(mcp): harden dynamic catalog lifecycle

* test(mcp): use typed startup connection

* test(mcp): isolate dynamic e2e fixtures

* fix(mcp): refresh tools after reconnect

* fix(mcp): close dynamic catalog cache gaps

* test(mcp): update OAuth connection mocks

* fix(mcp): preserve app snapshot ownership

* style(mcp): sort connection imports

* fix(mcp): close review race conditions

* fix(mcp): preserve cache ownership edges

* fix(mcp): harden recovery lifecycle

* fix(mcp): guard tool-less app refresh

* fix(mcp): fence distributed cache races

* fix(mcp): retire stale connection state

* fix(mcp): keep tool snapshots authoritative

* fix(mcp): fence stale app tool publications

* style(mcp): sort repository test imports

* test(mcp): mock empty startup publication

* fix(mcp): preserve app publication generations

* fix(mcp): harden publication recovery races

* fix(mcp): address tool catalogs by runtime config

* fix(mcp): load scoped catalogs for assistant writes

* fix(mcp): harden catalog publication recovery

* fix(mcp): serialize forced connection replacement

* fix(mcp): serialize ordinary creation with replacements

* fix(mcp): harden catalog fallback boundaries

* fix(mcp): close lifecycle fencing gaps

* fix(mcp): preserve catalog authority on failures

* fix(mcp): compensate failed catalog mutations

* fix(mcp): fence catalog refresh ordering

* style(mcp): sort agent loader imports

* fix(mcp): cancel stale connection creation

* fix(mcp): fence catalog coordination

* fix(mcp): close catalog race windows

* fix(mcp): harden cross-pod catalog fencing

* fix(mcp): close catalog lifecycle edges

* style(mcp): sort assistant imports

* fix(mcp): reject stale recovery authority

* fix(mcp): restore static catalog on every startup

* fix(mcp): order app catalog publications

* style(mcp): sort catalog revision imports

* fix(mcp): separate catalog allocation and commit fences

---------

Co-authored-by: Pascal Garber <pascal@artandcode.studio>
2026-08-08 13:50:21 -04:00
Marco Beretta
9e6d677751
⚙️ ci: Bump GitHub Actions to Node.js 24 Runtimes (#14689)
* ci: bump GitHub Actions to Node.js 24 runtimes

Clear Node 20 deprecation warnings on runners by moving workflow
actions to majors that declare node24 (checkout, cache, setup-node,
artifacts, Docker buildx/build-push/qemu/login, github-script,
setup-go, Azure login/helm, create-pull-request, axe-linter).

* fix: release leader lock via ioredis on Redis Cluster

@keyv/redis EVAL can surface unhandled MOVED redirects on cluster,
so resign() logged failure and left LeadingServerUUID set. Use ioredis
for leader election SET NX / GET / Lua (same pattern as principals and
concurrency locks) so cluster redirects are retried and resignation
clears the lock.
2026-08-07 11:25:29 -04:00
Danny Avila
ad0f72dede
🌀 ci: Deterministic Circular Dependency Checks (#14579)
* 🌀 ci: Deterministic Circular Dependency Checks

* 🌀 ci: Enforce Type-Level Edges in Circular Dependency Scan

* 🌀 ci: Materialize Import-Type Expression Edges in Cycle Scan

* 🌀 ci: Collect Inline Type-Only Specifier Edges in Cycle Scan
2026-08-01 14:43:26 -04:00
Danny Avila
52b2ebf948
🧪 test: Run mock E2E against Redis in shards (#14551)
* 🧪 test: Run mock E2E against Redis in shards

* 🧪 test: Isolate local Redis E2E data
2026-07-31 12:10:43 -04:00
Danny Avila
23d1ad473d
🍃 fix: Amazon DocumentDB Compatibility for Pipeline-Form Updates (#14495)
* 🍃 fix: Amazon DocumentDB Compatibility for Pipeline-Form Updates

- Rewrite acceptTerms without aggregation-pipeline update + $$NOW (null-guarded first-acceptance claim preserves the original timestamp under concurrent and repeat requests)
- Rewrite decrementTagCounts clamp-at-zero decrement as ordered two-op bulkWrite (clamp before guarded $inc)
- Rewrite extendFilesTTL TTL hold as projected read + per-doc guarded $set via tenantSafeBulkWrite, preserving only-widens/ceiling/cleared-stays-permanent semantics
- Log background index-build failures via Model 'index' listeners (previously swallowed silently, e.g. partialFilterExpression rejection on DocumentDB <5.0)
- Add misc/documentdb live-compatibility harness + assessment (AWS-cited)
- Fix latent file.spec helper bug: createdAt backdating was silently stripped by mongoose immutability

Closes #14488

* 🍃 fix: Harden DocumentDB-Safe Updates Against Cross-Call Races

Addresses Codex review on #14495:
- decrementTagCounts: normalize-null / $inc / clamp-negative op triple so
  interleaved decrements of the same tag converge on max(0, ...) exactly as
  the serialized pipeline did (clamp keys on count < 0, not count < amount)
- acceptTerms: guard the repeat-acceptance fallback with a non-null timestamp
  (exact complement of the claim guard) and retry the claim when a
  config/reset-terms.js reset races between the two updates, so acceptance
  never resurrects a reset cycle without a fresh audit timestamp

* 👷 ci: Suppress Ignored-File Warnings in Changed-File ESLint Run

Changed files under config-ignored paths (packages/data-schemas/misc/**)
emit "File ignored" warnings that fail --max-warnings=0.
2026-07-28 22:14:34 -04:00
Danny Avila
5af12c722e
🎭 ci: Scope Playwright Runs to Relevant Changes (#14388)
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
GitNexus Index / index (push) Waiting to run
GitNexus Index / post-index (push) Blocked by required conditions
2026-07-22 12:23:39 -04:00
Danny Avila
dbb771aa7c
🚦 ci: Gate Playwright Runs to Maintainers (#14385)
* ci: gate Playwright runs to maintainers

* ci: guard pull request context explicitly
2026-07-22 12:23:02 -04:00
Danny Avila
a3c92b83c8
🧾 ci: Skip Workflows for Markdown-Only Changes (#14378) 2026-07-21 20:35:59 -04:00
Anmol
5cf849d8d5
🪆 fix: Build RAG-API Chart Before Parent to Bundle PostgreSQL Dependency (#14262)
Co-authored-by: anmol-kumar-us <anmolsrivastav.lw@gmail.com>
2026-07-20 20:01:24 -04:00
Danny Avila
cf9a426d29
🛡️ fix: Guard HITL checkpoint size against MongoDB 16MB limit (#14157)
Some checks failed
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile, librechat-dev, node) (push) Has been cancelled
Docker Dev Images Build / build (Dockerfile.multi, librechat-dev-api, api-build) (push) Has been cancelled
GitNexus Index / index (push) Has been cancelled
Sync Locize Translations & Create Translation PR / Sync Translation Keys with Locize (push) Has been cancelled
Sync Helm Chart Tags / Ignore non-main push (push) Has been cancelled
Sync Helm Chart Tags / Sync chart tags (push) Has been cancelled
GitNexus Index / post-index (push) Has been cancelled
Sync Locize Translations & Create Translation PR / Create Translation PR on Version Published (push) Has been cancelled
* 🛡️ fix: Guard HITL checkpoint size against MongoDB 16MB limit

A LangGraph HITL checkpoint embeds the whole serialized message history
in a single BSON document, so a large conversation (inlined base64 media,
big tool outputs, long history) can serialize past MongoDB's 16MB
document ceiling. `MongoDBSaver.put` would then throw a raw
`BSONObjectTooLarge` at pause time and the pause would be lost with no
legible error.

`LazyMongoSaver` now measures the serialized checkpoint on the persist
path (rare HITL pauses only — the clean-exit common path is untouched):
debug-logs the size, warns past a soft 8MB threshold, and throws a typed
`CheckpointTooLargeError` before the doomed write past a 15MB hard limit
(16MB minus headroom for the document's other fields). Thresholds are
overridable via the constructor for testing.

Adds integration coverage (real serde + mongodb-memory-server) for the
under-threshold, soft-warn, and hard-reject cases.

* 🧱 fix: Add explicit types for isolatedDeclarations build

The production build (tsdown + rolldown-plugin-dts) compiles with
--isolatedDeclarations, which requires explicit type annotations on
exported bindings whose initializers it can't infer syntactically.
`CHECKPOINT_HARD_LIMIT_BYTES` (arithmetic over two consts) tripped
TS9010; annotate it and `CHECKPOINT_WARN_BYTES` as `number`. Verified
with `tsc --isolatedDeclarations` over the package.

* fix: Codex review — include metadata in the size guard + flush parked bookkeeping

P1 (lost bookkeeping): `put` consumes the write anchor, then AWAITS
assertCheckpointFitsDocument (checkpoint serialization). A bookkeeping-only
putWrites dispatched in that window sees neither the anchor nor persistedIds,
so it parks — and `put` never flushed it, dropping the marker (e.g. a
completed Send-sibling's __no_writes__) so a resume re-executed the sibling.
Extract flushBufferedBookkeeping (shared with the anchoring putWrites) and call
it after super.put in the persist path.

P2 (metadata ignored by guard): MongoDBSaver.put stores the serialized
checkpoint AND metadata (plus metadata_search) in the SAME document, but the
guard measured only the checkpoint — a just-under-limit checkpoint with large
metadata fell through to a raw BSONObjectTooLarge. Measure checkpoint +
metadata; the fixed headroom now only covers metadata_search/ids/framing.

Two integration regressions added (both fail without the fix, pass with it):
metadata-pushes-over-the-ceiling, and flush-during-the-serialization-window.

* fix: count metadata_search (raw metadata copy) in the checkpoint size guard

Codex follow-up: MongoDBSaver.put stores metadata a SECOND time as
`metadata_search: metadata` — the whole raw metadata object as a queryable BSON
subdocument in the same agent_checkpoints document. Measuring only checkpoint +
serialized metadata undercounted by that raw copy, so a large metadata.writes
payload could pass the 15 MB preflight while metadata_search pushed the actual
BSON past 16 MB — the raw BSONObjectTooLarge the guard exists to prevent.

Add mongoose.mongo.BSON.calculateObjectSize(metadata) for the metadata_search
contribution (mongoose already imported; no new dep). Headroom now only covers
ids + BSON framing. New integration test sizes a case where checkpoint +
serialized metadata is under the limit but the raw metadata_search copy pushes
it over — green (24/24).

* ci: run packages/api agents integration specs (checkpointer) in CI

The checkpointer.integration.spec.ts (durable HITL checkpointer vs a real
in-process MongoDB) is a *.integration.spec.ts, which test:ci deliberately
excludes — and cache-integration-tests.yml only covers cache/cluster/mcp/
stream, not src/agents/**. So it ran nowhere and its regressions guarded
nothing. Add:
- test:agents-integration script (jest over src/agents/*.integration.spec.ts,
  runInBand — mongodb-memory-server is in-process, no external service);
- a dedicated agents-integration-tests.yml (mirrors the proven build setup,
  no Redis) triggered on packages/api/src/agents/** changes;
- babel-plugin-replace-ts-export-assignment as a packages/api devDep: the spec
  imports @langchain/langgraph-checkpoint (whitelisted for babel transform,
  uses `export =`), whose transform needs this plugin — it was only present
  under client/node_modules, unresolvable from packages/api, so the suite
  couldn't load. 24/24 pass locally.
2026-07-08 15:31:33 -04:00
Ravi Kumar L
a0529c9af7
🪭 feat: Add opt-in Langfuse fanout gateway + collector (#13872)
* feat: add opt-in Langfuse fanout collector

* feat: fan out Langfuse feedback scores

* docs: prepare Langfuse fanout for OSS setup

* fix: clarify Langfuse fanout collector config

* test: stabilize librechat suite

* test: fix upload dialog import order

* fix: omit empty Langfuse tenant fields

* fix: gate tenant Langfuse fanout

* test: cover central Langfuse env fallback

* style: format Langfuse fanout config

* feat: route langfuse fanout by destination

* docs: clarify langfuse compose destination scope

* test: remove unrelated suite stabilization

* style: sort agent imports

* fix: treat blank tenant fanout toggle as disabled

* fix: rename tenant fanout emergency toggle

* test: guard langfuse fanout collector config drift

* feat: tune langfuse fanout batching

* test: render fanout helm tests without dependencies

* fix: narrow remote agent run config

* refactor: share string normalization helper

* fix: align langfuse fanout env parsing

* fix(langfuse): align score fanout toggles with traces

* fix(langfuse): keep central fanout config collector-only

* fix(langfuse): type fanout collector config

* fix(langfuse): harden tenant fanout config

* feat(langfuse): support media fanout gateway

* fix(langfuse): route tenant fanout through destination URL

* fix(langfuse): harden fanout routing checks

* ci(langfuse): test fanout gateway changes

* ci(langfuse): check fanout go formatting

* fix(langfuse): satisfy api typecheck
2026-06-26 11:26:39 -04:00
Danny Avila
03ecac8ac1
🧪 ci: Resolve DataTable test infinite re-render (#13947)
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
GitNexus Index / index (push) Waiting to run
GitNexus Index / post-index (push) Blocked by required conditions
DataTable.spec failed with "Too many re-renders" (35 tests). Root cause: @tanstack/react-virtual is measurement-driven, and jsdom has no real layout, so its re-render loop never converges. This went unnoticed because packages/client had no jest CI job (only the client workspace runs jest in frontend-review.yml).

- DataTable: only read the virtualizer (getVirtualItems/getTotalSize) when virtualization is active; the non-virtualized branch renders rows directly, so engaging it for small tables was wasted render-phase work.
- Spec: mock @tanstack/react-virtual, since jsdom can't exercise real virtualization layout.
- Add a test:ci script to @librechat/client and a Tests: @librechat/client CI job so packages/client specs run on every frontend PR.
2026-06-24 23:40:18 -04:00
Danny Avila
82662443e5
🧱 ci: Retry Failed Docker Builds (#13935)
* ci: retry failed Docker build jobs

* ci: skip stale Docker build retries

* ci: handle Docker retry edge cases
2026-06-24 10:09:36 -04:00
Danny Avila
ff81377573
🚦 ci: Stop Auto-Indexing PR Branches in GitNexus Index (#13866) 2026-06-20 11:03:21 -04:00
Danny Avila
c820dfb9a0
🛤️ ci: Limit GitNexus Deploys To Main And Dev Only (#13799) 2026-06-16 15:00:22 -04:00
Ravi Kumar L
fbc990f684
📈 fix: Isolate RUM Telemetry Proxy Auth from App Auth (#13765)
* fix(rum): isolate telemetry proxy auth

* feat(rum): track proxy error metrics

* refactor(rum): simplify proxy auth strategy flow

* test(rum): clarify proxy success metric assertion

* test(metrics): use typed supertest import

* test(metrics): add local supertest types

* test(metrics): keep supertest types local

* test(metrics): use official supertest types

* fix(rum): log proxy auth strategy errors

* fix(rum): classify proxy auth errors in metrics

* style(rum): sort telemetry metric imports

* ci: mention import sort check command

* ci: show targeted import sort example
2026-06-15 12:49:44 -04:00
Danny Avila
919a46312b
🧹 ci: Relieve disk pressure on GitNexus deploy (#13666)
The gitnexus droplet is ~8.7GB usable, not the 60GB the disk-cleanup
comment assumed. With /usr (~2.8GB), the in-use docker images (~2.1GB),
and the growing /opt/gitnexus/indexes (~1.2GB), deploys were aborting at
the `AVAIL_MB < 2048` guard ("Disk critically low").

Two fixes:
- Reclaim the previous gitnexus image after force-recreate. The pre-pull
  `docker system prune -af` cannot remove it while the old container is
  still running, so a stale ~700MB generation accumulated every deploy.
  A post-recreate `docker image prune -f` makes the box self-cleaning.
- Lower the abort threshold 2048 -> 1536MB. The image is ~700MB and
  shares most layers with the running one, so an incremental pull needs
  well under 1GB; the old guard was sized for the 60GB assumption.

Also corrects the stale 60GB comment to reflect the actual disk.
2026-06-10 22:44:54 -04:00
Danny Avila
b4fa200e5f
ci: Bump GitNexus to 1.6.7 to Fix Embeddings Index Timeout (#13658)
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
GitNexus Index / index (push) Waiting to run
GitNexus Index / post-index (push) Blocked by required conditions
*  ci: Bump GitNexus to 1.6.7 to Fix Embeddings Index Timeout

* ⏲️ ci: Raise GitNexus Index Timeout for 1.6.x Embedding Volume
2026-06-10 14:05:54 -04:00
Danny Avila
d91cec2101
🤗 ci: Cache and Authenticate HF Model Downloads in GitNexus Index (#13653) 2026-06-10 09:21:35 -04:00
Danny Avila
fe7bf39d9f
🗜️ ci: Cache Dependencies and Builds in Cache Integration Tests (#13652)
* 🗜️ ci: Cache Dependencies and Builds in Cache Integration Tests

Port the node_modules and package-dist caching pattern from
backend-review.yml to cache-integration-tests.yml, which ran a full
npm ci (~72s) and rebuilt data-provider, data-schemas, and api on
every run. Cache keys are identical to backend-review.yml so the two
workflows share entries. Drops setup-node's npm tarball cache,
superseded by the node_modules restore, matching backend-review.yml.

* 🗜️ ci: Exercise Warm-Cache Path
2026-06-10 09:09:29 -04:00
Danny Avila
70f7450bab
🪟 ci: Shard Windows Frontend Unit Tests (#13651)
* 🪟 ci: Shard Windows Frontend Unit Tests

Mirror the 4-way jest sharding the Ubuntu frontend test job already
uses onto the Windows job, which currently runs the whole client suite
in a single 20-minute job. Also drops the `--verbose` flag, which npm
consumed itself (it preceded `--`) and only raised npm's own log level.

* 🪟 ci: Trigger Frontend Tests on Workflow Changes
2026-06-10 09:00:28 -04:00
Danny Avila
0bd1a7350f
👷 ci: Add API runtime smoke (boot the production image) to docker-smoke (#13605)
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
GitNexus Index / index (push) Waiting to run
GitNexus Index / post-index (push) Blocked by required conditions
* 👷 ci: Add API runtime smoke (boot the production image) to docker-smoke

The docker-smoke workflow only built the `client-package-build` stage and
never booted the runtime, so it couldn't catch the class of regression that
recently took production down: the api tsdown bundle externalizes runtime
deps that, after `npm ci --omit=dev`, were missing from the image
(`Cannot find module 'get-stream'`).

- Add an `api-runtime-smoke` job that builds the real production image
  (final `api-build` stage, `npm ci --omit=dev`), then:
  1. loads the @librechat/api bundle's full require graph in the pruned
     image (deterministic, no DB) — fails on any missing/ESM-incompatible
     runtime dependency.
  2. boots the actual entrypoint and asserts no module-load crash (the
     server loads its require graph before connecting to Mongo, so this
     surfaces without a database).
- Expand triggers to include `packages/api/**`, `packages/data-schemas/**`,
  and `api/package.json` (previously a packages/api change only triggered
  this via a root lockfile change, and even then only built the client stage).
- Add gha build cache + concurrency cancellation to bound CI cost.

* 👷 ci: Address Codex review — boot smoke against real Mongo + crash detection

- Boot the production image against a real MongoDB container with the env
  the server needs, so the *entire* require graph loads. `api/db/connect.js`
  throws at module scope without `MONGO_URI` and is imported before
  models/services/routes, so the previous no-env boot exercised almost none
  of the legacy API graph. (Codex finding 2)
- Gate on `/health` returning 200 AND the container staying alive, failing on
  any container exit. A non-module startup crash (ReferenceError, SyntaxError,
  bad config) now fails the smoke instead of slipping past a missing-module
  grep. (Codex finding 3)
- Expand trigger from `api/package.json` to `api/**`, since the image copies
  the whole `api/` tree and runs `node server/index.js`. (Codex finding 1)

* 👷 ci: Address Codex round 2 — poll /readyz + cover all image inputs

- Poll /readyz instead of /health. /health returns 200 at app.listen, but
  initializeMCPs() and checkMigrations() run *after* listen and process.exit(1)
  on failure; /readyz only returns 200 once serverReady is set after those
  complete. So post-listen startup crashes now fail the smoke too. (finding A)
- Expand triggers to every source tree copied into the production image:
  client/**, config/**, skill/** (the final stage copies client/dist, config,
  and skill). (finding B)
2026-06-08 18:44:52 -04:00
Danny Avila
cb6dbc8f60
refactor: Migrate @librechat/client build to tsdown (#13596)
*  refactor: Migrate @librechat/client build from Rollup to tsdown

Mirrors the data-schemas migration. Replaces Rollup (rpt2 + postcss) with
tsdown (rolldown + oxc); the package build drops from tens of seconds to ~0.3s.

- Emit isolated-declaration .d.ts via oxc (dts.oxc) and enforce
  isolatedDeclarations in tsconfig for editor DX (source made clean: explicit
  export type annotations added across src, no `any`).
- Extract component CSS to dist/style.css so the CJS output stays valid
  CommonJS (the prior postcss runtime-injection produced an ESM import in the
  CJS bundle that breaks jest/require). Imported once in the client app entry;
  Vite bundles it for the app.
- Repoint package.json to dual .mjs/.cjs + .d.mts/.d.cts and add ./style.css
  and ./package.json exports.
- Update CI build-cache keys to hash tsdown.config.mjs; remove rollup.config.js.

* 🔧 chore: address Codex review on client tsdown migration

- Add tsdown.config.mjs to turbo.json build `inputs` so changes to the new
  bundler config invalidate the Turbo cache (the shared inputs only listed the
  rollup configs). Also covers the already-migrated data-schemas.
- Name the memoized default export (ControlComboboxMemo) instead of the
  codefix-generated `_default_1`, for clearer stack traces / grepping.
2026-06-08 11:20:03 -04:00
Danny Avila
fbc51f6e66
refactor: Migrate data-provider Build to tsdown (split tsc dts) (#13597)
Replace the Rollup + `rollup-plugin-typescript2` build with a split
pipeline: tsdown (rolldown) bundles the JS in ~0.2s, and plain `tsc`
emits the declarations to `dist/types` (~2s). Full cold build drops from
~9.2s to ~2.5s (~3.6x) with zero source changes.

Unlike data-schemas, the fast oxc/isolated-declarations dts path isn't
viable here: the package's 78 exported zod schemas produce 374
`isolatedDeclarations` errors (TS9013/TS9038) and a `z.ZodType<T>`
annotation would break the 76 downstream `.extend`/`.shape`/`.pick`
usages. Plain `tsc` keeps the rich zod types intact, and since dts was
never the bottleneck (rollup-plugin-typescript2 was), the win stands.

- dts stays unbundled in `dist/types/` — identical to the prior output,
  so the existing deep `dist/types` imports and the exports `types`
  paths are unchanged.
- ESM output renamed `index.es.js` -> `index.mjs` (via the exports map;
  no consumer hardcodes the old path). cjs/types paths unchanged.
- `./react-query` now emits a real cjs build + types — the exports map
  already promised them, but Rollup only ever built the esm file.
- Kept `rollup` + the plugins used by `server-rollup.config.js`
  (the `rollup:api` server-bundle smoke test in backend-review.yml);
  removed only the deps used solely by the deleted `rollup.config.js`.
- Repointed CI build-cache keys from `rollup.config.js` to
  `tsdown.config.mjs`.
2026-06-08 11:09:16 -04:00
Danny Avila
6bc75d24c8
️ refactor: Migrate @librechat/api build to tsdown (#13595)
* ️ refactor: Migrate @librechat/api build to tsdown

Replace Rollup with tsdown (rolldown + oxc isolated-declarations) for the
@librechat/api package build, mirroring the merged data-schemas migration.

- Add tsdown.config.mjs (cjs output, oxc dts, externalize all bare deps,
  bundle first-party `~/` + relative imports)
- Annotate exports for isolatedDeclarations (codefix-driven). Collapse the
  tokens.ts model->token maps to Record<string, Record<string, number>> and
  switch validation.ts's runtime `files` field from z.any() to z.unknown()
  so no explicit `any` is introduced
- Repoint package.json main/types/exports to tsdown's .cjs/.d.cts output
- Add src/telemetry.ts entry shim so the two index.ts entries don't collide
  in oxc's flat dts output (stable dist/telemetry.{cjs,d.cts})
- Delete rollup.config.js

Build time ~36s -> ~0.5s. No runtime behavior change: 5712 unit tests pass,
both entries load via require(), legacy /api consumes them unchanged.

* 👷 ci: Hash packages/api/tsdown.config.mjs in build-api cache keys

The build-api cache keys hashed `packages/api/server-rollup.config.js`,
which never existed (api used `rollup.config.js`, now removed) — a copy-paste
artifact from the data-provider key that matched no file. Replace it with the
new `packages/api/tsdown.config.mjs` so edits to the build config (entry,
format, externals) bust the api build cache, matching the data-schemas key.
2026-06-08 10:54:48 -04:00
Danny Avila
192703e041
perf: Migrate data-schemas Build to tsdown with isolatedDeclarations (#13578)
*  perf: Migrate data-schemas Build to tsdown with isolatedDeclarations

Replace Rollup with tsdown (rolldown + oxc) for @librechat/data-schemas. With the source made isolatedDeclarations-clean, oxc emits .d.ts without tsc, dropping the package build from ~5.8s to ~0.8s (~7x).

- Annotate exported model/method factories for isolatedDeclarations (TypeScript's fixMissingTypeAnnotationOnExports codefix plus hand-authored interfaces); type the ~44 mongoose `any`s and add an explicit PromptMethods interface (previously its declaration was silently dropped by the Rollup build).
- Repoint package.json exports/main/module/types to tsdown output; drop rollup config.
- Config lives in tsdown.config.mjs (native ESM) so CI without a TS-config loader can build it; bundle `dotenv` so the package stays self-contained for its env-loading side effect.
- Fix a latent token `metadata` mismatch the accurate types surfaced: widen TokenCreate/UpdateData inputs to accept plain objects, flatten OAuthMetadata at the api boundary.
- Update mongoMeili/aclEntry specs to the precise model types; drop redundant terser minification from data-provider's library build.

All data-schemas tests pass; api builds clean against the new output.

* 🔧 chore: Hash tsdown.config.mjs in data-schemas CI build-cache keys

The data-schemas build switched from rollup to tsdown, but the build-data-schemas / build-api cache keys in backend-review, config-review, and playwright-mock still hashed the (now-deleted) rollup.config.js. Hash tsdown.config.mjs instead so a config-only change invalidates the cached dist/api builds. (Found by Codex review.)

* 🔧 chore: Replace deprecated tsdown `external` with `deps.neverBundle`

tsdown 0.22 deprecated the top-level `external` option in favor of `deps.neverBundle`. Migrate the data-schemas config and set `deps.onlyBundle: false` to silence the (intentional) dotenv bundling hint. Build output and externalization are unchanged — dotenv bundled, all peers external.
2026-06-07 21:40:48 -04:00
Danny Avila
7554f2e9e9
🩹 fix: Bump GitNexus to 1.6.5 and Fail-Soft the PR Index Job (#13569)
* 🩹 fix: Bump GitNexus to 1.6.5 and Fail-Soft the PR Index Job

The GitNexus Index workflow began failing on most PRs with
"Analysis failed: Maximum call stack size exceeded". Root cause is in
the pinned gitnexus@1.5.3 CLI: pipeline.js does
`deferredWorkerCalls.push(...chunkWorkerData.calls)`, and once a chunk
yields more extracted calls than V8's argument-count limit (~125k on
this repo) the spread-push throws a RangeError. It is deterministic on
repo size, not flaky — LibreChat simply grew past the threshold, so it
fails "more often" as more branches cross it. Stack-size flags don't
help; it's an arg-count limit, not stack depth.

gitnexus@1.6.5 refactored that code path (the .calls spread-pushes are
gone) and indexes this repo cleanly. Bump the indexer, the deploy image
tag/build-arg, and the Dockerfile default in lockstep (an index written
by 1.6.5 must be served by a 1.6.5 server), and move the co-pinned
@ladybugdb/core to 0.16.1 to match.

Also make the index job fail-soft on pull_request events so a future
tool-internal crash degrades gracefully instead of red-X'ing PRs. Push,
dispatch, and /gitnexus command runs still fail loudly, keeping the
deploy-gating and completion-comment logic correct.

* 🐳 fix: Unbreak the GitNexus Deploy Image for 1.6.5

Addresses two issues in the deploy image surfaced after the 1.6.5 bump:

- The image build's lbug-adapter patch grepped
  dist/mcp/core/lbug-adapter.js for "LOAD EXTENSION fts", but in 1.6.5
  that file is a shim re-export and the FTS load moved to
  dist/core/lbug/lbug-adapter.js. The grep would fail the build on the
  next image rebuild. The patch is also obsolete: 1.6.5 loads the vector
  extension itself via loadVectorExtension. Removed the patch step.

- The image installed only gitnexus, letting @ladybugdb/core resolve
  freely via gitnexus's ^0.16.1 range while the index workflow pins
  0.16.1 exactly. Pin the native DB in the image too (nested under
  gitnexus so install-extensions.js keeps resolving it), restoring the
  intended indexer/server lockstep.
2026-06-07 08:03:28 -04:00
Danny Avila
265d660076
👷 ci: Type-check the Client Workspace (#13560)
The `client/` workspace was never type-checked: the existing typecheck
job only covered `packages/` and `api/`, and Vite/esbuild transpiles
without type-checking, so type errors shipped through every CI gate.

- Add a `typecheck` job to frontend-review.yml running `tsc --noEmit`
  over `client/` (zero tolerance), reusing the data-provider +
  client-package build artifacts. Triggers on `client/**`,
  `packages/client/**`, `packages/data-provider/**`.
- Fix all 168 pre-existing client type errors this surfaced (source +
  tests), including genuine latent bugs:
  - `getFileConfig()` was typed as merged `FileConfig`, but the server
    returns the raw config that `mergeFileConfig()` consumes (`TFileConfig`).
  - SidePanel/Agents `Retrieval`/`ImageVision` were bound to `AgentForm`
    but use the assistants `Capabilities` enum → `AssistantForm`.
  - `useSearchResultsByTurn` read a `sources` field its type lacked.
  - Removed orphaned dead code: `Artifacts/Mermaid.tsx` (imported a
    never-installed dep) and dead barrel re-exports (`./Plugins`, `./MCPAuth`).
- Narrow `client/tsconfig.json` to the client app (drop `../e2e` and
  `../config/translations`, which reference backend/tooling modules) so
  the gate's scope matches its trigger.

No `any`/`@ts-ignore`/`as unknown as`. Localized newly-surfaced strings.
2026-06-06 18:40:31 -04:00
Danny Avila
bfb6b224d2
🔧 chore: Update ESLint config, Import Sorting script, Test Sharding, Bump @librechat/agents (#13552)
* 🔧 chore: Update ESLint config, add import sorting script, Test Sharding, Bump `@librechat/agents`

* Change 'no-nested-ternary' rule from 'warn' to 'error' in ESLint config
* Add new scripts for sorting imports in the project
* Update lint-staged configuration to include import sorting
* Modify GitHub Actions workflows to support sharding for unit tests

* chore: remove nested ternary expressions

* refactor: Extract scale multiplier logic into a separate function in CircleRender component
* refactor: Simplify auto-refill rendering logic in Balance component for better readability
* refactor: Improve width style handling in DataTable components for clarity and maintainability

* chore: remove CircleRender component

* delete: Remove CircleRender component as it is no longer needed in the project

* chore: Bump @librechat/agents to version 3.2.31 and update Node.js engine requirement

* Update @librechat/agents dependency from 3.2.2 to 3.2.31 in package-lock.json, api/package.json, and packages/api/package.json
* Change Node.js engine requirement from >=20.0.0 to >=24.0.0 in @librechat/agents

* chore: Add import sorting check to ESLint CI workflow

* Implement a new job in the GitHub Actions workflow to verify import ordering on changed files.
* The job checks for changes in specific file types and reports any import order drift, providing instructions for local fixes.
2026-06-06 12:31:55 -04:00
Danny Avila
a1bfa3b298
🎭 test: Run Mock E2E Suite Through createRun With In-Process Fake Model (#13508)
* 🎭 test: Run Mock E2E Suite Through createRun With In-Process Fake Model

Replace the standalone HTTP mock LLM server with an in-process fake model
injected into the real createRun -> Run.create pipeline via
run.Graph.overrideTestModel, so the mock suite exercises the agents
integration end-to-end without a live provider or a separate server.

- Bump @librechat/agents to 3.2.2 for the FakeChatModel/createFakeStreamingLLM exports
- Add an env-gated applyTestRunHook seam in packages/api createRun (no /api changes)
- Add e2e/setup/fake-model.js to drive default replies + the skill-authoring tool-call flow
- Drop the mock-llm webServer from playwright.config.mock.ts and set LIBRECHAT_TEST_RUN_HOOK

* 🧹 test: Retire Standalone Mock LLM Server From E2E Recorder

Migrate the `--profile=mock` recorder onto the same in-process fake model
as the Playwright mock suite, then delete the now-unused HTTP mock server
so the fake-LLM logic lives in a single place.

- Point record.js mock profile at the fake model via LIBRECHAT_TEST_RUN_HOOK
- Remove the mock-llm-server spawn/wait and MOCK_LLM_PORT plumbing from record.js
- Delete e2e/setup/mock-llm-server.js (e2e/setup/fake-model.js is now the only source)
- Update e2e/README.md to describe the in-process fake LLM

* 🏷️ ci: Rename Playwright Mock E2E Check to Playwright E2E Tests
2026-06-04 08:33:28 -04:00
Atef Bellaaj
86fe79c37d
🔗 feat: Add Granular Access Control to Shared Links via ACL System (#13051)
* feat: Add granular access control to shared links via ACL system

* fix(shared-links): preserve isPublic on failed migration grants

Transient ACL failures during auto-migration permanently stranded
links — $unset ran unconditionally, removing the legacy flag that
triggers retry. Now only $unset isPublic after all grants succeed.

* fix(config): skip isPublic unset for failed ACL grants

Bulk migration unconditionally removed isPublic from all links,
even those whose ACL writes failed. Failed links then lost the
legacy marker needed for auto-migration retry. Now tracks failed
link IDs per-batch and excludes them from the $unset step.

Also adds sharedLink to AccessRole resourceType schema enum —
was missing, only worked because seedDefaultRoles uses
findOneAndUpdate which bypasses validation.

* ci(config): add jest config and PR workflow for migration tests

config/__tests__/ specs depend on api/jest.config.js module
mappings but had no dedicated runner. Adds config/jest.config.js
extending api config with absolutized paths, npm test:config
script, and a GitHub Actions workflow triggered by changes to
config/, api/models/, api/db/, or packages/ ACL code.

* fix(permissions): honor boolean sharedLinks config

SHARED_LINKS has no USE permission, so boolean config produced
an empty update payload — gate conditions only matched object
form, making `sharedLinks: false` a no-op on existing perms.

* fix(share): resolve role before creating shared link

Role lookup between create and grant left an orphaned link
without ACL entries if getRoleByName threw — retry then hit "Share already exists" with no recovery path.

* fix: Restore Public ACL Access Checks

* fix: Type Public ACL Lookup

* fix: Preserve Private Legacy Shared Links

* chore: Promote Shared Link Permission Migration

* fix: Address Shared Link Review Findings

* fix: Repair Shared Link CI Follow-Up

* fix: Narrow Shared Link Mongoose Test Mock

* fix: Address Shared Link Review Follow-Ups

* fix: Close Shared Link Review Gaps

* fix: Guard Missing Shared Link Permission Backfill

* test: Add Shared Link Mock E2E

* test: Stabilize Shared Link Mock E2E

---------

Co-authored-by: Danny Avila <danny@librechat.ai>
2026-06-03 14:17:17 -04:00
Danny Avila
d680763db3
🧭 ci: Use System Chrome for Mock E2E (#13481)
Some checks are pending
GitNexus Index / index (push) Waiting to run
GitNexus Index / post-index (push) Blocked by required conditions
Sync Locize Translations & Create Translation PR / Sync Translation Keys with Locize (push) Waiting to run
Sync Locize Translations & Create Translation PR / Create Translation PR on Version Published (push) Blocked by required conditions
Sync Helm Chart Tags / Ignore non-main push (push) Waiting to run
Sync Helm Chart Tags / Sync chart tags (push) Waiting to run
2026-06-02 22:07:10 -04:00
Teresa Blanco
b45e4aeae5
🎭 feat: Add Credential-Free Playwright Smoke Suite with a Local Mock LLM (#13472)
* 🧪 feat: add e2e playwright tests

* 🧪 feat: Add Playwright Recording Harness

* test: fix mock playwright config

* test: harden mock e2e environment

* test: preserve mock dotenv secrets

* test: harden mock isolation setup

* ci: cache mock e2e builds

* test: harden e2e cache and recorder checks

* test: preserve data-provider exports in oauth route test

* test: isolate mock auth logout state

* test: allow isolated logout smoke setup

* test: prepare logout smoke auth via api

* test: isolate oauth route module mock

---------

Co-authored-by: Danny Avila <danny@librechat.ai>
2026-06-02 16:36:39 -04:00
Danny Avila
a7cfbccc50
🌎 ci: Fix Locize Sync CDN Mode (#13465) 2026-06-01 21:47:46 -04:00
Danny Avila
67398e0a69
🏷️ ci: Fix Helm Tag Sync Git Auth (#13460) 2026-06-01 18:32:38 -04:00
Danny Avila
e21146c044
⛑️ ci: Fix Helm Tag Sync Workflow Planning (#13451) 2026-06-01 11:44:36 -04:00
Danny Avila
fb282a2afa
🐳 chore: Upgrade Docker Builds To Node 24 (#13448)
* chore: upgrade docker builds to node 24

* test: avoid array at in telemetry spec
2026-06-01 10:03:18 -04:00
Danny Avila
d5a2d3b26b
🏷️ ci: Sync Helm Chart Tags (#13446)
* ci: Sync Helm chart tags

* ci: Address Helm tag sync review
2026-06-01 08:43:32 -04:00
Danny Avila
7ab925498f
🐢 ci: Raise test-packages-api timeout to 20 min (#13326)
The `Tests: @librechat/api` job in `backend-review.yml` had
`timeout-minutes: 10`. Recent runs have started getting cancelled
right at the 10-minute mark with tests still actively passing — the
log streams `PASS …` lines through the final second before
`The operation was canceled.` fires.

Looking at recent runs in the wild:
- Warm `ubuntu-latest` runners: suite finishes in ~4–5 min.
- Cold/busy runners: 10+ min. No headroom = job killed mid-suite.

The suite has grown over the last few months (OpenTelemetry, MCP
OAuth, Bedrock, the Operational Prometheus Metrics work from #13265,
plus the agents/responses harness). 10-minute ceiling that worked
before is now a tail-latency cliff.

Bump to 20 minutes. Still well below the runner's hard 6-hour cap,
generous enough to absorb runner variance, and a stuck/genuine hang
will still get killed eventually rather than billing forever.

No code changes; one-line config bump.
2026-05-26 17:30:04 -07:00
Danny Avila
ee2120598b
🌐 chore: Guard Locize Reviewer Request (#13286) 2026-05-24 08:37:31 -04:00
Danny Avila
a7c18f54f7
🪤 chore: Prevent CI Path Argument Injection (#13284)
Some checks are pending
Docker Dev Images Build / build (Dockerfile, librechat-dev, node) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile.multi, librechat-dev-api, api-build) (push) Waiting to run
GitNexus Index / index (push) Waiting to run
GitNexus Index / post-index (push) Blocked by required conditions
Sync Locize Translations & Create Translation PR / Sync Translation Keys with Locize (push) Waiting to run
Sync Locize Translations & Create Translation PR / Create Translation PR on Version Published (push) Blocked by required conditions
2026-05-23 23:06:04 -04:00
Danny Avila
9799f7e698
🎨 ci: Check Prettier Formatting Drift on Package Changes (#13282)
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
GitNexus Index / index (push) Waiting to run
GitNexus Index / post-index (push) Blocked by required conditions
Adds a `Run Prettier --check on changed files` step to the existing
`eslint-ci.yml` workflow. Same path filter (api/**, client/**,
packages/**), same changed-files detection, runs after the ESLint step.

## Why

Today there is no `prettier --check` in CI — only the local
`lint-staged` pre-commit hook runs `prettier --write`. When a PR is
merged with the hook bypassed (e.g. GitHub UI edit-and-merge, or
`git commit --no-verify`), a file can land in a non-prettier-canonical
state and nobody notices. The next contributor who stages an unrelated
change in that file then ends up with a "drive-by" prettier diff in
their PR.

`packages/api/src` had 14 such files accumulated; #13281 fixes the
existing drift. This PR closes the gap so it doesn't regrow.

## What the step does

- Detects changed JS/TS files under `api/**`, `client/**`, or
  `packages/**` against the PR base.
- Runs `npx prettier --check $CHANGED_FILES`.
- On failure, prints a `::error::` annotation telling the contributor
  how to fix it locally (`npx prettier --write <files>`).

Same one-step shape as the existing ESLint check — no extra workflow
file, no extra `npm ci`, no extra checkout.

## Ordering note

#13281 (`chore: prettier --write packages/api/src`) should land first
so the existing drift is cleared. After both PRs merge, the
pre-commit hook + this CI check together prevent drift from
re-accumulating.

## Test plan

- [x] `npx js-yaml .github/workflows/eslint-ci.yml` validates.
- [ ] CI green on this PR itself (touches only `.github/workflows/`,
  which the path filter includes, so the workflow runs on itself).
- [ ] After merge: a synthetic PR introducing prettier drift should
  fail the new step with the diagnostic message.
2026-05-23 17:53:11 -04:00
Danny Avila
202a17987b
🚦 ci: Enforce ESLint on Package Changes (#13280) 2026-05-23 17:05:41 -04:00
Danny Avila
6d6ea08da4
🆔 feat: Built-in Build Metadata for Support Triage (#12756)
* 🏗️ refactor: Derive App Version from Root package.json + Add buildInfo Schema

The hardcoded `Constants.VERSION` in `data-provider` is now replaced at
rollup build time via `@rollup/plugin-replace`, sourcing from the root
`package.json` so version bumps are a single-file change.

Adds the shape needed by the rest of the series:
- `interface.buildInfo` boolean flag (default `true`) — lets self-hosters
  opt out of exposing commit/branch/date.
- `buildInfo` on `TStartupConfig` — commit/commitShort/branch/buildDate.
- `SettingsTabValues.ABOUT` — new settings tab enum value.

Ref: https://github.com/danny-avila/LibreChat/issues/12406

* 🛠️ feat: Add Build Metadata Resolver and Expose via /api/config

Adds `resolveBuildInfo()` in `@librechat/api` that surfaces commit SHA,
branch, and build date from (in order) `BUILD_*` env vars, then local git
metadata. Result is cached per-process.

`/api/config` includes a `buildInfo` field on both authenticated and
anonymous responses when `interface.buildInfo !== false` and at least one
resolver field is populated. Omitted entirely otherwise.

Designed so pre-built Docker images carry metadata via build-arg while
source installs pick it up from `.git` — no manual version tracking.

Ref: https://github.com/danny-avila/LibreChat/issues/12406

* ℹ️ feat: Add Settings → About Panel with Diagnostics Copy

New Settings tab that renders the running build's version, commit (short
SHA), branch, and build date in a monospaced block alongside a "Copy
diagnostics" button that emits a preformatted text blob for pasting into
support issues.

Tab is hidden when `interface.buildInfo` is set to `false`. Reads from
`startupConfig.buildInfo` provided by `/api/config`.

Ref: https://github.com/danny-avila/LibreChat/issues/12406

* 🐳 ci: Inject BUILD_COMMIT/BRANCH/DATE into Docker Images

Adds optional `BUILD_COMMIT`, `BUILD_BRANCH`, `BUILD_DATE` ARGs to both
`Dockerfile` and `Dockerfile.multi`, wired as `ENV` vars in the runtime
stage so the backend's `resolveBuildInfo` picks them up.

All image-publishing workflows (`tag`, `main`, `dev`, `dev-branch`,
`dev-staging`) now compute `${github.sha}`, `${github.ref_name}`, and a
UTC timestamp, then pass them to `docker/build-push-action` as
`build-args`.

Defaults are empty — non-CI builds (local `docker build`) still work,
and the backend falls back to local `.git` metadata if ARGs aren't set.

Ref: https://github.com/danny-avila/LibreChat/issues/12406

* 📝 docs: Direct Bug Reporters to Settings → About for Version Info

The previous instructions (`docker images | grep librechat`,
`git rev-parse HEAD`) only worked for a subset of deployments and
rarely produced a commit SHA for users pulling pre-built images.

Point users to the new in-app Settings → About panel's
"Copy diagnostics" button, which captures version, commit, branch,
build date, and user agent in a single preformatted block. Fallback
instructions preserved for older installs.

Ref: https://github.com/danny-avila/LibreChat/issues/12406

* 🐳 fix: Move BUILD_* ENV to End of Docker Stages to Preserve Layer Cache

Per-commit BUILD_COMMIT/BUILD_DATE changes were being promoted to ENV
before `npm ci` / `npm run frontend` (single-stage) and before
`npm ci --omit=dev` (multi-stage api-build), which invalidated the cache
for every subsequent layer on every CI run.

Move the ARG/ENV block below the heavy install and build steps in both
Dockerfiles. Metadata is still available in the runtime image but no
longer busts layer reuse.

Addresses codex review on #12756.

* 🔧 fix: Propagate interface.buildInfo=false to Unauthenticated /api/config

The unauthenticated branch of `/api/config` was emitting an `interface`
object only when `privacyPolicy` or `termsOfService` was set, which
meant an admin's explicit `interface.buildInfo: false` opt-out was never
visible to anonymous/guest clients. `Settings.tsx` gates the About tab
on `startupConfig?.interface?.buildInfo !== false`, so a missing field
fell through as "enabled" for those clients.

Include `interface.buildInfo: false` in the unauth payload whenever it's
explicitly disabled. Keep the implicit default (true) absent to preserve
the minimal-unauth-payload convention.

Addresses codex review on #12756.

* 🔀 ci: Trigger Dev Image Workflows on Root package.json + Dockerfile Changes

The baked `Constants.VERSION` now reads from the root `package.json` via
rollup-plugin-replace, but the `dev-images.yml` and `dev-branch-images.yml`
path filters only matched `api/**`, `client/**`, `packages/**`. A release
commit that only bumps root `package.json` would not trigger a rebuild,
leaving `latest` dev images with stale Footer/About version metadata.

Include `package.json`, `package-lock.json`, and both Dockerfiles in the
path filters so dependency changes (lockfile rebuilds) and image build
tweaks also rebuild dev images.

Addresses codex review on #12756.

* 🧽 fix: Harden About Panel Lifecycle, A11y, and Loading Gate

Review follow-ups on #12756:

- #1 timer leak: stash the copy-state `setTimeout` in a ref and clear it
  from a `useEffect` cleanup so unmounting the Settings dialog mid-toast
  doesn't fire `setCopied(false)` on an unmounted component.
- #3 flash of About tab: gate `aboutEnabled` on `startupConfig != null`
  so the tab stays hidden until `/api/config` returns. For admins who
  disabled `interface.buildInfo`, the tab no longer briefly appears and
  vanishes on page load.
- #6 aria-live placement: move the live region off the interactive
  button onto a dedicated `<span role="status" aria-live="polite">` so
  screen readers announce the copied state, not the full button content
  on every re-render.
- #2 missing coverage: add `About.spec.tsx` exercising populated/empty
  buildInfo rendering, invalid-date handling, diagnostics clipboard
  payload, copy-state toggling, unmount cleanup, and the live region.

*  perf: Eagerly Resolve Build Info at Module Load

Review follow-up #4 on #12756: `resolveBuildInfo()` calls `execFileSync`
with a 2s timeout on source installs without `BUILD_*` env vars. Paying
this cost on the first HTTP request blocks the event loop mid-flight.

Call `resolveBuildInfo()` once at config route module load so the
resolver's cache is warm before any request arrives. Docker images with
the BUILD_* env vars set sidestep the git path entirely, so this only
affects the edge case of source installs.

* 📝 docs: Document rollup Version Placeholder Contract

Review follow-ups #5 / #8 on #12756. The `__LIBRECHAT_VERSION__`
placeholder relies on a substring replacement rule that only works
because the token appears inside a string literal, and the substitution
only runs during `npm run build:data-provider`.

- Expand the `Constants.VERSION` JSDoc to spell out that consumers read
  the placeholder through the built dist bundle; source-level test
  imports would see the raw placeholder.
- Add a NOTE above the rollup `replace` config warning future
  contributors not to repurpose the token as a bare identifier without
  switching to a quoted replacement value.

Non-functional; prevents future contributors from stepping on a subtle
constraint.

* 🪪 fix: Only Toast "Copied" When Clipboard Copy Actually Succeeds

Codex R5 on #12756. `copy-to-clipboard` returns a boolean indicating
whether the underlying `execCommand('copy')` / fallback prompt actually
wrote to the clipboard. The previous handler flipped to the "Copied"
state unconditionally, which in hardened browsers or when the
permission prompt is dismissed would mislead users into filing bug
reports without the diagnostics blob attached.

Gate the state/timer/live-region on the boolean return; silently no-op
on failure rather than showing a false positive. Adds a test asserting
the button label stays at "Copy diagnostics" when the clipboard call
fails.

* 🐳 fix: Derive main image metadata from checkout

* 🪪 fix: Keep About enabled until disabled

*  test: Avoid literal Settings mock text

* 🧱 refactor: Rename Build Info Module
2026-05-23 09:41:13 -04:00
Danny Avila
21574f02ca
🛡️ chore: Harden CI Supply Chain Workflows (#13090)
* chore: harden CI supply chain workflows

* chore: address CI hardening review feedback

* chore: tighten GitNexus dispatch hardening

* chore: use app token for Locize PR automation

* chore: use dedicated token for Locize PR automation
2026-05-18 16:55:25 -04:00