mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-10-22 18:13:27 +00:00
5298 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
49e1e32f3a
|
🧺 fix: Funnel MCP Catalog Work Through a Shared Gate (#15429)
* 🧯 fix: Bound MCP Catalog Work Across Requests * 🛑 fix: Reject Saturated MCP Discovery * fix: fairly bound MCP catalog work * test: type catalog snapshot mock arguments * test: assert snapshot deadline forwarding * fix: harden MCP catalog scheduling |
||
|
|
2e2c29e10d
|
🫀 fix: Expire Aborted Background Tool Leases (#15428)
* fix: Bound Background Tool Producer Heartbeats * fix: fence timed-out delivery retirement |
||
|
|
6be3f798fc
|
🪩 style: Shimmer Live Reasoning Labels and Timers (#15431)
* ✨ style: Shimmer the Thinking Label and Elapsed Timer While Generating The label above streaming reasoning and the elapsed-time reading beneath a streaming response both sat static while every other in-flight indicator on the row — a running tool call's label, a running skill, a pending question — carried the shimmer. Give them the same treatment so "in flight" reads the same way everywhere. Both are gated on state that is already established: - `Reasoning` shimmers only while `effectiveIsSubmitting && isLast`. Settled thoughts, an older row the reader paged to, and reasoning overtaken by later content all keep the plain label. The legacy `Thinking` component and `ThinkingLabel` (the detached-subagent marker) do not opt in. - `Elapsed` only ever renders under the streaming latest row at the newest sibling position (`shouldShowElapsed`), so there is no settled reading for the animation to misrepresent. Completed tool-call durations go through `getRunStepDurationLabels` and are untouched. `.shimmer` and Tailwind's `animate-in` entrance both drive `animation-name`, and `.shimmer` — plain CSS after `@tailwind utilities`, equal specificity — would win and silently kill the generated-label slide-in. The two therefore sit on nested spans, in the order that is safe for `background-clip: text`: the transform on the ancestor, the clipped background on the descendant. * 🎨 fix: Drive the Shimmer From Theme Roles and Honor Reduced Motion Two review findings, both against the shared `.shimmer` utility rather than this PR's call sites — but this PR adds two more consumers of it, one of which sweeps continuously for the whole generation, so they land here. Theme roles. The gradient hard-coded near-black and near-white and selected the second with `.dark`, so a stored or custom theme that restated its text colors got a shimmer that ignored them. Themes are applied as bare RGB triplets on the document element; the sweep now reads `--shimmer-base` / `--shimmer-dip` (with the established `-alpha` companions the border roles already use) and falls back to `--text-primary` when a theme states no base of its own. The fallback is resolved at the label, not in the `:root` declaration — `var()` substitutes where it is declared, so seeding `--shimmer-base: var(--text-primary)` on `html` would have frozen the default and ignored every theme scope applied further down. Dark still declares its base outright: that value is dimmer than the dark theme's text color, and restating it as `--text-primary` would have brightened every shimmer. Default light and dark are preserved: rendered old against new at a frozen sweep position, 144 of 137,280 pixels differ by a single 1/255 channel step, which is the alpha-syntax rounding path. A theme that sets only `--text-primary` now colors the sweep with no shimmer-specific override. Reduced motion. `.shimmer` was the one custom animation in `style.css` with no `prefers-reduced-motion` guard — `.t-icon-swap`, `.t-avatar`, `.scroll-animation-*` and `[data-lc-fade]` all carry one. Stopping the animation alone is not a fallback: the background settles at position 0 and shows only half the sweep, stranding the tail of the label in the near-invisible dip. The guard drops the gradient and hands the glyphs back to the color they would otherwise have had. * 🎨 fix: Register the Shimmer Stops and Keep the Sweep Whole Codex round 2, on the previous round's fixes. Register the stops. Moving the constants into CSS variables did not make them theme-owned: `applyResolvedTheme` writes only `themeColorTokens`, so nothing a theme declares could reach `--shimmer-base`. Dark was the case that mattered — `style.css` declares a `.dark` base outright, which a theme can only outrank by having the property applied to the document element — so a custom dark theme still got the stock white sweep. `rgb-shimmer-base` and `rgb-shimmer-dip` now sit in the registry beside the text roles, seeded with today's values in both modes; `registry.spec` already enforces light/dark key parity. The opacities stay in CSS as `--shimmer-*-alpha`, the way the border roles keep `--border-*-alpha`. This narrows the earlier claim: a *versioned* theme resolves every default, so it now states its own sweep rather than inheriting `--text-primary`. The `--text-primary` fallback still serves plain CSS and legacy partial `themeRGB` themes, which apply only the keys they name. Keep the sweep whole. `key` remounts the label row so the entrance replays, restarting the sweep with it; reasoning labels revise on a 3s default (`DEFAULT_UPDATE_INTERVAL_MS`) against a 4s sweep, so it never completes a cycle. Keying only when an entrance will actually play leaves an un-animated label — smooth streaming off, or reduced motion — with a stable identity and an unbroken sweep, instead of a bare snap with nothing to cover it. Not adopted: keeping the shimmer element stable and replaying the entrance inside it. The clipped element paints the glyphs itself, so a descendant's opacity cannot fade them — rendered at entrance t=0, the current nesting is correctly invisible while the swapped one sits fully lit through its own fade. The remaining restart happens only underneath a deliberate entrance animation. * 🎨 fix: Carry Legacy Themes Onto the Registered Shimmer Stops Codex round 3. Registering the stops migrated versioned themes but not stored or environment `themeRGB` ones: `ThemeProvider` routes those to the legacy `applyTheme` adapter, which bypasses `resolveTheme` and applies only the keys a theme names. A stored theme has no shimmer keys, so it kept the stock sweep while the rest of its palette moved — and in dark the CSS cannot recover on its own, because `.dark` declares a base outright and the `--text-primary` fallback never runs. `mapColors` already carries legacy themes across a token they predate: `--surface-composer-hover` derives from `rgb-surface-hover` when absent. The shimmer base follows the same shape, deriving from `rgb-text-primary` — which is what it already resolves to in light, so only dark changes. The dip has no legacy counterpart and stays at its default; it is the faded half of the sweep, carried at low alpha, so it reads as dimmed against any base. Not a regression being fixed but an improvement being extended: before this branch, every theme got the same hard-coded sweep. This is the last path that still did. * 🎨 fix: Mirror the Shimmer Fallback Onto Versioned Theme Resolution Codex round 4. The compat fallback added last round covered only the legacy adapter, but this codebase carries these fallbacks on both paths: the `rgb-surface-composer-hover` derivation exists in `mapColors` AND in `resolveTheme`. Adding the shimmer stop to just one left the halves disagreeing. The versioned path is the one where the omission actually hurts. A definition written before these keys existed cannot name them, so `resolveTheme` filled the gap from the bundled base and pinned that theme's in-flight labels to LibreChat's own sweep — a theme restating its text as white would light every label in the stock near-black. Omission there means "not written yet", not "wants the bundled sweep"; a theme that does want it still says so by naming the stop, exactly as it opts out of the composer-hover fallback. Both paths now behave the same: a theme that restates its text and names no shimmer base gets its sweep from that text color, whichever way it is applied. |
||
|
|
98eb3f8b1c
|
🧿 fix: Restore Shared IP Limits for Generation Retries (#15427)
* fix: Preserve IP Limits for Generation Retries * test: Cover Scheduled Retry IP Limiting * style: format retry limiter route |
||
|
|
982a9a5141
|
🧴 fix: Strip LangChain URLs From Provider Errors (#15421)
Some checks are pending
Backend Unit Tests / Build packages (push) Waiting to run
Backend Unit Tests / Codegraph select (push) Waiting to run
Backend Unit Tests / TypeScript type checks (push) Blocked by required conditions
Backend Unit Tests / Circular dependency checks (push) Waiting to run
Backend Unit Tests / Tests: api (shard 1/3) (push) Blocked by required conditions
Backend Unit Tests / Tests: api (shard 2/3) (push) Blocked by required conditions
Backend Unit Tests / Tests: api (shard 3/3) (push) Blocked by required conditions
Backend Unit Tests / Tests: data-provider (push) Blocked by required conditions
Backend Unit Tests / Tests: data-schemas (push) Blocked by required conditions
Backend Unit Tests / Tests: @librechat/api (shard 1/4) (push) Blocked by required conditions
Backend Unit Tests / Tests: @librechat/api (shard 2/4) (push) Blocked by required conditions
Backend Unit Tests / Tests: @librechat/api (shard 3/4) (push) Blocked by required conditions
Backend Unit Tests / Tests: @librechat/api (shard 4/4) (push) Blocked by required conditions
Codegraph E2E Votes / vote (full suite) (push) Waiting to run
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
Frontend Unit Tests / Codegraph select (push) Waiting to run
Frontend Unit Tests / Build packages (push) Waiting to run
Frontend Unit Tests / TypeScript type checks (client) (push) Blocked by required conditions
Frontend Unit Tests / Tests: @librechat/client (push) Blocked by required conditions
Frontend Unit Tests / Tests: Ubuntu (shard 1/2) (push) Blocked by required conditions
Frontend Unit Tests / Tests: Ubuntu (shard 2/2) (push) Blocked by required conditions
Frontend Unit Tests / Vite build verification (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
* 🧹 fix: Strip LangChain Attribution From User-Facing Provider Errors LangChain classifies provider failures by mutating `error.message` with a docs URL, and that message is forwarded verbatim into the error content part we persist, so users saw `docs.langchain.com/...` credited for a provider's own 429. The client only intercepted `MODEL_NOT_FOUND`, leaving every other code to render raw. Resolve the classification server-side instead: codes with localized copy become the typed payload the client already knows how to localize, and every other message keeps the provider's wording with the docs URL removed before it reaches the database. The untouched error still reaches the logs through `getSafeErrorMetadata`. The client keeps a code-driven lookup for messages persisted before this, and gains `com_error_model_rate_limit` alongside the existing not-found copy. The OpenAI-compatible and Responses controllers now share one provider-error helper rather than three copies of it, so the strip applies on those surfaces too. * 🛡️ fix: Harden Error Sanitization For Non-String Messages And Legacy Text Codex review follow-up. `stripLangChainTroubleshootingUrl` assumed a string, so an error-like object with a truthy non-string `message` threw `.replace is not a function` inside the error handler that was meant to report it — the template interpolation it replaced had coerced such values safely. It now coerces at the boundary, as does the code lookup. Legacy messages whose LangChain code has no localized copy still rendered the docs URL, which is exactly the historical case the client-side path exists to clean up. The rendered fallback is now stripped too. Both helpers move to `librechat-data-provider`, which is shared by the client and the backend, so one implementation serves both instead of a second regex drifting in the component. |
||
|
|
f0f82ec5fa
|
🧷 fix: Fence Server-Admitted Queue Boundaries (#15393)
* fix: fence server-admitted queue boundaries * fix: preserve effective queue admission boundaries * fix: fence queue recovery predecessor * fix: unify queued boundary reconciliation * fix: make queued turn receipt settlement monotonic * fix: narrow queued receipt projection status * fix: close queued turn settlement races * fix: make queued admission lineage clock independent * fix: centralize queued turn admission ownership * test: type queued admission outcomes * fix: make queued admission identity durable * fix: carry queued admission identity end to end * style: sort queued turn test imports * fix: unify indeterminate admission ownership * fix: migrate queued admission fence state * fix: migrate every queued admission owner * fix: surface queued admission reconciliation * fix: preserve queued receipt evidence * test: type queued receipt regression * fix: stabilize queued receipt authority * fix: supersede initial receipt fetches |
||
|
|
f20b978075
|
🎯 ci: Retarget Pull Requests Opened Against main to dev (#15416)
* 🎯 ci: Retarget Pull Requests Opened Against `main` to `dev` Contributions land on `dev`; `main` only moves at release time. With `main` as the repository's default branch, pull requests default to it, and the mismatch has been resolved by hand at merge time. Adds a `pull_request_target` hook that switches a newly opened or reopened pull request's base from `main` to `dev` and posts a one-time explanation, plus a manual `workflow_dispatch` sweep (dry-run by default) for the existing backlog. Release-bound upstream branches keep `main` as their base: `dev`, `release/*`, `hotfix/*`, `backport/*` and `*-main`. The `target: main` label exempts any pull request. The exemptions apply only to branches on this repository, so a fork branch that happens to match one of those names is still retargeted. The job checks out the trusted base commit rather than the pull request head, so the write-scoped token never runs contributor code. `pull_request_target` reads its workflow definition from the base branch, so this must exist on `main` to take effect. * 📝 docs: Record the `dev`-First Branch Policy for Agents `CLAUDE.md` and `AGENTS.md` never stated which branch work targets, and the default branch is `main` — so `gh pr create` and the GitHub UI point at `main` unless told otherwise, and an agent following the defaults gets it wrong. States the policy, the reason a backport to `main` is never needed (`main` is kept as a strict ancestor of `dev`), and the exemptions the retarget workflow honors. * 📐 docs: Record Typecheck, Formatting and Issue-Closing Gotchas for Agents Four repeat cycle-wasters that neither file stated, each verified against the current tree: - `tsdown` emits without typechecking, so `npm run build` proves nothing about types in `packages/api`, `packages/client` or `packages/data-schemas`; only `packages/data-provider` runs `tsc` in its build. - `packages/client/tsconfig.json` excludes `*.spec.ts(x)` and `*.test.ts(x)`, so type errors in its specs surface only at run time. - `npm run sort-imports` with no arguments rewrites all six source roots. - `Fixes #N` never closes an issue here, because closing keywords are honored only on merges into the default branch and everything merges to `dev`. Also notes that worktrees share one stash stack, and points at `npm run static-checks -- --against origin/dev` as the local mirror of CI. * 🔁 fix: Address Codex Review on the Retarget Workflow - **Backport branches are no longer exempt.** Exempting `backport/*` and `*-main` contradicted the invariant the policy rests on: `main` is a fast-forward of `dev`, so a backport merged straight to `main` is precisely what breaks it. `target: main` remains the deliberate, explicit escape hatch; `dev`, `release/*` and `hotfix/*` stay exempt as release-bound branches. - **`npm run update` does not sync `dev`.** `config/update.js` hard-codes `git checkout main` and `git pull origin main` — it is the self-host deployment updater, not a branch-sync tool. Telling contributors to refresh `dev` with it would have branched them off `main` and recreated the very flow this change exists to prevent. Replaced with the explicit git commands. - **A dry run no longer reports work it did not do.** The summary said "Retargeted N" on a dispatch that wrote nothing, which could read as a completed sweep. - **A failed explanation comment is recorded, not swallowed.** The base edit has already landed at that point, so a later run would skip the pull request and the contributor would never receive the note. It now counts, fails the run, and names the retry: `EXPLAIN_MISSING=true`, which posts a missing explanation onto an already-retargeted pull request. - **The sweep paginates.** `gh pr list --limit 500` is a cap, not a page size, and would have silently ignored the remainder while reporting success. Verified against the stubbed `gh` boundary: `*-main` now retargets, the dry run performs zero writes and says so, a failing comment yields a non-zero exit, and `EXPLAIN_MISSING` posts onto a pull request already based on `dev`. The paginated query returns all 258 open pull requests based on `main`. * 🧷 fix: Address the Second Codex Round on the Retarget Workflow - **`already_explained` now separates "lookup failed" from "no marker".** A transient comments-API failure returned the same status as a clean miss, so the run would post a second explanation and report success — defeating the deduplication it exists for. A failed read now declines to post and is recorded. - **`CONTRIBUTING.md` syncs from the canonical remote.** The previous fix pulled `dev` from `origin`, which in the fork-based flow described two sections later is the contributor's own fork — so it would fail outright or silently sync a stale branch, landing them on the wrong base again. - **The hook retries on `synchronize`.** If the `opened` run failed before `gh pr edit` landed, nothing retried and the pull request stayed on `main`. A retargeted pull request no longer matches `branches: [main]`, so this adds no redundant runs — and the existing backlog now drains on contributor activity rather than only by sweep. - **Sweeps serialize against each other** via a shared concurrency group, while the hook stays keyed per pull request. Deliberately not one global group: GitHub keeps a single pending job per group, so a burst of openings would cancel queued retargets — trading a rare duplicate comment for dropped retargets is the wrong way round. - **`main` is an ancestor of `dev`, not a strict one.** Right after a sync the two refs are equal, which the previous wording described as a violated invariant. Verified: a failing comments lookup edits the base but posts nothing and exits non-zero. |
||
|
|
b7ae67307e
|
🕰️ fix: Anchor Temporal Variables to Turn Start (#15419) | ||
|
|
06ddaf8a3d
|
🧷 fix: Attach to a Continuation the Finished Run Left Behind (#15414)
* 🧷 fix: Attach to a Continuation the Finished Run Left Behind The FINAL path never clears the submission atom. A pane that has just finished a run therefore keeps holding it, and the external-run re-arm treated that as "already attached" — so the announcement was skipped, the status query stayed disabled, and nothing attached. That is the common shape, not an edge case. A background tool dispatch finishing starts the next generation for a conversation whose pane almost always just completed the run that dispatched it. The sidebar spinner showed the run because it reads the active list directly; the pane itself sat on the previous turn until a reload, so both the harvested task output and every following generation stayed invisible. Distinguish an attachment from bookkeeping: a submission stands in for a live stream only while `isSubmitting`, which stays true across reconnect backoff and is raised before a submission is installed, so recovery still reads as attached and a fresh send has no window where it looks finished. When the announcement is answered and a finished submission is still installed, release it — the run it describes is over, and a server-side continuation is a different generation that the resume path owns building for. * 🪢 fix: Treat an Attaching Resume as Live, Not as Leftovers Self-review of the previous commit. Gating liveness on `isSubmitting` alone reads a resumed attachment as finished for the whole window between installing its submission and its stream opening — this hook installs the submission, and only the `open` handler raises `isSubmitting`. An announcement landing there would release the submission this hook had just built, tearing down the attachment to rebuild it, and on a slow or retrying subscribe that repeats every heartbeat. The generation epoch is the missing half: this hook stamps it immediately before installing a resume submission, `subscribeToStream` stamps it for every other attachment, and terminal teardown clears it. Read both — `isSubmitting` covers sends and reconnect backoff, the epoch covers attaching — and a submission counts as leftovers only when neither says otherwise. |
||
|
|
b88cb3e361
|
👐 ci: Cancel the Playwright E2E Gate With Its Run (#15415)
The `e2e` fan-in gate guarded itself with `always()`, so a run superseded by `concurrency: cancel-in-progress` still dispatched it onto a fresh runner after every other job had gone `cancelled`. It then read `needs.e2e_shards.result == 'cancelled'`, took the `!= 'success'` branch, and exited 1 — leaving the required `e2e` check red on a run that was never adjudicated, and burning a runner slot to do it. `!cancelled()` keeps the property the gate actually needs, running when a shard fails so the failure is reported rather than skipped, and drops the one it never wanted. It also matches `e2e_shards` and `mcp_tool_list_changed`, which already guard themselves this way; run 33391962579 shows both lanes running normally past a skipped `codegraph_select`, so a skipped dependency still reaches the gate. Claude-Session: https://claude.ai/code/session_012jJbt9x1sCu8ggJQmdjoMy Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
81338cef95
|
🍃 test: Sweep Every DB Schema Method Against a Live Engine (#15392)
* 🍃 test: Sweep Every data-schemas Method Against a Live Engine Adds `misc/documentdb/sweep.documentdb.spec.ts`: drives all 510 exported data-schemas methods against a real engine and records, per method, how many driver queries it issued and whether the engine rejected any of them. Every DocumentDB incompatibility found so far — pipeline updates, `$$REMOVE`, `$facet`, the transaction-probe false negative, the Mongoose-compiled mixed projection — was invisible until someone thought to look for its class. The sweep inverts that: the engine adjudicates whatever each method emits, known class or not. Query shapes are parsed before they are matched, so an empty database adjudicates most entry paths. Mechanics: arguments are synthesized from each function's parameter names, then repaired adaptively from the validation errors they provoke (ObjectId and number casts, required schema paths, object-parameter shapes) until the method reaches the database; the driver prototype is instrumented to count calls and capture server errors — including on cursor consumption, where parse errors actually surface — and duplicate-key violations are classified as benign. A method that never issues a query is reported un-adjudicated instead of silently green: coverage is visible, not assumed. Two modes with identical mechanics so matrices diff cleanly: in-memory MongoDB (`SWEEP_BASELINE=true`, free, CI-able) and live DocumentDB (`DOCUMENTDB_URI`). `SWEEP_REPORT_PATH` writes JSON for machine diffing; `DOCUMENTDB_STRICT=true` turns rejections into failures. First full run against live DocumentDB 5.0.0: 363 of 510 methods issued at least one query, zero engine rejections, zero divergences from the MongoDB baseline. The jest config gains the main config's transform-ignore patterns so the full method bundle (which pulls ESM markdown tooling) loads. * 🍃 fix: Adjudicate Index DDL, Real ACL Cascades, and a Run-Scoped Sweep Database Review round 1, all four findings real: - The driver instrumentation now wraps `createIndex`, `createIndexes`, `dropIndex`, `dropIndexes`, and `listIndexes` (cursor-wrapped), so an engine rejecting index DDL — which `recordAuditEntry` issues before any listed operation and then swallows — is adjudicated instead of surfacing as a false `no-queries` under STRICT. - The `removeAllPermissions` no-op override is gone: `createMethods` supplies a real implementation delegating to `deleteAclEntries`, so driven deletion cascades exercise their ACL operations (not-driven drops 147 → 142). - Each race timer is cleared when its attempt settles, instead of hundreds of referenced timers living until the shared deadline and the last one holding the process open after the report. - The live run connects to a run-scoped database (`librechat_sweep_<runId>`), so its `dropDatabase` teardown can never erase fixtures out from under the sibling live suites sharing the URI, nor any retained state in the URI's own database. * 🍃 fix: Attribute Straggler Queries by Async Context and Adjudicate Transaction Commits Review round 2, all three findings real: - Per-method attribution now rides AsyncLocalStorage instead of a module global: a method that outlives its timeout keeps recording queries and engine errors under ITS OWN row rather than corrupting whichever row the sweep advanced to, and a final reclassification pass upgrades any row whose engine error arrived after its outcome was assigned. - The session prototype's `commitTransaction`/`abortTransaction`/ `withTransaction` are instrumented: transaction-lifecycle rejections surface from the session, not a collection operation, and several methods convert them into domain errors — previously a real commit rejection classified as a mere `threw` and STRICT passed. - The compatibility doc's published numbers now come from the corrected-harness baseline (368 driven / 142 un-driven) and state explicitly that the recorded live-run matrix predates the instrumentation widening, so the next live run is authoritative for the newly adjudicated surfaces. * 🍃 fix: Settle Stragglers Before STRICT and Commit a Real Transaction on a Replica-Set Baseline Review round 3, all four findings real: - STRICT is now enforced against the FINALIZED rows: `afterAll` waits (bounded) for every timed-out invocation to settle, reclassifies rows whose engine errors arrived late, and asserts zero rejections — previously a straggler's rejection could arrive after its per-test assertion had already passed against the `timeout` outcome. - The baseline runs on a single-node replica set with the live branch's exact connection options (`autoIndex`/`autoCreate` off, run-scoped `dbName`): a standalone baseline would reject driven transactions for topology reasons, and asymmetric model auto-creation would attribute DDL in one mode and not the other — both manufacturing false matrix divergences. - `resolveMCPAuthorityProof` gets a seeded case (fixture ported from the compat suite, executed in tenant context via the new `within` wrapper on overrides): generic synthesis died on target validation before a session ever started, so the session instrumentation adjudicated nothing. It now drives 22 operations through the production path, transaction commit included, on both engines. * 🍃 docs: Sync the Published Baseline Counts With the Seeded Harness * 🍃 fix: Materialize Transaction-Read Collections and Settle Stragglers Before Teardown Review round 4, three findings real (the fourth — stale doc totals — was already fixed on the pushed head): - Straggler settlement moved BEFORE engine teardown: dropping the database or closing the client would abort outstanding work with closed-client errors that are not engine verdicts, leaving a timed-out row unadjudicated while STRICT passes. - The authority fixtures (sweep AND compat) materialize PluginAuth and Token before the transaction: `loadCurrentProof` reads both inside it, and DocumentDB rejects any in-transaction statement touching a non-existent collection — the rejection surfaces as `proof_unavailable` before the commit is reached. This is the mechanism behind the compat authority test failing on every live cluster run while passing against MongoDB; with the collections materialized, the next live run adjudicates the transaction itself instead of the missing namespace. - Engine errors are recognized by `instanceof MongoServerError` instead of a `name` equality, so server-originated subclasses (MongoWriteConcernError, MongoBulkWriteError) can no longer slip through the session wrapper as domain throws. * 🍃 fix: Materialize Group and Config for the Authority Transaction and Exclude Constructor Exports Review round 5, both findings real. The proof transaction also reads Group and Config; the sweep fixture materializes both (the compat fixture already creates a Config record and now materializes Group). SessionError — an error class exported alongside the session methods — is excluded from the method map, correcting the totals to 509 methods / 369 driven / 140 un-driven; the published baseline is synced in the same commit. * 🍃 fix: Build a Fresh Method Bundle per Case and Emit a Normalized Matrix Review round 6, both findings real: - Every row now invokes a freshly constructed method bundle. Factory-local caches leaked across alphabetically ordered rows — the session methods cache `sessionIndexesPromise`, so `ensureSessionIndexes` returned that settled promise and was misreported as `no-queries`. It now issues its 3 index operations, making the matrix order-independent (370 driven / 139 un-driven). - The report separates run metadata from a canonical `rows` payload with run-specific values normalized out (run id, generated ObjectIds and UUIDs), so the two engines' matrices diff cleanly instead of differing on every row that embeds a synthesized value in its error text. * 🍃 fix: Materialize Authority Snapshot Namespaces Before Its Transaction Amazon DocumentDB rejects any statement inside a transaction that touches a collection which does not exist, and `asMCPError` converts that server rejection into `proof_unavailable` — so on a deployment where no PluginAuth or Token row has ever been written, every MCP authority proof fails with an error naming nothing about the real cause. This is the mechanism behind the live-harness authority probe failing on every real cluster run since July while passing against MongoDB. `loadAuthoritativeSnapshot` now materializes the nine namespaces its snapshot reads before opening the transaction, once per process. MongoDB tolerates the in-transaction read either way, so nothing changes there; a create failure is logged at debug and left to surface from the transaction itself. The regression test drops the credential namespaces to reproduce the state and asserts the resolve recreates them and succeeds; it fails without the fix. * 🍃 docs: Record the Authoritative Hardened Live Run The hardened harness ran against a live DocumentDB 5.0.0 cluster: 370 of 509 methods drove at least one query, zero engine rejections, and the matrix is byte-identical to the MongoDB baseline — every outcome and query count matches. This run adjudicated index DDL, transaction commits and aborts, ACL cascades, and the authority snapshot transaction, none of which the first pre-hardening run could see. The same run turned the compatibility suite's MCP authority probe green for the first time on any real cluster, confirming the namespace-materialization diagnosis for the `proof_unavailable` failures seen since July. * 🍃 fix: Retry Snapshot Namespace Creation After a Transient Failure The catch suppressed every `createCollection` error, so a transient failure settled the once-per-process memo as successful: creation was skipped for the rest of the process, the transaction kept failing on the missing collection, and every later proof returned `proof_unavailable` even after the condition cleared. The existing reset-on-reject path could never fire because the inner promise never rejected. Only `NamespaceExists` (including a concurrent creator winning the race) is suppressed now; every other failure propagates, clears the memo, and lets the next request retry. The regression test rejects the first creation with a transient error, asserts the proof fails, then asserts the following request recreates the namespace and succeeds — it fails against the swallow-all form. * 🍃 fix: Throttle Snapshot Namespace Preflight Retries With a Bounded Cooldown Clearing the memo on every rejection made a persistent failure — an authorization error, say — turn each authority request into another serial collection preflight: a DDL retry storm precisely while the database is unhealthy, since the single-flight promise only coalesces requests while an attempt is pending. A failure is now recorded with its timestamp and replayed without touching the database until a bounded cooldown elapses (30s, overridable through the existing hooks for tests). Transient failures stay retryable, just not on every request. Both sides of the boundary are covered: one test asserts a persistent failure issues no further creations inside the cooldown, and the existing retry test runs with the cooldown disabled to assert recovery still works. The throttle test fails with the cooldown gate removed. * 🍃 fix: Instrument Db-Level Namespace DDL in the Sweep The authority path's namespace preflight calls `Model.createCollection()`, which delegates to `Db.createCollection` — not to any `Collection` operation the sweep wrapped. A DocumentDB rejection of that DDL would therefore be laundered by `asMCPError` into `proof_unavailable`, leaving the row classified as a mere `threw` while `DOCUMENTDB_STRICT=true` passed: the exact false-green class this suite exists to close, reintroduced by the preflight itself. `Db.createCollection`/`dropCollection`/`renameCollection` are now instrumented, and `NamespaceExists` (48) joins the benign server codes since an idempotent preflight against an existing collection is the expected outcome, not an incompatibility. The authority row's adjudicated query count rises from 22 to 31, which is those nine preflight operations becoming visible. |
||
|
|
ccd673bf05
|
🧰 fix: Keep File-Backed Agent Capabilities and Their Saved Tools in Agreement (#15418)
An agent with knowledge files showed File Search as selected in the builder while the save omitted `file_search` from its tools. At runtime the tool was gone, the row still looked selected so re-adding it changed nothing, and the next save re-confirmed the broken state. Recovery needed a MongoDB write. The builder's model for file-backed capabilities is stated in ToolsSection: they stay selected while they hold files, and removal is routed to the file manager rather than the capability flag — mirroring the file-only `context` built-in. Three places implemented that model separately and drifted: - display resolved it from the flag OR the file count, - persistence resolved it from the flag alone, - the removal guard lived inline in the tools list and was missing entirely from the marketplace dialog. That last gap is how the bad state was reached. `ToolsMarketplaceDialog` toggled a selected File Search straight to `file_search: false` with no file check, producing flag-off-with-files — a state display cannot show and persistence silently drops. `items/capabilities.ts` now owns the capability table and the one rule, and display, removal and persistence all derive from it: - persistence resolves the same way display does, so a file-holding capability keeps its tool (this also heals already-broken agents on their next save), - both toggle paths route removal of a file-holding built-in to the file dialog, - the removal guard takes file counts rather than the agent, so a component can pass the hydrated entries it already watches while submit reads the form — the rule is shared, the source of the count need not be. Submission also stops mutating `data.tools` in place and dedupes the result. Supersedes the persistence half of #15380, which fixed the save but left the marketplace path able to recreate the state and kept the rule in a third copy. Fixes #15368 |
||
|
|
7b0b145159
|
🪁 fix: Keep Tool Flyouts in View (#15404)
* fix: keep tools menu submenus within the viewport * fix: let the viewport cap override the submenu min-width |
||
|
|
26887bc5a9
|
🛗 chore: Elevate @librechat/agents to 3.7.11 and multer to 2.3.0 (#15409)
* 📦 chore: bump `@librechat/agents` to v3.7.11
* chore: bump multer to latest
|
||
|
|
52f7251ef7
|
📦 chore: Bump Packages from LibreChat SCA Audit (#15410) | ||
|
|
413906abc3
|
🖍️ fix: Honor Agent Image Detail Settings (#15407)
* 🖼️ fix: Apply an Agent's Configured Image Detail The Agent Builder exposes an Image Detail slider and saves it to `model_parameters.imageDetail`, but nothing ever read it back: `encodeAndFormat` resolves the level from `req.body.imageDetail`, which the agents route never populates, so every image was encoded as `detail: "auto"`. Providers that reject `auto` — MiniMax's OpenAI-compatible API among them — fail every image upload through an agent, and the setting that would fix it has no effect. The root cause is a classification error rather than a missing argument. `extractLibreChatParams` splits LibreChat's own settings out of an agent's model parameters, and its key set is derived from the `librechat` group in `parameterSettings`. `imageDetail` sat in `baseDefinitions` instead, so it was neither extracted for LibreChat's use nor stripped from the options handed to the provider. It is now grouped with `resendFiles` and the other LibreChat-level parameters, which gives it the same path those already take: extracted during agent initialization, carried on the initialized agent, passed to the client, and read by `encodeAndFormat`. `AgentClient.getSaveOptions` already expected `options.imageDetail` — nothing had ever populated it. The request body still wins where it is set, so the classic endpoints keep resolving detail from conversation settings exactly as before. Fixes #15359 * 🔤 fix: Type Image Detail as Its Enum, Not a Bare String `TConversation.imageDetail` is the `ImageDetail` enum, so the extractor's return type and the initialized agent's field are now declared as that enum rather than widened to `string`, and the tests pass enum members instead of raw literals. Caught by CI's type check, which I had run before adding the tests and not after — jest compiles the specs through babel, so the suite stayed green over a type error. |
||
|
|
e9e77a4b33
|
🧜♀️ chore: Bump mermaid to v11.17.2 and dompurify to v3.4.14 (#15405)
Bumps mermaid 11.16.1 -> 11.17.2, and raises the dompurify override from 3.4.12 to 3.4.14 to clear GHSA-55q2-fjhq-7xh7 (XSS: IN_PLACE hook removal leaves a detached subtree executable). The dompurify pin lives under the `monaco-editor` override because monaco depends on an exact 3.4.8; the override lifts it to match what the rest of the tree resolves for `^3.4.0`, so a single copy stays hoisted. Bumping it to 3.4.14 (current latest) keeps that single-copy property — pinning 3.4.13 would have left `^3.4.0` on 3.4.14 and installed two copies. The mermaid floor moves to ^11.17.2 rather than staying ^11.16.1 because 11.17.0/11.17.1 dropped the `edgePaths` class from rendered SVG, breaking the flowchart, block and user-journey stylesheets. Restored in 11.17.2. Lockfile delta is limited to mermaid 11.16.1 -> 11.17.2, @mermaid-js/parser 1.2.0 -> 1.2.1, dompurify 3.4.12 -> 3.4.14, plus new fastdom + strictdom. katex is unaffected: mermaid raised its floor to ^0.16.47, but the global `katex: ^0.16.21` override supersedes it, and mermaid only ever calls `katex.renderToString()`, which 0.16.28 provides. |
||
|
|
044894e8bb
|
🎛️ fix: Settle New Turns at the Bottom, Steady Status Dots, and Elapsed Time in the Panel (#15394)
Some checks are pending
Backend Unit Tests / Build packages (push) Waiting to run
Backend Unit Tests / Codegraph select (push) Waiting to run
Backend Unit Tests / TypeScript type checks (push) Blocked by required conditions
Backend Unit Tests / Circular dependency checks (push) Waiting to run
Backend Unit Tests / Tests: api (shard 1/3) (push) Blocked by required conditions
Backend Unit Tests / Tests: api (shard 2/3) (push) Blocked by required conditions
Backend Unit Tests / Tests: api (shard 3/3) (push) Blocked by required conditions
Backend Unit Tests / Tests: data-provider (push) Blocked by required conditions
Backend Unit Tests / Tests: data-schemas (push) Blocked by required conditions
Backend Unit Tests / Tests: @librechat/api (shard 1/4) (push) Blocked by required conditions
Backend Unit Tests / Tests: @librechat/api (shard 2/4) (push) Blocked by required conditions
Backend Unit Tests / Tests: @librechat/api (shard 3/4) (push) Blocked by required conditions
Backend Unit Tests / Tests: @librechat/api (shard 4/4) (push) Blocked by required conditions
Codegraph E2E Votes / vote (full suite) (push) Waiting to run
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
Frontend Unit Tests / Codegraph select (push) Waiting to run
Frontend Unit Tests / Build packages (push) Waiting to run
Frontend Unit Tests / TypeScript type checks (client) (push) Blocked by required conditions
Frontend Unit Tests / Tests: @librechat/client (push) Blocked by required conditions
Frontend Unit Tests / Tests: Ubuntu (shard 1/2) (push) Blocked by required conditions
Frontend Unit Tests / Tests: Ubuntu (shard 2/2) (push) Blocked by required conditions
Frontend Unit Tests / Vite build verification (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
* 🎛️ fix: Settle New Turns at the Bottom, Steady Status Dots, and Elapsed Time in the Panel * 🎛️ fix: Anchor Panel Timers, Order Turns by Trigger Time, Keep a Non-Color Status Cue |
||
|
|
14d4f2789d
|
🍃 fix: Send a Pure Inclusion Projection From the Legacy Receipt Sweep (#15390)
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
Sync Helm Chart Tags / Ignore non-main push (push) Waiting to run
Sync Helm Chart Tags / Sync chart tags (push) Waiting to run
* 🍃 fix: Send a Pure Inclusion Projection From the Legacy Receipt Sweep The legacy actor-receipt sweep selected its candidates with `.select('_id +agentEventActorReconciliations')`. `_id` alone does not make a Mongoose projection inclusive and a `+` token only un-hides its field, so this compiles to `{ _id: 1 }` plus a `: 0` exclusion for every other `select: false` sibling — a mixed projection MongoDB tolerates via the `_id` exception but Amazon DocumentDB rejects (`Projections cannot have a mix of inclusion and exclusion`). The sweep failed on every 30-second maintenance pass, receipts never expired, and the rejection took the whole Promise.all down with it, so the sequenced lane reclamation never ran either. - The candidate read uses the object form `{ _id: 1, agentEventActorReconciliations: 1 }` — a pure inclusion that still returns the hidden field and fetches nothing extra. A regression test spies on the driver call and asserts the compiled projection contains no exclusions. - Every maintenance step now fails alone: a rejection is logged with its step name and counted as zero progress instead of aborting the pass, and lane reclamation runs regardless. Covered by a test rejecting one step and asserting reclamation still runs. - The static guard gains a detector for the exact broken shape — `select('_id +field')` — with fixtures for both directions. The shape is narrow on purpose: probing real Mongoose shows a bare non-`_id` token makes the projection inclusive and turns `+` tokens into plain `field: 1` inclusions, so the repo's nine other `+`-selects are all legal and stay unflagged. The live audit suite gains a raw mixed-projection probe and drives the sweep itself as production shape number eight. * 🍃 fix: Gate Lane Reclamation on Batch Recovery and Type the Projection Assertion Review round 1, both findings real: - Batch-receipt recovery is a prerequisite of lane reclamation, not an independent step: reclamation consumes the lane-cleanup markers (`fulfillLaneCleanupRequest` unsets the delivery-side marker, and `reclaimLaneIfInactive` clears `cleanupRequestedAt` when the lane is still retained), so running it against a half-recovered batch erases a request no later successful recovery can re-arm, retaining the lane permanently. The blanket isolation reintroduced exactly that; reclamation now waits for a batch-recovery pass that did not fail, while the genuinely independent steps keep failing alone. Covered by a test rejecting batch recovery and asserting reclamation is withheld. - The captured-projection assertion drops its `Record<string, unknown>` cast and asserts deep equality with the exact expected pure-inclusion projection, which is both typed and strictly stronger. |
||
|
|
c93bd7ec9d
|
🪟 fix: Keep the Subagent Panel Steady and Honest Across Event Deliveries (#15387)
* 🪟 fix: Keep the Subagent Panel Steady and Honest Across Event Deliveries * 🧮 fix: Budget the Activity Projection at the Database Seam and Scope Retained Views * 🧮 fix: Charge Label Associations to the Budget and Scope History Flags * 🧮 fix: Gate Mixed Activity Metadata and Keep the Budgeted Suffix Contiguous * 🧵 fix: Render Only the Active Thread's Retained Turns |
||
|
|
c7f0a8f247
|
📬 feat: Move Agent Queued Turns Server-Side (#15370)
* feat: move agent queued turns server-side * test: update queued turn composition coverage * fix: harden queued-turn lifecycle ownership * fix: centralize queued-turn lifecycle ownership * fix: close queued-turn lifecycle gaps * fix: unify queued turn lifecycle transitions * fix: fence queued-turn deletion admission * fix: preserve queued-turn lifecycle ownership * fix: close queued-turn reconciliation races * fix: reconcile quarantined queued-turn admissions * fix: annotate admission evidence default * fix: deepen queued turn lifecycle admission * fix: fence queued turn provider admission |
||
|
|
4de0a9341b
|
✍️ chore: Clarify That Interrupting a Steer Does Not Interrupt Reasoning (#15386)
Some checks are pending
Backend Unit Tests / Build packages (push) Waiting to run
Backend Unit Tests / Codegraph select (push) Waiting to run
Backend Unit Tests / TypeScript type checks (push) Blocked by required conditions
Backend Unit Tests / Circular dependency checks (push) Waiting to run
Backend Unit Tests / Tests: api (shard 1/3) (push) Blocked by required conditions
Backend Unit Tests / Tests: api (shard 2/3) (push) Blocked by required conditions
Backend Unit Tests / Tests: api (shard 3/3) (push) Blocked by required conditions
Backend Unit Tests / Tests: data-provider (push) Blocked by required conditions
Backend Unit Tests / Tests: data-schemas (push) Blocked by required conditions
Backend Unit Tests / Tests: @librechat/api (shard 1/4) (push) Blocked by required conditions
Backend Unit Tests / Tests: @librechat/api (shard 2/4) (push) Blocked by required conditions
Backend Unit Tests / Tests: @librechat/api (shard 3/4) (push) Blocked by required conditions
Backend Unit Tests / Tests: @librechat/api (shard 4/4) (push) Blocked by required conditions
Codegraph E2E Votes / vote (full suite) (push) Waiting to run
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
Frontend Unit Tests / Codegraph select (push) Waiting to run
Frontend Unit Tests / Build packages (push) Waiting to run
Frontend Unit Tests / TypeScript type checks (client) (push) Blocked by required conditions
Frontend Unit Tests / Tests: @librechat/client (push) Blocked by required conditions
Frontend Unit Tests / Tests: Ubuntu (shard 1/2) (push) Blocked by required conditions
Frontend Unit Tests / Tests: Ubuntu (shard 2/2) (push) Blocked by required conditions
Frontend Unit Tests / Vite build verification (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
The interrupting receipt's hover implied the interrupt cuts in immediately, but the seal waits for reasoning to finish. Say so, and point at stopping the response as the way to interrupt reasoning. |
||
|
|
56d54a9416
|
📦 chore: bump vite to v8.2.2 (#15384)
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
Sync Helm Chart Tags / Ignore non-main push (push) Waiting to run
Sync Helm Chart Tags / Sync chart tags (push) Waiting to run
Refreshes the lockfile to the version the existing `^8.0.16` range already resolves to, so `npm install` and `npm ci` stop disagreeing about which Vite builds the client. Carries rolldown 1.0.3 -> 1.2.6 with it. |
||
|
|
5fdc4f8656
|
🔒 chore: bump google.golang.org/grpc to v1.82.2 (GHSA-hrxh-6v49-42gf) (#15383)
Resolves the high-severity grpc-go advisory (xDS RBAC authorization bypass, HTTP/2 rapid-reset mitigation bypass, xDS RBAC panic) flagged against `otel/langfuse-fanout`. grpc is a transitive dependency there, linked only because the OTLP collector protobufs carry generated gRPC service stubs; the service itself speaks OTLP over HTTP and never starts a gRPC server or uses xDS. Carries `protobuf` to v1.36.11 and the `genproto` stamps forward, as required by grpc v1.82.x. No source changes. |
||
|
|
9472d51dac
|
🧪 fix: Align Detached Activity E2E With the Removed Running Chip (#15382)
* 🧪 fix: Align Detached Activity E2E With the Removed Running Chip * 🧪 fix: Drop Narrating Comment From Detached Activity Spec |
||
|
|
1e4cae07c7
|
🧠 feat: Retain Subagent Reasoning Like Main Chat and Drop the Running Status Chip (#15379)
Some checks are pending
Backend Unit Tests / Build packages (push) Waiting to run
Backend Unit Tests / Codegraph select (push) Waiting to run
Backend Unit Tests / TypeScript type checks (push) Blocked by required conditions
Backend Unit Tests / Circular dependency checks (push) Waiting to run
Backend Unit Tests / Tests: api (shard 1/3) (push) Blocked by required conditions
Backend Unit Tests / Tests: api (shard 2/3) (push) Blocked by required conditions
Backend Unit Tests / Tests: api (shard 3/3) (push) Blocked by required conditions
Backend Unit Tests / Tests: data-provider (push) Blocked by required conditions
Backend Unit Tests / Tests: data-schemas (push) Blocked by required conditions
Backend Unit Tests / Tests: @librechat/api (shard 1/4) (push) Blocked by required conditions
Backend Unit Tests / Tests: @librechat/api (shard 2/4) (push) Blocked by required conditions
Backend Unit Tests / Tests: @librechat/api (shard 3/4) (push) Blocked by required conditions
Backend Unit Tests / Tests: @librechat/api (shard 4/4) (push) Blocked by required conditions
Codegraph E2E Votes / vote (full suite) (push) Waiting to run
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
Frontend Unit Tests / Codegraph select (push) Waiting to run
Frontend Unit Tests / Build packages (push) Waiting to run
Frontend Unit Tests / TypeScript type checks (client) (push) Blocked by required conditions
Frontend Unit Tests / Tests: @librechat/client (push) Blocked by required conditions
Frontend Unit Tests / Tests: Ubuntu (shard 1/2) (push) Blocked by required conditions
Frontend Unit Tests / Tests: Ubuntu (shard 2/2) (push) Blocked by required conditions
Frontend Unit Tests / Vite build verification (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
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
Sync Helm Chart Tags / Ignore non-main push (push) Waiting to run
Sync Helm Chart Tags / Sync chart tags (push) Waiting to run
|
||
|
|
fcae1025c0
|
🧳 feat: Register Principal-Owned Code Environments (#15365)
* feat: add principal-owned code environments * fix: address code environment CI coverage * fix: expose principal code environments in endpoint config * fix: reject missing code environment bodies * fix: harden code environment lifecycle * fix: revalidate principal code environments * fix: synchronize code environment authorization * fix: bind code environments to current principals * fix: fail closed on code ACL cache errors * fix: prevent code environment override shadowing * fix: preserve principal environment defaults * fix: suppress revoked code environment aliases * fix: fail closed on environment augmentation * fix: narrow code environment defaults * fix: isolate code environment fallback * style: sort code config imports |
||
|
|
0389a92452
|
📦 chore: bump @librechat/agents to v3.7.10 (#15378)
|
||
|
|
cd46d50810
|
🕯️ style: Settle the Applied Steer Receipt Once the Response Is Done (#15377)
The applied checkmarks kept the amber steering identity forever, so a conversation full of historical steers stayed lit long after anyone cared whether they landed. The receipt now keeps amber only while the owning response is still generating (isSubmitting, the window where it is the live thing at the end) and settles to the timestamp gray with a soft color transition. Reload, share, and search render settled. |
||
|
|
9ac801ee45
|
✅ feat: Delivery Receipt Checkmarks for Steered Messages (#15376)
* ✅ feat: Delivery Receipt Checkmarks for Steered Messages Users regularly read steering and interrupt as broken because nothing visibly reacts: the in-flight bubble's only applied signal is its own disappearance, an armed interrupt is a silent icon swap, and the inline part's explanation hides behind a hover-only "?". Every phase now shows an explicit, immediate receipt driven by events the protocol already emits (no server changes): - Shared SteerReceipt (Chat/Steering/Receipt.tsx): Sending… → Delivered ✓ (202 ACK) → Interrupting (amber ✓ + pulsing dot on preempt arm) → Applied ✓✓ on the inline part, with hover cards explaining each state (the info text doubles as the accessible name). - The applied ✓✓ replaces SteerPart's hover-gated "?" in the same 32px meta row and stays visible on reload, share, and search views. - A one-shot draw-in plays only at the live chip→inline hand-off: resolveSteerChip stamps liveAppliedSteerIds in the same batch as the chip removal and SteerPart consumes the id on mount. - An unacknowledged interrupt shows the amber label and pulse without a check: a check only ever asserts what the server confirmed. Failed and delivery-uncertain steers keep their recovery rows, checkless. - Queued rows gain one caption for the group ("Sends when this response finishes"), shown only while the run is active. * 🤝 fix: Address Codex Round 1 on Steer Receipts - Extend InfoHoverCard with an optional custom trigger (children) and compose the receipt through it instead of duplicating the hover-card state, portal, and focus handling in the feature. - Replace the feature-specific style.css keyframe with the existing tailwindcss-animate utilities (animate-in fade-in-0 zoom-in-50 duration-theme-normal, motion-reduce:animate-none), matching the house pattern in Thinking.tsx. - Scope the live-applied subscription per steer id via a membership selectorFamily so stamping/consuming one receipt no longer re-renders every mounted historical SteerPart. - Gate the live stamp on a genuinely first application: a replayed on_steer_applied whose ids are already in the durable applied set no longer re-arms an animation its part already consumed. - Track a row-local arming flag so escalating a delivered steer flips its receipt to the unconfirmed Interrupting state on the click, with the check appearing only on the confirmed durable arm (and reverting to Delivered on a lost race). * 🤝 fix: Address Codex Round 2 on Steer Receipts - Stamp the live-applied id only when the inline part is actually committed: resolveSteerChip now returns the first-application ids and applySteerToMessages threads them through its bounded retries, stamping in the same batch as the setMessages commit. An abandoned placement or a replayed (referentially stable) event can no longer strand an id that would animate a historical part on a later visit. - Move the queued caption outside the role=list container so the list owns only listitem rows, with a structure test. * 🤝 fix: Stamp Only the Rendered Steer Id as Live-Applied The applied event settles both the server id and its client alias, but the inline part renders (and therefore consumes) only part.steerId, so stamping both left the alias resident in the live set as dead weight. Stamp exactly the rendered id. * 🤝 fix: Address Codex Round 4 on Steer Receipts - Capture the live draw-in per steer IDENTITY, not per mount: a content slot overwritten with a different steer (applySteerPart permits index replacement) re-derives its flag and every identity consumes its id, so nothing lingers to animate a historical part later. - Treat a relabelled chip (preempt past sending) as confirmation even while this row's arm request is pending: the SSE steer_updated can deliver the durable arm before the HTTP response, and the check must not wait out the round-trip timeout. - Announce receipt transitions (Sending to Delivered, Interrupting) through the row's existing polite live region; mount state is not replayed. |
||
|
|
9684b2478f
|
🍃 fix: Restore Amazon DocumentDB Compatibility Across Trigger and Subagent Writes (#15375)
* 🍃 fix: Restore Amazon DocumentDB Compatibility Across Trigger and Subagent Writes The README advertises Amazon DocumentDB 5.0+ support, but six sites added between 2026-07-29 and 2026-08-30 reintroduced constructs the engine rejects, and the transaction probe silently reported a false negative. All seven are adjudicated against a live DocumentDB 5.0.0 cluster, not against AWS's omission-based documentation. Rejected by every engine version, now rewritten with supported operators: - `claimNextAgentTriggerDelivery` used a pipeline-form update. It reads the globally oldest candidate under the same sort, then claims that exact `_id` and lifecycle state with a compare-and-swap. Ownership is still granted only by the fenced write. An empty queue costs the one read it costs today; a successful claim costs one additional round trip. - `renewAgentTriggerDeliveryProducerLease` used a `$cond` to keep the later deadline. `$max` is the exact classic-operator form and evaluates no expression. - `claimBackgroundToolResults` and `releaseBackgroundToolResultClaims` rewrote the entire `content` array through `$map`. The filtered positional operator addresses the matching parts directly, so each write touches only those parts and still needs no read-modify-write. - The bounded subagent thread-view projections used `$$REMOVE` to omit keys. They now emit `null` and are pruned back to absent inside the pass that already materializes each record, preserving the absent-vs-present contract. - The selected-task read used `$facet`. It is now two concurrent reads, which also removes the 16 MiB single-document risk the surrounding code already avoids for the same reason. `supportsTransactions` probed a canary collection that does not exist, and DocumentDB rejects a transaction touching a non-existent collection. The probe returned `false` on an engine that fully supports transactions, so every caller silently took the non-transactional path. It now materializes the canary first, once per process. Adds `documentdb.spec.ts`, a static guard over the whole class, because no runtime test can catch it: every suite runs `mongodb-memory-server`, which is real MongoDB and accepts all of these constructs. Adds `misc/documentdb/audit.documentdb.spec.ts` to re-adjudicate against a real cluster, and records the live verdicts and connection requirements in the compatibility assessment. * 🍃 fix: Address Review — CAS Exhaustion, AST Guard, Single-Snapshot Selected Read Addresses all three review findings, and the strengthened guard immediately found a seventh incompatible site the original character-level scan missed. - Exhausting the claim's compare-and-swap budget now throws instead of returning null. Every lost round read a claimable row, so the queue is not empty; null is the engine's confirmed-empty signal and advances its idle backoff, which would leave queued work waiting. A throw lands on the engine's retryable claim-failure path, which holds the polling cadence. Covered by a new test asserting the bounded budget and that the loser owns nothing. - The static guard now walks the TypeScript AST: it catches pipelines bound to a variable first, cast with `as`, chosen by a conditional, or carried in a `bulkWrite` operation's `update` property (including shorthand). Every shape is proven against fixtures in the suite itself, so the detectors can never silently go inert. The stronger scan flagged `updateToolCallResult`, whose pipeline lived in a `stages` variable: the backgrounded tool-call settle path was still broken on DocumentDB. Rewritten with the filtered positional operator — subfield sets preserve an existing `resultClaim` without per-part branching, an explicit `$unset` keeps the old whole-object replacement's wakeup-disarm semantics, and the attachments merge becomes `$pull` plus `$push` (two atomic writes that converge under the retry the claim fencing already provides). New tests lock the disarm and supplied-claim semantics, and the live audit suite now drives this method too. - The selected-task read is a single aggregation again: each selected row yields both its bounded message fields and its source payload from the same document version, restoring the `$facet` snapshot guarantee the concurrent split lost, and returning the endpoint to three reads. * 🍃 fix: Close Settle Atomicity Windows and Spread Claim Contention Self-review pass (eight independent review angles over the branch diff). Correctness: - The attachments merge is a single fenced write again: read the array, merge with the exact semantics the old `$filter`/`$concatArrays` had, and write it back with the settle patch in one update guarded on the array being unchanged — the same compare-and-swap this file already uses for control receipts. The `$pull`-then-`$push` split could strip attachments on a failure between the writes (the throw path retires the delivery, so nothing healed it) and duplicated them under concurrent re-application. - A supplied `resultClaim` is stamped BEFORE the settle write instead of after it. The claim-last order left a terminal, claimable, unclaimed part visible to the ~1s wakeup poll, which could acquire and deliver a result a manual poll had already delivered; claim-first leaves only inert intermediate states (a claim on a part that is not yet terminal admits no consumer). The stamp's guard is `resultClaim: null`, restoring the old `$ifNull` treatment of a stored null. - `releaseBackgroundToolResultClaims` filters on `content: { $type: 'array' }`: the filtered positional operator errors on a row without the array where the old pipeline no-op'd, and that throw surfaced as retryable release failures that left claims stamped forever. - The claim loop reads a BATCH of candidates per round, so the engine's parallel claimers spread across the batch instead of convoying on one head-of-queue row; exhaustion now throws the typed `AgentTriggerClaimContentionError`. The dead-capability adoption fence also pins `availableAt`, which the write copies. - The transaction probe probes first and creates the canary only after a failure — plain MongoDB never pays the DDL or gains the collection — logs a failed create instead of swallowing it, and concurrent first probes share one in-flight promise. Structure and guard: - One `partScope(prefix)` builder yields the `$elemMatch` document filter and the arrayFilters element filter from a single predicate, deleting the aggregation `$expr` twin, and the agent-ownership rule shared by settling and claiming lives in one `agentOwnershipFilter` helper. - The static guard scans all three backend workspaces (`packages/data-schemas`, `packages/api`, `api`), parses each file once, and shares the tree between both detectors. - The audit suite asserts each probe's returned verdict, the compat suite's run recipe carries the connection parameters the audit header documents, and the `$facet` pipeline assertion covers every captured aggregation in one loop. * 🍃 fix: Keep Attachment Contention Retryable and the Selected Snapshot Authoritative Second codex round, both findings real: - Exhausting the attachment-merge fence now returns an unmatched result with a warn log instead of throwing. The settle retry loop treats unmatched as retry-then-heal-later and degrades to false; a throw escaped it onto ambiguous-failure handling that can retire the completion, losing a healthy completed tool result to mere contention. Locked by a test asserting the bounded rounds resolve to `{ matched: false }` rather than a rejection. - When the selected task's rows also appear in the normal page window, the dedup pass kept the page copy — read by an independent concurrent aggregate — and discarded the single-snapshot version, letting a page row claim a transcript that `selectedSources` does not carry. Duplicates are now replaced in place with the snapshot version (page order kept), so the selected message/source pair stays per-document coherent end to end. * 🍃 fix: Treat a Stored-Null Claim Stamp as Unclaimed in the Claim Fence Third codex round. The subfield-preserving settle write keeps a persisted `resultClaim: null` that the old whole-object rewrite dropped, and the claim path's outer `$elemMatch` fence still required the field to be absent via `$exists: false`. The in-memory claimable scan, the claim arrayFilters, and the settle stamp all already treat a null stamp as unclaimed, so a part carrying one became terminal but permanently unclaimable: every manual poll and wakeup read `ready` in memory, missed the fence, and returned `not_ready` forever. The fence now matches missing-or-null like every other layer. The regression test persists an actual null stamp — asserted present before claiming, so the test cannot pass vacuously — and claims it. |
||
|
|
a9ccac8656
|
🧲 feat: Enable Secure Attached Environment Pairing (#15355)
* feat: add secure code environment pairing * fix: satisfy code environment type checks * fix: secure code environment administration * fix: isolate code pairing control plane * fix: validate code pairing control responses * fix: secure code pairing transport * fix: validate code pairing wire format * fix: harden pairing secret lookup |
||
|
|
7533d138fa
|
🧬 perf: Evolve Compaction Guidance on Warm Turns (#15371)
* perf: evolve compaction guidance on warm turns * style: sort compaction adapter imports |
||
|
|
9dcef360e2
|
🧳 fix: Carry Stateful Environments Through Runtime Config (#15374) | ||
|
|
29b3e2ef3e
|
📜 fix: Resolve MCP Server Instructions for Startup-Deferred Servers (#15361)
* fix: Fetch MCP Instructions from the First Live Connection Startup inspection intentionally defers servers that need per-user or runtime context, including OAuth/OBO, custom variables, user API keys, runtime placeholders, and startup-disabled servers. An enabled serverInstructions declaration therefore never resolves to text during inspection, even though the first live connection already has the instructions from the initialize response. Backfill resolvedInstructions from that connection through an identity-preserving YAML cache patch. Preserve updatedAt so live connections do not become stale, and globally invalidate the tenant-scoped read-through caches because YAML entries are shared across tenants. Literal instruction strings continue to win. Scope remains YAML-tier servers. Config-overlay servers are keyed by config hash, and DB-backed user servers need a separate identity-preserving write through mongoose timestamps and credential sanitization. * fix: Surface per-identity MCP instruction divergence `resolvedInstructions` is a single field on a config shared by every user of the server, and for a startup-deferred server the text now comes from one user's authenticated connection. That is exact for a server advertising one static block, but a server that tailors instructions per identity cannot be represented by it. Rather than let the stored copy churn per connection — each write invalidates the read-through cache globally, and the model context would vary by whoever connected last — keep the first text and log the divergence, so the assumption is diagnosable instead of silent. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SxKWxwqxAGckYpRsYTqx3F * perf: Skip MCP instruction backfill for non-YAML tiers `setResolvedInstructions` writes only the YAML tier, so a config-overlay, user, or plugin server reached it, spent a cache round-trip — a network hop under Redis — and was refused. That repeated on every connection creation, because the refusal leaves `resolvedInstructions` unset and nothing memoizes the outcome. Gate on the existing `isUserSourced`/`isPluginSourced` predicates plus an explicit `config` check. An unset source still proceeds: it predates per-tier stamping and the registry resolves it by name. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SxKWxwqxAGckYpRsYTqx3F * test: Pin the MCP instruction context read path Every existing assertion read back through `getServerConfig`, but `MCPManager.getInstructions` resolves instructions from `getAllServerConfigs`, which is served by a different read-through cache. A backfill that invalidated only the per-server cache would pass the suite and still leave the reported bug unfixed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SxKWxwqxAGckYpRsYTqx3F * fix: Refuse MCP instruction backfill from mismatched configs Self-review findings on the backfill, both in the shared-copy write: A config-tier override shadowing a YAML base keeps the base's 'yaml' source tag (`overlaySource`), so the connection manager's tier guard cannot see it, and instructions fetched from a tenant's overridden endpoint would be patched into the shared global YAML entry — reaching every other tenant's model context and persisting after the override is removed. `setResolvedInstructions` now takes the config the delivering connection was created from and compares it field-wise against the stored entry over ADMIN_CONFIGURABLE_FIELDS, refusing on mismatch. Field-wise rather than whole-object, since inspector-derived fields legitimately differ. The skip condition also only refused *identical* text, so a connection built from a stale read-through snapshot (resolvedInstructions still unset) could overwrite already-stored different text — violating the documented first-write-wins invariant and re-triggering global cache invalidation per divergence. The condition is now `!= null`. Documented the aggregate-key cross-instance write race alongside its existing tolerance for `reinspectServer`: the backfill patch fires at most once per server per registry lifetime, and the atomic-write upgrade (hash fields or Lua CAS) is the follow-up that closes the race for every writer at once. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SxKWxwqxAGckYpRsYTqx3F * fix: Scope deferred MCP instructions safely * fix: Narrow optional Keyv namespace in Redis store detection Keyv types `namespace` as `string | undefined`, so passing it straight into `FORCED_IN_MEMORY_CACHE_NAMESPACES?.includes(...)` fails `tsc --noEmit` in both cache classes — tsdown builds do not catch it, but the TypeScript type checks CI job runs tsc and would. An unset namespace (never the case after construction) now reads as not Redis-backed, which falls back to the guarded non-Lua path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SxKWxwqxAGckYpRsYTqx3F * fix: Harden the shared-instruction gate and CAS the patch Codex round two, both verified before fixing: A configured `oauth` block slips the backfill gate whenever `requiresOAuth` is not literally true. The inspector stamps `requiresOAuth = false` on every `startup: false` server without consulting `oauth`, so the stamped population connects bare and fetches anonymously — but the gate's safety rested entirely on that stamp: a config reaching the manager unstamped gets OAuth machinery armed (`isOAuthServer` treats `oauth != null` as OAuth) while `requiresUserScopedConnection` waves it through. The gate now rejects `oauth`/`oauth_headers` outright; genuinely static servers carry neither. The registry validates config identity against a snapshot that can lag by the cache TTL, while the Lua patch checked only that `resolvedInstructions` was unset — so a replica could validate against an old entry, another replica replace it, and the patch land instructions on the replacement. `patch` now takes the validated entry's `updatedAt` and both Lua scripts (and the in-memory and fallback paths) refuse when the stored entry no longer matches: identity validation and the write are one compare-and-set. Both guards verified red-without-fix; suite 36/36. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SxKWxwqxAGckYpRsYTqx3F * fix: Loosen apiKey on the scoping config and sort imports CI caught two things local gates filtered past: `UserScopedConnectionConfig` gained `apiKey` on the strict Pick side, but raw (pre-inspection) configs carry an optional `apiKey.source` — exactly what the type's loosened intersection exists for — so `agents/initialize.ts` stopped compiling. The gate only reads `apiKey?.source`, so the loosened shape is sufficient and the TypeScript type checks job goes green again. The `canBackfillSharedServerInstructions` import landed unsorted in UserConnectionManager.ts, failing the changed-file import-sort gate. Verified with a full `tsc --noEmit` error-list diff against clean dev (zero branch-only errors) rather than per-directory counts, which is how the initialize.ts error slipped local verification. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SxKWxwqxAGckYpRsYTqx3F * fix: Make MCP aggregate writes atomic * test: Fix Redis aggregate spy assertion * fix: Reject placeholder-bearing admin keys from shared backfill Codex round three P1, verified end-to-end before fixing: processMCPEnv injects an admin `apiKey.key` into the request headers (env.ts:448) BEFORE header values get per-user placeholder resolution (env.ts:478), so a key like `{{LIBRECHAT_OPENID_ACCESS_TOKEN}}` makes the connection identity-scoped — while `placeholderBearingFields` never inspects `apiKey.key` and the gate rejected only `source: 'user'`. Instructions fetched under one user's identity could then be stored for everyone. The gate now scans the admin key value with the same runtime-placeholder predicate. Kept narrow deliberately: widening `placeholderBearingFields` itself would change `requiresUserScopedConnection` for every caller — connection pooling included — which is its own decision. Static admin keys still backfill (positive control test); both new refusal tests verified red without the gate change. Suite 39/39. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SxKWxwqxAGckYpRsYTqx3F * refactor: Drop gate term covered by placeholder-bearing apiKey |
||
|
|
91cfd04f22
|
🏕️ fix: Restore Code Environments From Runtime Config (#15373) | ||
|
|
30124f21b2
|
🎻 refactor: Orchestrate Agent Runs Through a Request-Free Host (#15366)
* refactor: decouple agent initialization from HTTP * refactor: centralize remote agent execution lifecycle * fix: preserve pre-settlement error rendering * fix: preserve request-backed tool loading * style: sort agent execution imports * fix: adapt public agent tool loaders |
||
|
|
c1cb591d49
|
🗿 feat: Add Attached Stateful Code Environments (#15352)
* feat: add attached stateful code environments * fix: include code environment in lazy agent type * fix: harden stateful environment routing * fix: complete code environment route isolation * fix: declare code environment map type * fix: preserve configured code execution routes * fix: harden stateful environment updates * test: assert route-scoped sandbox readiness * fix: harden stateful environment lifecycle * fix: isolate migrated code sessions * test: assert route-qualified code sessions |
||
|
|
dc77b78d3e
|
🔇 refactor: Quiet Framework Logging in Unit Tests (#15363)
* 🔇 refactor: Quiet Framework Logging in Unit Tests Unit test output was overwhelmingly framework log lines rather than test results. A `packages/api` run printed 9,076 lines for 434 suites; 1,596 of them were the identical `at Console.log (winston/transports/console.js:87)` frame that Jest staples onto every winston write. Three causes: - Each winston logger built its Console transport with a hard-coded `level: 'info'`. Winston resolves a transport's explicit level ahead of its parent's, so the logger's own `NODE_ENV`-derived `warn` never applied — in tests or in production. `CONSOLE_LOG_LEVEL` now drives it, defaulting to the previous `info` so deployments are unaffected. `meiliLogger` had the same hard-coded level and is routed through the same helper. - `LOG_TO_FILE` defaults on, so every Jest worker opened DailyRotateFile transports. A run left `packages/api/logs/` holding a 107KB error log, a gzipped rotation, audit JSON, and — from fake-timer suites — files dated 1969 and 2023. - Client suites carried leftover debug `console.log` in shipped source (`Test mode. Skipping silent refresh.` alone accounted for 109 blocks) plus three test-harness defects whose React error dumps buried everything else: a markdown suite rendering Mermaid with no Router, a `~/Providers` mock missing `useSearchContext` (the component's own catch swallowed the throw, so the sources path was dead while the suite passed), and `getBoundingClientRect` stubs without `left`, which made a computed `right` NaN. A shared `config/jest.setup.logging.cjs` sets the two env vars for both backend workspaces. It sets env only and requires nothing: eagerly requiring the logger froze `CREDS_KEY` into `encryptV3` before specs could set it and broke five suites. `TEST_VERBOSE_LOGS=true` restores the logs for debugging. Output per full run: packages/api 9,076 -> 770 lines (winston lines 1,596 -> 0) packages/data-schemas 2,521 -> 946 lines (winston lines 290 -> 0) client 10,384 -> 3,153 lines (console blocks 285 -> 74) Test counts are unchanged: client 476 suites / 5,827 tests green, and the Ariakit `act(...)` warnings the selector suites emitted are gone rather than suppressed. * 🔧 fix: Settle Shutdown Timers Before Counting Them `destroy()` finishes cancelling its fenced retirement timers on the tick after it resolves. The count only returned to the baseline because a console write from the logger happened to yield first — silencing that logging left four timers still pending at the assertion, deterministically failing the suite in CI's sharded, coverage-enabled run. Advance the fake clock by zero before counting, so the assertion proves the timers were cancelled rather than that something incidental yielded. * 🛡️ fix: Validate CONSOLE_LOG_LEVEL Instead of Trusting It Winston resolves a level name against the logger's level map, so a name outside it resolves to `undefined` and the transport drops every message. Passing `CONSOLE_LOG_LEVEL` straight through meant a typo (`warning`) or a stray space silently muted all console output — a misconfiguration that reads as a dead deployment. Validate against the level set, normalize case and whitespace, and fall back to the default with a warning on the way past, since the logger cannot report its own misconfiguration. The level map itself was duplicated verbatim in both loggers; it now lives in `utils` beside the validation that depends on it. * 🎚️ fix: Let CONSOLE_LOG_LEVEL Outrank DEBUG_CONSOLE `DEBUG_CONSOLE=true` forced the console transport to `debug`, so on those deployments every non-`silent` value of `CONSOLE_LOG_LEVEL` was discarded — `CONSOLE_LOG_LEVEL=error` still emitted debug and info, defeating the control it advertises. `DEBUG_CONSOLE` now only moves the *default* level and keeps choosing the debug format, so an explicit level stays in charge of verbosity. With no explicit level the resolved default is still `debug`, unchanged for existing setups. Covers the wiring with a spec that re-imports the logger per environment, rather than only testing the resolver in isolation. |
||
|
|
b0a559876f
|
🧩 feat: Collapsible Wake-Up Task Cards and Subagent UI Consistency (#15364)
* 🧩 feat: Collapsible Wake-Up Task Cards and Subagent UI Consistency * 🧩 fix: Codex Round 1 — Shared Composer Surface, Pinned Event Tasks, Durable Wake-Up Links * 🧩 fix: Codex Round 2 — Pin Requested Event Tasks, Share Gating, Focus Return * 🧩 fix: Codex Round 3 — Promote Composer Surface to @librechat/client Semantic Primitive |
||
|
|
cd3768ed1f
|
🍵 feat: Continue Late Steers in Warm Agent Runs (#15357)
* feat: continue late steers in warm agent runs * chore: bump agents sdk to v3.7.9 * fix: guard terminal steer admission |
||
|
|
180b56531a
|
fix: log config-blocked MCP recovery at debug instead of error (#15360)
A server blocked by the domain policy (or any configuration that makes discovery impossible) fails recovery deterministically on every catalog request, and each attempt logged at error level. In the e2e suite, where e2e-http is deliberately policy-blocked to exercise the allowlist override flow, this produced an error line every few seconds. Discovery raises McpError InvalidRequest precisely for these config-impossible cases, so classify it in discoverCandidate and log at debug — the level this file already uses for config-proven skips — keeping error for genuinely unexpected failures. |
||
|
|
240e9e920f
|
🧺 fix: Skip Oversized and Duplicate Files Instead of the Whole Batch (#15358)
Some checks are pending
Backend Unit Tests / Build packages (push) Waiting to run
Backend Unit Tests / Codegraph select (push) Waiting to run
Backend Unit Tests / TypeScript type checks (push) Blocked by required conditions
Backend Unit Tests / Circular dependency checks (push) Waiting to run
Backend Unit Tests / Tests: api (shard 1/3) (push) Blocked by required conditions
Backend Unit Tests / Tests: api (shard 2/3) (push) Blocked by required conditions
Backend Unit Tests / Tests: api (shard 3/3) (push) Blocked by required conditions
Backend Unit Tests / Tests: data-provider (push) Blocked by required conditions
Backend Unit Tests / Tests: data-schemas (push) Blocked by required conditions
Backend Unit Tests / Tests: @librechat/api (shard 1/4) (push) Blocked by required conditions
Backend Unit Tests / Tests: @librechat/api (shard 2/4) (push) Blocked by required conditions
Backend Unit Tests / Tests: @librechat/api (shard 3/4) (push) Blocked by required conditions
Backend Unit Tests / Tests: @librechat/api (shard 4/4) (push) Blocked by required conditions
Codegraph E2E Votes / vote (full suite) (push) Waiting to run
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
Frontend Unit Tests / Codegraph select (push) Waiting to run
Frontend Unit Tests / Build packages (push) Waiting to run
Frontend Unit Tests / TypeScript type checks (client) (push) Blocked by required conditions
Frontend Unit Tests / Tests: @librechat/client (push) Blocked by required conditions
Frontend Unit Tests / Tests: Ubuntu (shard 1/2) (push) Blocked by required conditions
Frontend Unit Tests / Tests: Ubuntu (shard 2/2) (push) Blocked by required conditions
Frontend Unit Tests / Vite build verification (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
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
Sync Helm Chart Tags / Ignore non-main push (push) Waiting to run
Sync Helm Chart Tags / Sync chart tags (push) Waiting to run
* 🧺 fix: Skip Oversized and Duplicate Files Instead of the Whole Batch Uploading several files at once failed entirely when any single one exceeded `fileSizeLimit`: `validateFileSizes` returned false for the batch and `discardProcessedUploads()` dropped every processed upload, valid files included. Duplicates rejected the selection the same way. Adds `partitionUploads`, which splits a selection into the files that may be uploaded and the ones that cannot, and applies it at both validation points — duplicates against the raw selection, sizes against the processed files whose bytes resizing has already settled. Offenders are discarded individually and named in a toast; the rest upload. Per-file rules only: `totalSizeLimit` describes the batch as a whole, so `validateFileSizes` still enforces it over whatever survives the partition. When nothing survives, the batch keeps reporting the existing all-or-nothing errors rather than per-file skip notices, so single-file uploads are unchanged. * 🧮 fix: Count the Partitioned Selection Against the File Limit Codex round 1. `validateFiles` received the unpartitioned selection, so its `fileList.length + files.size` check spent `fileLimit` slots on files the partition was about to discard — with 9 of 10 slots taken, picking an already-attached file alongside a new one rejected both. The partition now runs first and hands `validateFiles` only the survivors, keeping the count rule in one place. A selection that loses every file still passes the untouched list through, so the batch-level checks report it in their usual order. Also stops the partition tests allocating real buffers: one case built a 500 MiB ArrayBuffer to assert a size-only branch. * 🎚️ fix: Apply the File Count to the Files That Survive Processing Codex round 2. Round 1 stopped duplicates spending `fileLimit` slots, but the count still ran before sizes were known: the pre-processing partition defers size checks, so an oversized file was counted and then discarded. With one slot left, picking an oversized file beside a valid one rejected both. The count now runs where the survivors are finally known — after the post-processing partition — through a `validateFileLimit` extracted from `validateFiles`, so both callers share one rule and one message. The two batch-composition rules the partition now owns, count and duplicates, sit behind a single `skipBatchRules` flag in place of `skipDuplicateValidation`. Selections that overshoot the limit are now processed before being rejected, where they used to be turned away up front. That is the price of counting only what will actually be attached, and matches how duplicates and sizes have always been handled in this function. * 🔕 fix: Hold Skip Notices Until the Batch Is Actually Uploading Codex round 3. The pre-processing partition announced its dropped duplicates as soon as `validateFiles` passed, but the surviving files still had to clear the file count and `totalSizeLimit` afterwards. A selection that failed there showed both "Skipped duplicate file(s)" and the batch rejection, with nothing uploaded — contradicting the notice's own contract that it describes a batch that is otherwise still going. Both partitions' skipped entries are now reported together, after the batch is known to be uploading. A selection rejected in full reports itself through the batch-level errors alone. |
||
|
|
2ae6c8aea9
|
🛎️ feat: Wake Agents on Background Tool Completion (#15350)
* feat: wake agents for background tool completion * fix: isolate background completion contracts * fix: anchor background completion identity * fix: close background completion delivery gaps * fix: preserve manual background polling * fix: preserve legacy tool group identity * fix: close background wakeup identity gaps * test: type background wakeup enqueue mock * fix: preserve background completion identity * test: type activity phase fixture * test: mock phase media query * fix: arbitrate background result ownership * fix: retain tool step routing helper * test: expand phase groups before identity checks * fix: preserve background completion ownership * fix: bound durable background results * fix: type wakeup input budget export * chore: sort background handler imports * fix: persist timed-out background completions * fix: reconcile background completion ownership * fix: type background completion capabilities * fix: harden background completion terminalization * test: type missing completion evidence * fix: require evidence for completion retirement * chore: satisfy background completion static checks * test: cover automatic background completion wakeups * test: assert background wakeup agent identity * fix: wake capability-fenced trigger deliveries * test: keep capability shield fixture public * test: assert capability worker wakeup * fix: close background completion ownership gaps * test: satisfy completion lease static checks * fix: preserve artifact completion wakeups * fix: close background delivery recovery gaps * fix: simplify background receipt guidance * fix: recover dead background completion batches * fix: fence background completion recovery * fix: recheck background recovery ownership * fix: fence unpublished background continuations * test: await background message hydration |
||
|
|
e3ccaba5af
|
🛄 feat: Restore Compaction Guidance Across Continuations (#15356)
* 🧭 feat: preserve compaction guidance across continuations * 🔧 fix: narrow persisted compaction fields |
||
|
|
b97d2868d4
|
🛑 feat: Cancel MCP Discovery Work at the Caller Deadline (#15353)
* feat: cancel discovery probes and tools/list at the caller deadline deadlineMs could only be checked, never enforced: between checkpoints, the health probe's ping ran to the SDK's 60s default, an in-flight tools/list page ran to its page timeout, and a spent budget still paid for credential preparation and a publication-order reservation before anything noticed. Derive one AbortSignal per discovery from the deadline and the caller's own cancellation, and hand it to the work the SDK can genuinely cancel: client.ping (and its fallback verification) via isConnected(signal), and every tools/list page via fetchToolsSnapshot. An aborted probe reports false for that caller only and never mutates connection state, so a shared app connection stays usable for everyone else. Phases that cannot be cancelled are no longer started once the budget is spent: discovery returns before Graph preprocessing and token resolution begin, and a snapshot returns incomplete before reserving publication order. Teardown stays deliberately exempt — dispose() must finish. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xr1Dabvdn1mzzyYgpJgU5B * fix: keep aborted probes honest and combine caller signals Codex round 1 on the abort threading found three gaps, all real: - An aborted ping left lastConnectionCheckAt stamped, so a dead shared connection read as healthy for the whole CONNECTION_CHECK_TTL. The probe now restores the stamp when the caller signal caused the failure; the real-SDK test closes the server after an aborted probe and asserts the next caller sees it dead. - The app-connection fast path built its signal from the deadline alone, so cancelling the originating request did not cancel a hung probe or tools/list there. One shared createDeadlineAbortSignal helper now combines both, used by the factory and the manager. - The entry checkpoint could not see a budget that expired during credential preparation; discoverToolsInternal rechecks before token resolution begins. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xr1Dabvdn1mzzyYgpJgU5B * fix: consult the caller signal at discovery decision points Round 2 found the pattern left half-applied: the signal cancels SDK requests, but two orchestration points ignored it. - An app-connection probe aborted by the caller fell through to discovery fallback, opening a fresh connection for a request that no longer exists. The manager now returns before the fallback when the caller signal is aborted or the budget spent, and the caller signal travels into non-OAuth discovery too — previously only OAuth connections could carry one, so signal moves to UserConnectionContext. - fetchOrderedToolsSnapshot bounded the refresh wait by the deadline but not the signal, so a signal-only caller stayed blocked for the shared refresh's full cycle. settlesBefore now interrupts on either. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xr1Dabvdn1mzzyYgpJgU5B * fix: gate every discovery checkpoint on one cancellation predicate Round 3 exposed the structural cause of the last two rounds: discovery carries the same fact in two forms — a deadline number and a signal — and each gate consulted only the deadline. isDiscoveryCancelled() is now the one predicate at the static entry, the post-preparation entry, and the pre-fallback gate, matching the manager gate and the snapshot gates that already checked both. connect() cannot carry the signal, so a signal-only abort previously waited out the full connection timeout. connectWithinBudget races connect against the timeout and the abort with owned cleanup: the timer is always cleared, the abandoned attempt's rejection is swallowed, and disposal hands whatever the dead connect still constructs to the mid-connect disposal guard. |
||
|
|
41c92dfd06
|
⏱️ fix: Bound MCP Tool Discovery With a Caller Deadline (#15346)
* fix: bound MCP tool discovery end to end with a caller deadline connectionTimeout bounds a single connect() only. Discovery then spends it again on the unauthenticated fallback and hands tools/list its own 30s budget, so a caller working to a deadline had no way to cap the whole operation. Thread an optional absolute deadline through discovery into both connect() and the tools/list walk, and dispose a timed-out authenticated connection before the fallback opens its own socket — withTimeout does not cancel the connect it abandoned, so the two were briefly concurrent. Passive catalog recovery now sets one 3s per-server budget instead of a per-attempt timeout it could spend several times over. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xr1Dabvdn1mzzyYgpJgU5B * fix: close deadline gaps in the app path, refresh wait, and disposal race Codex review of the deadline threading found three real gaps: - discoverServerTools returns through an app-connection fast path that never reached the factory, so its tools/list kept the 30s default. - fetchOrderedToolsSnapshot checked the deadline before awaiting a refresh but the refresh runs on the connection's own budget, so an in-flight one could still hold a budgeted caller for that budget. Stop waiting on it rather than adopting it. - connectClient never rechecked isDisposed after awaiting constructTransport, so a connect abandoned by its caller could assign a transport and connect after dispose() had already found nothing to close. Disposing before the fallback widened that window, so bound the attempt to its own disposal. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xr1Dabvdn1mzzyYgpJgU5B * fix: close the transport itself when disposal beats connect Rewriting the disposal-race test against a real SDK server and transport exposed a defect the mocked version could not see: client.close() only closes a transport the client has adopted, and it has not adopted one when disposal lands before client.connect(). The abandoned attempt's session therefore stayed open and the reference was merely dropped. Close the transport directly before closing the client. The test now asserts the server observed the close, which fails against the previous fix. |
||
|
|
70f735336d
|
🛎️ fix: Enroll Remote Agent Runs in the Generation Lifecycle (#15349)
* fix: enroll remote agent runs in generation lifecycle * fix: close remote lifecycle ownership gaps * fix: close remote conversation drain races * fix: fence remote runs during conversation deletion * fix: reconcile remote deletion and settlement races * fix: complete owner deletion recovery * fix: preserve remote cleanup receipts * fix: consume deletion receipts before cleanup * fix: expose idempotent deletion option * chore: sort remote lifecycle imports |
||
|
|
8fcab7e44f
|
🔄 fix: Recover Missing MCP Marketplace Catalogs (#15323)
* fix: recover missing MCP marketplace catalogs * fix: make MCP catalog recovery passive * test: type MCP catalog recovery fixtures * fix: bound and back off passive MCP catalog recovery Passive recovery runs inline on `GET /api/mcp/tools` and its results are request-local by design, so every list request re-dialed the same cold servers with the default connection timeout. Three limits keep that cost proportional to what recovery can actually recover: - Cap the discovery timeout at 5s instead of inheriting the connection default (`initTimeout ?? 30s`); a server configured to connect faster keeps its own shorter limit. - Skip a server the config tier already marked `inspectionFailed`, leaving it to that tier's retry window rather than re-dialing it per request. - Skip a server whose declared `customUserVars` are unset, matching the gate `reinitMCPServer` applies for issue #10969 — connecting without them fails auth, so the attempt is spent for nothing. Servers that still fail discovery enter a one-minute per-process cooldown, which is what stops an unreachable server from being re-dialed by every subsequent list request. A server that recovers clears its own entry, and expired entries are swept at most once per window so the map stays bounded. Skipped servers render exactly as they did before recovery existed: present in the catalog with an empty tool list. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xr1Dabvdn1mzzyYgpJgU5B * fix: bound passive MCP recovery by deadline, key cooldowns by config Both follow-ups address the same mistake: recovery expressed its own request-level constraints in terms borrowed from other layers. `connectionTimeout` bounds one connection attempt, and `MCPConnectionFactory.discoverToolsInternal` spends it twice — once on the authenticated connection, then again in `attemptUnauthenticatedToolListing` — so capping it bounded no total this layer could reason about. Recovery now enforces its own wall-clock deadline per server with `withTimeout`, which holds however many attempts the factory makes; `connectionTimeout` is left to do only its own job, still honouring a shorter operator `initTimeout`. An attempt abandoned by the deadline disposes its own connection when it settles, and `Promise.race` keeps a handler on it, so a late rejection is not unhandled. A per-request budget now caps total recovery regardless of server count. A server is dialed only if the remaining budget can fund a full deadline; never dialing one is not evidence against it, so a skipped server records no cooldown and a later request reaches it once those ahead are cached or cooling down. Cooldown identity now includes the publication generation — the same effective-config identity the tool caches fence on — instead of just user and server name. Correcting a server's URL or transport keys a new entry, so the refetch the client issues on update is no longer skipped for up to a minute by the previous configuration's failure. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xr1Dabvdn1mzzyYgpJgU5B * refactor: keep passive MCP recovery stateless and bounded by its own work Reverts the cooldown, request budget and deadline race added in |
||
|
|
36f129089b
|
🔊 fix: Autoplay Latest Message for Browser TTS and short responses (#15347)
* 🔊 fix: Autoplay Latest Message for Browser TTS and short responses Automatic playback never reached the Web Speech API and dropped audio for short responses. Two independent defects: Autoplay ignored the selected TTS engine. `ChatForm` mounted `StreamAudio` whenever automatic playback was on, and that component always POSTs to `/api/files/speech/tts`, so the Browser engine silently fell through to the external endpoint and `speechSynthesis.speak()` was never called. The engine-aware `useTextToSpeech` hook had no consumers. Autoplay now selects its driver by engine the same way `MessageAudio` does, with the gate shared between both drivers so they trigger on identical terms. `MediaSourceAppender` stranded its queue. `tryAppendNextChunk` only ran from `addData` and `updateend`, while `sourceopen` — which fires only once a media element attaches the object URL — created the `SourceBuffer` without draining what had queued up behind it. A response read in full before that event left every chunk in the queue with no `updateend` to ever restart the drain, so the element sat at `readyState: 0` and never played. Short responses lost the race; longer ones streamed past `sourceopen` and worked. The handler now drains, and `close()` (previously never called) ends the stream once the queue empties. Also in the same path: the error branch had its timeout comparison inverted, so real failures were logged as timeouts and swallowed; and the non-MSE fallback that hands the element a finished blob lived inside the cache-only branch, leaving browsers without MSE support for `audio/mpeg` with no audio at all unless Cache TTS happened to be enabled. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0184N7d9kgpdqmbCb9ujMmNX * 🧹 style: Sort imports in autoplay files `npm run sort-imports:check` on the changed files. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0184N7d9kgpdqmbCb9ujMmNX * 🔇 fix: End the media source when no audio ever arrives Codex P2 on #15347. The `hasAppended` guard in `tryEndOfStream` was defensive against a throw that cannot happen: `endOfStream()` only raises `InvalidStateError` while the source is not open or a buffer is updating, both of which the remaining guards already cover. Nothing about an empty buffer throws. The guard's actual effect was to strand the two cases it was meant to protect. A TTS response that completes with zero bytes, or a read timeout that fires before the first byte, both reach `close()` with the MediaSource URL already attached to the element — and left it `open` forever, so the element waited on a source that could never receive data and kept its streaming resources |