mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-10-21 17:44:11 +00:00
5324 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
a290677d21
|
🗿 fix: Bound Background Task Retention (#15477)
* fix: bound background task registry memory * fix: preserve background registry capacity state * fix: align background payload accounting * fix: harden background capacity admission * fix: honor background payload admission * fix: make background eviction atomic |
||
|
|
ce209102b8
|
🧫 fix: Contain Detached Tool Output (#15476) | ||
|
|
0f585497ea
|
🚮 fix: Replace Backtracking LangChain Error Regexes (#15473) | ||
|
|
6122d210da
|
🛄 fix: Reclaim Pending MCP OAuth Prompts on Resume (#15471) | ||
|
|
74535662fe
|
🎖️ fix: Apply Fallback Role on Group Overage (#15472) | ||
|
|
67e2107d74
|
🔐 fix: Keep OpenID Metadata Out of Logs and Traces (#15470)
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 / publish (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
* 🔒 fix: Keep Secrets Out of Debug Logs and Spans * 🧯 fix: Derive Safe Exception Types * 🛡️ fix: Close Diagnostic Redaction Gaps * 🧼 fix: Redact Escaped JSON Secrets |
||
|
|
d83958d3b3
|
📎 fix: Keep SharePoint Picker Selections While Navigating (#15389)
The picker never set `selection.enablePersistence`, so it dropped the current selection every time the user opened another folder or switched pivot. A selection could only ever be assembled from a single location — pick three files, open another folder, and the three are gone. One option on the picker configuration; no change to what LibreChat does with the result. Fixes the selection half of #15237. Folder selection — the other half — is a new capability rather than a fix, and is parked on `feat/sharepoint-folder-selection`. |
||
|
|
537ddba6bb
|
🫴 fix: Hand Composer Focus Back After Upload Dialogs (#15469)
Typing a message, pasting an image, then pressing Enter did nothing: the composer held the full draft, the send button was enabled, and Enter was inert until the user clicked back into the textarea — which reads as "the textarea needs a text change before it will submit". The send gating was never at fault. Radix's modal `DialogContent` always cancels its default close-focus restore and focuses `context.triggerRef.current` instead. `DragDropModal` — the "Select Upload Type" dialog a paste or a drop opens — is opened programmatically and so has no `Dialog.Trigger`, leaving that ref null: nothing is focused and focus falls to `document.body` when the dialog unmounts. Enter-to-send is a keydown handler on the textarea, so from `body` it is never reached. `UploadModalProvider` now records the element the upload was started from, captured in `openModal` before the dialog mounts (an effect runs too late — Radix has already moved focus), and restores it once the dialog closes, queued behind Radix's own deferred restore. This covers every close path: picking a destination, Cancel, Escape, and dismissing the overlay. `OGDialog`'s existing `triggerRef` seam does not reach this case, since it only fires through Radix's `onOpenChange` and choosing an upload destination calls `closeModal()` directly. |
||
|
|
c3beae1e18
|
🔌 refactor: Give the Subagent Surface a Host Seam and Its Own Jotai State (#15455)
* 📐 docs: Record Who Owns Which Piece of Client State * 🔌 refactor: Give the Subagent Surface a Host Seam and Its Own Jotai State The subagent chat surface reached up into seven application atoms and could not be lifted anywhere, and the six atoms it owns were still Recoil while its sibling stores had already moved to Jotai. Feature-owned state converts to Jotai and stays where it is: the selection, per-task control receipts, live progress and the parent-stream fence. The control atom keeps its session-storage retry — restored per store rather than per family member, so a reload still sees what the last one persisted. App-global state now arrives through one `ChatSurface` value the host supplies: `enterToSend`, `maximizeChatSpace`, `showScrollButton`, the reader's composer bindings, a `claimForeground` command for the slot the panel and the artifacts panel share, and `handOffComposerText` for a continuation's unsent words. Two hosts mount the same adapter — the conversation view and the shared-link view. Opening the panel was three copies of "clear the artifacts slot, then select"; that is now `useOpenSubagentPanel`, and the three call sites say what they mean. Nothing under `Chat/Subagents/**` imports `~/store` or `recoil` any more, and its whole suite runs with no `RecoilRoot` at all. * 🧹 refactor: Move Subagent State Into the Feature and Free Its Drained Atoms Two review findings, both fair. The selection atom still lived in `~/store`, which handed the supposedly liftable surface an implicit dependency on the application store alongside its declared host interface — and contradicted the ownership rule this branch added to CLAUDE.md two commits ago. The module moves to `components/Chat/Subagents/state.ts`; the hosts that write it (the SSE step handler, the message renderers) now import from the feature, which is the direction that survives an extraction. The conversation-switch drain blanked its two atom-family members but left them cached. Every invocation key is unique, so a long-lived tab accumulated two atom configurations per subagent call it ever saw — exactly what the comment at that boundary says it exists to prevent. `removeSubagentProgressAtoms` releases them after the values are cleared, mirroring `removeUsageAtoms`. The new test fails without the release. * 🔎 fix: Let a Subagent Card Render Where There Is No Panel to Open Search results render the same card through `Part`, and that route is not beneath either chat surface — so a search hit containing a subagent tool call threw on `useChatSurface()` before it could render at all. Rendering the card and opening a panel are different requirements. The host stays required for the panel, the activity view and the event group, all of which only ever mount inside one. `useOpenSubagentPanel` reads the context optionally instead and answers `null` where there is no host, which is also the honest answer for search: there is no panel on that route to open, so the card renders with its details control disabled. The new test renders the card with no surface at all, and throws with the reported error when the hook is put back the way it was. * 🧮 fix: Enrol Read-Created Progress Atoms in the Drain Registry The read is what creates a family member, and only the streaming paths were registering their keys. A card rendering a conversation that finished streaming long ago — or a search result that never streams at all — created one the navigation drain could not see, so browsing history accumulated an atom configuration per card for the life of the tab. `useSubagentProgress` does both halves: it reads the invocation's progress and enrols its key, so every route into the family shares the one cleanup boundary. The panel and the parent card both read through it now. * 🧽 fix: Free a Read-Created Progress Atom at Its Own Card's Unmount Enrolling read-created keys in the drain registry only helps where something runs the drain, and `/search` is a standalone route that never mounts the chat event handlers. Scrolling a virtualised result list therefore kept minting members nothing would ever free. `useSubagentProgress` cleans up after itself instead: on unmount it frees the family members when the read is the only reason they exist. Two guards keep that from taking live history with it — a key the stream registered belongs to the drain, and a member holding folded activity is the record of what the child did, so reopening its card still shows it. * 🔗 fix: Free a Progress Atom Only After Its Last Reader Lets Go Opening a foreground panel before the first event gives one still-empty member two readers: the card that spawned the child and the panel showing it. Closing the panel freed it while the card was still subscribed — and a subscriber does not re-render just because its atom left the family, so the first SSE update landed on the replacement member and the card's ticker stayed dead. `useSubagentProgress` counts its mounted readers and frees only when the last one lets go, alongside the two claims that already blocked it: a key the stream registered, and a member holding folded activity. |
||
|
|
a34d5dbfc7
|
🪂 fix: Preserve Subscribers Through Terminal Handoffs (#15462)
* fix: preserve fenced terminal subscriber handoff * test: parse replay conversation pathname * fix: capture late terminal subscribers |
||
|
|
c8939c9730
|
🪄 fix: Reuse Downloaded Artifacts for Image Reads (#15454) | ||
|
|
8f6228095f
|
🖼️ fix: Stop Reporting a Failed Sandbox Image Read as an Unreadable Format (#15453)
* 🖼️ fix: Stop Reporting a Failed Sandbox Image Read as an Unreadable Format `read_file` on a `/mnt/data` image collapsed every failure — rate limit, missing path, truncated transfer, transport error — into the binary-file hint, whose image branch reads as a permanent capability limit ("is an image file and cannot be read as text"). The model stopped retrying, and the operator had no way to tell which failure had actually occurred. The failure that surfaced it: the reader windows the bytes through `/exec` stdout, one call per window, against the Code API's per-user execution limiter (20 requests / 30s in the reference deployment). At a fixed 32KB window a 122KB chart cost 4 calls and a 1MB image cost 33 — more than a whole limiter window, so anything over ~640KB could never complete. A turn that generates several charts and reads them back exhausts the budget partway through and reports the 429s as unreadable images. - `handlers.ts`: failed reads name the real cause and the affordance that matches it — rate-limited waits and retries, a missing path lists the directory, a truncated transfer retries. The "cannot be read as text" phrasing is now reserved for bytes that are not a supported image. An image past the inline budget says to downscale and read the smaller copy rather than pointing at `bash_tool`, which cannot show an image. - `process.js`: the window is derived from the runner's stdout budget (48,960B vs 32,768B, so a 122KB chart is 3 calls and 1MB is 22), halves and re-reads the same offset when a runner truncates anyway (remembered per Code API, with the env remedy logged), waits out a rate limit it can afford instead of discarding the bytes already pulled, and stops at a 24-call ceiling so no read stalls a turn across limiter windows. - `utils/code.ts`: shared `getCodeApiRetryAfterMs` (header, then the `retry_after_seconds` body); `skillFiles.ts` drops its private copy. * ♻️ refactor: Move the Sandbox Image Reader Into TypeScript Addresses the review round on #15453. - P1: windowing, window sizing, the in-sandbox reader script, response parsing and byte assembly move to `packages/api/src/files/code/image.ts`, and the Code API rate-limit policy to `packages/api/src/utils/code.ts`. `process.js` keeps only the `/exec` transport it owns. Both new modules are tested against their real implementations (19 + 13 cases), including the round-trip ceiling, the changed-file guard and the shell-injection guard on the reader script. - P2: a configured stdout budget below the old 8KB window floor produced a window the runner could never emit, and narrowing was pinned to the same floor, so no retry could succeed. The window now aligns to the base64 group size instead, and the round-trip ceiling bounds what a small budget costs. - P2: `.env.example` documented a 32,768-byte default that no longer holds and never mentioned the stdout budget. It now describes `LIBRECHAT_CODE_SANDBOX_OUTPUT_MAX_SIZE` as the setting to tune, with the chunk override as the escape hatch. - P3: `process.spec.js` no longer reimplements the retry parser in its module mock. Behaviour is tested where it lives; the mock is a declared passthrough and the spec covers what the transport still owns — session forwarding, the profile header, parser hand-off and error logging. - P3: `getCodeApiRetryAfterMs` reads the response body through `in` narrowing rather than an `as` assertion. - P3: the window-sizing JSDoc no longer claims a multiple of 3 in the one case that is not — an explicit `LIBRECHAT_CODE_IMAGE_CHUNK_BYTES`. * 🎯 fix: Size Sandbox Image Reads to What the Limiter Can Deliver Addresses review round 2 on #15453. - Narrowing now halves the caller's own failing window instead of the shared learned value, and the learned value only moves down to the narrowest size any read needed. Reads run concurrently (one per tool call), so two reads failing at the same size were compounding into a window two steps smaller than the runner would have accepted, and persisting it for every later read. - Narrowing continues until a runner accepts a window or a floor below which no runner could serve an image at all. Two halvings stopped at 12,240 bytes — still ~16KB of base64 — so an 8-10KB stdout cap that was not configured up front stayed unreachable. - The round-trip ceiling is now one limiter window (20), and a read that cannot finish within it says so from the FIRST response rather than spending the whole budget rediscovering it: the first window reveals both the file size and what a call really delivers. A near-limit image no longer drains the turn's executions before failing, which is what made the wait budget look too small. - The round-trip message names the size this deployment can actually inline, so the model has a downscale target instead of a guess. - `python3: not found` from the sandbox reader was matching the generic "not found" in `isSandboxMissingFileError` and being reported as a missing image path, sending the model to `ls /mnt/data` while hiding a runner dependency. Image reads now classify only path-specific ENOENT forms as a missing file. * 📐 fix: Size a Truncated Read's Retry From the Cap It Revealed Addresses review round 3 on #15453. A ~1KB sandbox stdout cap is a real runner default, and the 1,536-byte narrowing floor sat above it: discovery stopped one step short and declared every image unreadable, even though small ones fit comfortably in the 576-byte window the configured path already derives. Narrowing now runs until no smaller window exists. It also no longer has to grope its way down: a truncated response emitted exactly as much as the runner allows, so its length measures the cap this deployment never declared, and the retry is derived from it directly — one round-trip instead of the eight blind halvings it takes to fall from ~49KB to ~1KB. Blind halving remains the fallback and bounds the result either way, so every retry is strictly smaller than the window that just failed. |
||
|
|
cdfe54c349
|
🚦 fix: Downgrade Stale Generation Fence Telemetry (#15452)
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 / publish (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 / publish (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: downgrade stale generation fence telemetry * fix: preserve fenced persistence failures * fix: avoid duplicate fence warnings * fix: propagate terminal publication fences * style: format fencing audit changes * fix: keep successor transitions generation scoped * fix: bound predecessor handoff retirement * fix: reconcile discovered successors completely * fix: preserve successor handoff acknowledgement |
||
|
|
97e1e950ad
|
🧲 fix: Draw OpenID Refresh Lookups Into System Context (#15451)
* fix: scope refresh user lookups to system context * fix: preserve refresh system context * fix: bind refresh recovery to user tenant |
||
|
|
224512851a
|
🗂️ fix: Resolve Code Interpreter Destination Collisions (#15449)
* 🗂️ fix: Resolve Code Interpreter Destination Collisions
Codeapi mounts each `/exec` input at a destination derived from its `name`
and rejects the whole request when two entries land on one. LibreChat never
enforced that: `primeFiles` sent `name: file.filename` verbatim, and the
graph seed deduped by `(storage_session_id, id)` — identity, not destination.
Two conversation files sharing a filename therefore killed code execution for
the rest of the conversation. The rejected request never reaches the sandbox,
so no result comes back to collapse the pair, and every later turn re-primes
both and fails identically. Only code-generated outputs are covered by the
`(filename, conversationId, context, tenantId)` partial unique index; uploads
carry `context: message_attachment`, so one conversation can hold several
records sharing a filename — two uploads of `image.png`, or an upload whose
name a later execution wrote back in place.
`packages/api/src/files/code/destinations.ts` now owns the name → destination
mapping for every contributor to an `/exec` file list, mirroring codeapi's
conflict rule including directory-prefix conflicts:
- `primeFiles` claims per pushed file, disambiguating with a `-2`, `-3`
counter and echoing the resolved path into the tool context, so the model
is told where each file actually mounts. Claim order comes from immutable
`createdAt`, not `getFiles`'s `updatedAt` sort, which usage accounting and
re-upload both bump — order-dependent destinations would silently repoint
a path an earlier turn had already handed the model. Newest wins the bare
name, matching `ToolNode.updateCodeSession` and giving an in-place rewrite
precedence over the original it superseded.
- `seedCodeFilesIntoSessions` drops a later file whose destination is already
claimed. Renaming there would invent a path nothing told the model about,
and a conflict at that layer means one logical file arrived twice under
different storage pointers.
Fixes #15443
* 🧹 chore: Tolerate Holes in the Destination Priority Sort
`primeFiles` sorts the raw `getFiles` result before its own per-entry guard
runs, so the comparator has to survive a null row rather than throw ahead of
the loop that was already defending against one.
Also tightens the tool-context assertion in the upload-vs-output case, which
was passing on a trailing space rather than on the path.
* 🔁 fix: Address Codex Review on Destination Assignment
Four findings from the review of `
|
||
|
|
09722f4b16
|
✔️ style: Anchor the Applied Steer Receipt and Name Tool Calls in Its Interrupt Copy (#15450)
* ✔️ style: Anchor the Applied Steer Receipt at the Row's Trailing Edge * 💬 style: Say Tool Calls Are Not Interrupted Either |
||
|
|
1ff4a44b61
|
🩹 fix: Stop the Subagent Composer Unmounting, and Give It Main Chat's Shape (#15448)
* 🩹 fix: Stop the Subagent Composer Unmounting, and Give It Main Chat's Shape Measured with react-scan and a DOM presence probe against the live panel: every external delivery unmounted and remounted the composer inside 10ms, taking focus and any half-typed steer with it. - The composer stays mounted across a delivery. A delivery re-keys the thread query to its new task and the task view blanks for the render or two that takes — deliberately, so task-scoped fields are never misattributed — but presence followed it down. The last mode the thread showed now carries the surface across that gap, holding submission until the new task's own view lands. `dispatched` also counts as live, so a run that has only just started no longer withdraws the field either. - One control, not three labels. Submitting IS the steer; with nothing to send the send button becomes main chat's Stop, which cancels the task. Queue and interrupt keep the chords main chat gives them (⌘/Ctrl+Enter, ⌥/Alt+Enter) instead of spelling themselves out beside the field, and every control says what it is through its tooltip and accessible name rather than a word label. - The two composers line up. The field takes `ChatForm`'s own metrics and the footer clears the viewport floor by the height of main chat's disclaimer, so both textareas now measure top 805 / height 52 and both surfaces end at 910 in a 950px viewport. - The header is main chat's header: a 52px bar that floats over the thread and fades into it, so the conversation scrolls under it instead of starting below a solid bordered bar. Residual re-renders are the per-second timestamp and elapsed tickers plus the 2s poll both queries run while a child is live — shared with main chat, about 2ms of render time across a 14s window, and left alone. * 🎛️ fix: Bind the Chord to Its Own Submission, and Keep Queue Reachable Codex and Copilot, round one. - A refused chord no longer decides a later click. `Composer` hands the submitting event to its host, and the panel keeps the resolved control bound to THAT event — a ⌘/Ctrl or ⌥ chord the composer then refuses (empty field, settling, a command in flight) leaves nothing behind for the next pointer submission to pick up. - The control comes from the reader's own key policy, not from raw modifiers. With Enter-to-send off, ⌘/Ctrl+Enter IS the submit chord, so reading modifiers directly made steering unreachable; the verdict from `resolveComposerKeyDown` decides instead. - Queue and interrupt keep a pointer. Chords alone stranded touch readers, and anyone whose shortcuts are disabled, rebound, or yielded. They are back as icon-only controls that say what they are through their tooltip and accessible name rather than a word label. - Stop cannot render unlabelled. Handler and label travel as one value (`ComposerStopProps`), so the type refuses a Stop without a name. * 🔗 refactor: Hang the Panel's Alternate Submissions Off One Shared Send Control Queue and interrupt had no pointer, then briefly had controls of their own beside the field — which repeats what submitting already does. Main chat never did that: it hangs its during-run actions off the send button. - `SendActions` moves to `@librechat/client`: the hovercard, the rows, the chord chips and their classes, lifted out of `DuringRunSendButton` unchanged. Each host passes its own anchor whole, so main chat keeps its ref (Enter's synthetic click routes through it), its submit type, and its identifying attributes. Its 13 existing tests still cover the rows. - The panel offers steer, queue and interrupt from that same control, and advertises a chord only while it still reaches its action — each hint is read from the decision table that will execute it, so a rebound, yielded or disabled chord advertises nothing. - The send control names what IT does (`com_ui_steer_send`) as distinct from the `Steer` row it offers, exactly as main chat pairs them. Two hosts now share one implementation, which is what the seam wanted. * 🧹 chore: Sort Panel Imports and Drop an Unrelated Reorder The import sort ran before the last edits to this file. Also reverts a Tailwind class reorder `eslint --fix` made to `MultiSearch`, which this change has no business touching. * 🛡️ fix: Only Offer Controls a Dispatched Task Can Actually Take Codex, round two. - A dispatched task is controllable only once the server can address it. `control.ts` answers 404 when the task has no durable input row and no live lease, which this panel reads as an inaccessible task and closes its controls for good — so treating every dispatched task as controllable could brick them. The field still stays (withdrawing it as a run starts is the swap this panel exists to avoid); submission waits for evidence. A task whose controls are definitively closed holds nothing. - Semantic status roles replace `text-amber-500`, `text-cyan-500` and `text-red-500` in the action rows — in main chat's list too, since both now render through the same module. - One popup over the send control. With actions to offer, the tooltip is withheld: both attached to the same button, opened on the same focus, above the same point, and the tooltip's higher z-index covered the list. - Where the pointer cannot hover, the alternate submissions are controls of their own: a tap on the hovercard anchor submits, so touch readers could not reach queue or interrupt at all. * 🚪 fix: Send Every Control Through the Same Addressability Gate Codex, round three — both findings were doors around the gate added last round. - Stop and the inline actions now answer to `controlAvailable`, not just to `composerMode`. A retained surface over a dispatched task the server cannot address yet still offered Stop, and the touch-side queue and interrupt buttons were live too; each is a command, and each 404 closes this task's controls for good. - A trapped panel keeps its actions inside itself. At mobile width with a hover-capable pointer the list still hung off the send control, portaled outside the `aside` the focus trap knows — so Tab never reached it. The actions come inline whenever the panel is a modal, hover or not. |
||
|
|
083be12ddc
|
🫂 ci: Unify Docker Image Publishing Workflows (#15447)
* 🛰️ ci: Publish Every Image Through One Native-ARM Workflow #15441 removed QEMU from the dev-branch image build and #15444 fixed the prefix-glob bug it shipped with. Both changes lived in one of five workflows that build images the same way; the other four still emulate arm64 with no layer cache and take 20-40 minutes. Rather than paste the pattern four more times, the build/merge logic moves to a reusable `docker-publish.yml` that every image workflow calls. #15444 is the argument for this: one wrong artifact-name expression would otherwise have needed correcting in five files, and the same prefix hazard exists in all of them (`librechat`/`librechat-api`, `librechat-dev`/`librechat-dev-api`, `lc-dev-staging`/`lc-dev-staging-api`). The shared implementation is the dev-branch one that already ran green, generalised only where callers differ. Callers pass what actually varies: - `images` — the target/file/image_name triples - `tag_suffixes` — newline-separated tags applied in both registries - `checkout_ref` / `build_branch` — main-image-workflow builds `main`, not the dispatch ref `main-image-workflow` and `tag-images` resolve their tags from git history, so each grew a small `resolve-tag(s)` job whose output feeds the publish. That also removes a duplicated `git fetch --tags` that previously ran once per matrix leg. Two details worth noting: - The digest-count assertion now derives its expected value from the `arches` input rather than a hard-coded 2, so the architecture list has a single source of truth shared by the build matrix and the merge guard. - Nothing is interpolated into a `run:` block any more; `build_branch` and the build digest arrive through `env`, so a ref name cannot reach a shell. Net 135 lines lighter, and QEMU no longer appears anywhere in .github/workflows. Workflow names are unchanged, so retry-docker-builds.yml still matches all five. * 🔗 fix: Pin Release Builds to One Commit and Survive Full Re-runs Review findings on the reusable publish workflow. **P1 — every build leg could resolve `main` separately.** `main-image-workflow` passed the symbolic ref `main` as `checkout_ref`, so the four build jobs each resolved it at whatever moment they were scheduled. If `main` advanced in between, the amd64 and arm64 halves of a release — or the API and application images — could be built from different commits and published under the same release and `latest` tags. `resolve-tag` now exports the commit it resolved the tag from, and the publish pins to that SHA. The single-job workflow this replaced checked out once, so this was a regression introduced by the split. `docker-publish` likewise now falls back to `github.sha` rather than passing an empty `ref` to `actions/checkout`, so callers that omit `checkout_ref` also get an immutable commit instead of relying on checkout's empty-ref handling. **P2 — digest artifacts on a full re-run.** `upload-artifact@v4` will not upload over an existing artifact name, and a full re-run keeps the run id, so a retry would fail before the merge could run. The suggested fix was to qualify the artifact name with `run_attempt`, but `retry-docker-builds` picks its endpoint by conclusion: `rerun` only when a job was cancelled, and `rerun-failed-jobs` otherwise. Under the latter the merge re-runs alone and must still find digests uploaded by the *previous* attempt's build legs, which a run_attempt-qualified name would hide — precisely the shape of the #15444 failure, where the builds passed and only the merge failed. `overwrite: true` resolves the conflict while keeping names stable across attempts. **Unreachable error branch.** `LATEST_TAG=$(git tag ... | grep ... | head -1)` under `set -euo pipefail` exits on grep's non-match, so the "No stable v<semver> tag found" message could never print. Added `|| true`, matching what `tag-images` already does. Pre-existing, carried over verbatim in the split. |
||
|
|
22a9368604
|
🛄 fix: Refresh Stale Skill File References (#15413)
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 lc-dev (linux/amd64) (push) Waiting to run
Docker Dev Branch Images Build / Build lc-dev-api (linux/amd64) (push) Waiting to run
Docker Dev Branch Images Build / Build lc-dev (linux/arm64) (push) Waiting to run
Docker Dev Branch Images Build / Build lc-dev-api (linux/arm64) (push) Waiting to run
Docker Dev Branch Images Build / Merge manifests (lc-dev) (push) Blocked by required conditions
Docker Dev Branch Images Build / Merge manifests (lc-dev-api) (push) Blocked by required conditions
* fix(agents): check skill version in codeEnvRef cache-hit path
When a SKILL.md body is edited, `updateSkill` bumps `Skill.version`
but does not clear the `codeEnvRef` on bundled skill files. The
cache-hit path in `primeSkillFiles` reused those stale refs without
comparing versions, causing Code API to return 403 ("Unauthorized file
reference") because the session key no longer matched.
Add a version check to the cache-hit guard: a ref whose version does
not match the current skill version is treated as a cache miss, forcing
a fresh upload under the new session key.
Closes #15275
* fix(agents): apply version check to aggregate cache path in primeInvokedSkills
The previous commit added a version check to the per-skill cache-hit
path in primeSkillFiles. Codex review pointed out that
primeInvokedSkills has its own aggregate cache-hit branch that returns
cached refs before delegating to primeSkillFiles. That branch checked
ref presence and session activity but not ref.version against
skill.version, so a stale ref from an older skill revision would still
bypass the guard.
Add the same version comparison to the aggregate filter and include the
skill object in the allResolved entries so the version is accessible.
Addresses Codex review on #15413
* fix(agents): type skill cache version checks
---------
Co-authored-by: dymux <putramkti@users.noreply.github.com>
Co-authored-by: Danny Avila <danny@librechat.ai>
|
||
|
|
a1e92e5530
|
🧩 ci: Scope Each Image's Digest Artifacts to Its Own Manifest Merge (#15444)
The first native-ARM run published `lc-dev-api` correctly but failed merging `lc-dev`: Merging 4 platform digests: 18f83e4a... b4700f27... ca747c51... edf23a05... ERROR: ghcr.io/danny-avila/lc-dev@sha256:ca747c51...: not found `lc-dev` is a prefix of `lc-dev-api`, so the merge job's `pattern: digests-lc-dev-*` matched all four uploaded artifacts — both images' digests — and then tried to resolve an API digest against the app repository. `lc-dev-api` was unaffected because no other image name prefixes it. Two changes: - Artifact names carry an explicit `-arch-` separator, so one image's pattern can no longer reach another's digests. - The merge asserts an exact digest count rather than merely rejecting zero. The old guard only caught an empty set, which is why four sailed through. Too few digests would publish a manifest missing an architecture; too many means the glob crossed images. Either is now a hard failure before anything is tagged. No bad manifest was published: `imagetools create` failed while resolving its sources, so `lc-dev` kept pointing at its previous build. The run also confirms the approach itself works — `lc-dev-api` merged to a proper index carrying linux/amd64 and linux/arm64, and the native arm64 legs built in 3-4 minutes against 13+ minutes under QEMU. |
||
|
|
283aae49b3
|
🖼️ fix: Give a Phase's Files Their Own Media Row (#15435)
Some checks failed
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 lc-dev (linux/amd64) (push) Waiting to run
Docker Dev Branch Images Build / Build lc-dev-api (linux/amd64) (push) Waiting to run
Docker Dev Branch Images Build / Build lc-dev (linux/arm64) (push) Waiting to run
Docker Dev Branch Images Build / Build lc-dev-api (linux/arm64) (push) Waiting to run
Docker Dev Branch Images Build / Merge manifests (lc-dev) (push) Blocked by required conditions
Docker Dev Branch Images Build / Merge manifests (lc-dev-api) (push) Blocked by required conditions
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
Publish `@librechat/client` to NPM / pack (push) Has been cancelled
Publish `@librechat/client` to NPM / publish-npm (push) Has been cancelled
* 🖼️ fix: Give a Phase's Files Their Own Media Row * ✂️ refactor: Drop the Inline-Media Dedup Six review rounds produced thirteen findings. The media row accounts for one and inline resolution for five, all fixed — both reason about attachments, and both converged. The dedup accounts for seven, arriving at roughly one per round (code fences, backtick spans of two widths, escapes, indented blocks, invalid title syntax, and raw HTML blocks), because it is the only part that has to know what markdown RENDERS. Each miss claimed a file nothing displayed and took it out of the row, so the standing cost of keeping it was a recurring way to hide content, in exchange for avoiding a duplicate. So it goes. `collectInlineMediaNames`, the fence tracking, the standalone image-line whitelist, and the claim wiring in `ContentParts` are deleted, along with the raw-HTML finding, which no longer has anything to apply to. Both reported problems still ship. The phase card's files ride their own row outside the fold, and a bare `` still resolves to the file the run produced. The only loss is that a chart the model inlines now appears twice — once in the row, once where the model put it — which is noise rather than a defect, and cheap next to a file that appears nowhere. What remains reasons only about attachments: which stored file a name refers to, whether that name is ambiguous, and which paths this app serves. * 🧹 style: Restore import order after the media context import shortened * 🪞 fix: Address Codex Round 7 — Latest Record and Dimension-Free Fallbacks The name map kept the FIRST record of a file while `collectPhaseAttachments` had been changed to keep the latest — the row and the inline reference disagreeing about which record of one file is current. A tool rewriting a path reuses its `file_id` and the later record carries the newer URL, so the inline reference resolved to a stale one. Both surfaces now take the latest, matching the attachment renderer. Resolution was gated on `isImageAttachment`, which additionally demands width and height. Those exist so `<Image>` can reserve layout space; a markdown `<img>` needs neither. The download fallback an oversized output produces carries an image name and a working URL and displays perfectly well, and requiring dimensions dropped it — leaving `` broken for exactly the outputs big enough to matter. The gate is now what an `<img>` actually requires: an image extension and a fetchable path. That predicate also moves INSIDE `buildAttachmentsByName`, where the contract belongs, rather than being a filter the one caller had to remember. Reusing `isImageAttachment` was the wrong kind of DRY — it answers a different question than the one asked here. * 🧬 fix: Address Codex Round 8 — Run-Step Identity and the Recovery Renderer Two of three; the artifact-panel one is deliberately left, see the PR thread. The row's dedup key omitted `stepId`, but `filterAttachmentsForPart` routes records to their owning part BY run step — so two executions that reuse a provider tool-call id and claim the same `file_id` are two artifacts, and collapsing them dropped one from the row that, once the fold hides the parts' own copies, is the only place it appears. `attachmentRenderKey` adds that dimension on top of `attachmentIdentity` rather than changing it: the merge in `useAttachments` reconciles DB rows against live records whose `stepId` may not have arrived, so it must stay tolerant of a field one side can lack. A step-less legacy row keeps the plain identity and never splits from itself. `MarkdownErrorBoundary`'s fallback renderer omitted the attachment-aware `img`, so a generated reference reverted to a page-relative URL precisely in the recovery path — where the media context still holds the file and a broken image is least welcome. |
||
|
|
d3ea6f415d
|
🐆 perf: Build Dev Branch Images on Native ARM and Cheapen Asset Brotli (#15441)
The dev-branch image build takes 20-40 minutes and has hit its 130-minute timeout. Neither cause was in the bundler. Per-step timings from run 33424998112 show QEMU is ~84% of the work: npm ci 52.6s (amd64) vs 333.2s (arm64, QEMU) — 6.3x npm run frontend 90.4s (amd64) vs 442.8s (arm64, QEMU) — 4.9x So build each platform on a runner of its own architecture, push by digest, and stitch the digests into the multi-platform manifest lists in a merge job. The tags consumers pull are unchanged. Layer caching goes to GHCR beside the image rather than `type=gha`: the Actions cache is capped at 10GB repo-wide and is branch-scoped, so `dev` and `main` runs would not share it, while a registry cache has no cap and is reusable by the other four image workflows when this pattern is rolled out to them. Separately, vite-plugin-compression2 defaults to brotli quality 11, which costs ~13s of CPU on this 18.4MB bundle against ~0.47s for gzip-9, and the plugin's scheduler serializes quality >= 10 as a high-memory operation so it cannot use the runner's cores. Quality 5 compresses in ~0.2s and still lands under gzip (4.3MB vs 5.0MB). `.br` is only served when ENABLE_STATIC_ASSET_BROTLI is set, which is off by default, so the 0.5MB the top quality buys is paid for on every build of every platform and rarely collected. The client build goes from ~11s to ~4s; gzip output is unchanged. This is a pilot on the dev-branch workflow only. dev-images, dev-staging-images, main-image-workflow and tag-images still use QEMU and still have no layer cache. |
||
|
|
bd24e45489
|
🧵 refactor: Give the Subagent Panel One Composer and Main Chat's Metrics (#15436)
* 🧵 refactor: Give the Subagent Panel One Composer and Main Chat's Metrics The panel drifted from main chat in the small measurements a reader notices side by side, and its footer swapped controls under the pointer every time a run settled. - One persistent composer. `@librechat/client` gains a `Composer`: the shared composer surface with a field, a height-holding action row, and the main chat send affordance. The panel renders it for the whole life of a run — Enter steers while the child is live, and continues the thread once it has settled — instead of replacing the input with a "Continue this chat" button at completion. Its placeholder is the main chat one (`Message {agent}`), not bespoke guidance copy. - Continuations carry the draft. Child threads are view-only server-side (`CHILD_THREAD_READ_ONLY_ERROR`), so continuing forks into a real conversation; what the reader typed rides along through the same per-conversation draft store the composer restores from, and comes back in the panel if the fork fails. - Main chat's footer metrics. Turn footers use `messageFooterClasses` and hold their height, so the elapsed timer is sized by the `text-xs` its main chat counterpart inherits rather than the panel's body size, and the reading leaving at completion no longer steps the thread. - The author column matches. Turns render `MessageIcon` unconditionally, and the streaming dot is inset onto the header icon's axis with `underHeaderIcon`, as main chat insets it. - Status indicators pin their color. Label first, dot last, so a longer status grows inboard and the color lands on one column in every row; a live row shimmers its label the way a running tool call does, and the dot no longer pulses against it. - The actor picker is the agent builder's. `ControlCombobox` with agent avatars and search, replacing a bespoke bare `Select`. * 🧷 fix: Keep Enter Off an Empty Field and Drop Dead Actor Rows - The shared `Composer` never submits an empty field on Enter. Continuing a settled thread needs no text, so its send button stays live — but a stray Enter must not navigate the reader out of the panel. - The actor picker lists only actors with a task to open. Its list has no disabled state, so a child without one was a row that silently did nothing; the selected thread stays listed regardless. - The header glyph loses its plate, like the `MessageRow` author slot, so an agent avatar reads as an avatar rather than an inset badge. The send glyph matches main chat's at 24px. * 🎚️ fix: Hand Continuation Text Over in Memory, Not Through Saved Drafts Codex review of the first pass, all three findings. - The continuation's text no longer goes through the composer draft store. Draft restoration is gated on the Save Drafts preference, so with drafts off the forked chat opened empty and the words were left sitting in local storage the reader had asked not to use. A new transient `pendingComposerTextByConvoId` carries them in memory instead, keyed by conversation so the handoff survives a navigation that resolves its record before moving the route; `useTextarea` drains it once, after the autosave restore it must not lose a race with. - The draft is read when the fork lands, not when it was requested, so anything typed while the request was in flight travels too. - Actors whose task id has not arrived are left out of the picker rather than offered as rows that cannot open anything (already in the previous commit; the filter keeps the selected thread listed regardless). * ⌨️ fix: Give the Panel Composer Main Chat's Keyboard Contract Codex round two. - The composer honors "Press Enter to send". With the preference off a bare Enter is a line break and never reaches the field's action, so reaching for one cannot steer a live run or leave for a continued chat; ⌘/Ctrl+Enter submits there instead. - The IME guard covers Safari. `isComposing` is already false on the very Enter that commits a candidate there, so tracked composition state and the legacy `Process`/229 signals stand in for it, as the main chat composer's own guard does. - The actor picker renders in the panel rather than a portal. On mobile the panel is a modal whose focus trap only knows its own descendants, so a portaled search field let Tab escape to the page behind it. * 🧭 fix: Bind the Continuation Draft, Contain Escape, Defer to the Key Policy Codex round three. - The continuation draft is bound to the selection that asked for it. The composer is shared across selections, so a fork that lands after the reader moved on was handing over the words now in front of them and clearing the composer they belong to. The live field is still preferred, but only while it is still that continuation's own. - Escape is handled on the panel element instead of through the focus trap's callback. That callback runs on a native listener on the aside, so it fired BEFORE any React handler inside it and closed the whole panel — taking the composer's text — when a nested popover meant to dismiss only itself. As a React handler it bubbles in DOM order, so the actor picker can contain its own Escape; `ControlCombobox` gained an `onOpenChange` so the panel knows when that is. - The composer defers to the host's key policy. `Composer` takes an optional `resolveKeyVerdict`, and the panel routes it through the same `resolveComposerKeyDown`/`useComposerBindings` the main chat form uses, so a rebound or unbound submit shortcut behaves identically here and chords claimed by global shortcuts are left for the window handler. * 🔑 fix: Stop the Trap Eating Escape, Freeze the Actor Mid-Continuation Codex round four. - `useFocusTrap` only consumes Escape when it actually answers it. Its listener is native and sits on the container, so it runs before every React handler inside — and by calling `preventDefault` with no callback it swallowed the key from the host that had taken Escape back, leaving the panel unclosable. Both other callers pass a handler, so nothing else changes. The panel suite now runs the REAL trap: stubbing it is what let the previous Escape assertions pass in both directions. - Nothing swaps the composer while a continuation is out. The panel holds the draft that travels with the fork, so the actor picker is frozen and the newest-task advance defers until the request settles, rather than leaving another actor's words to be discarded with the panel. - The 4 KiB cap applies only in control mode. It bounds a subagent control command's payload; a continuation is ordinary chat text bound for a composer that caps nothing. * 🪢 fix: Hold the Selection While a Failed Continuation's Words Survive Codex round five. The newest-task advance deferred while a continuation was in flight, but the error path released that hold at once — so the advance fired, the control identity changed, and the identity effect wiped the composer. The panel stays open after a failed continuation precisely so the reader can retry with those words, and this threw them away. The hold now covers a failed continuation for as long as its draft is still in the box, and releases itself the moment the draft is sent or cleared. The test asserts both halves, and fails if the hold narrows back to the in-flight case. * 🧹 fix: Hold for Any Unsent Draft, and Let a Cleared One Stay Cleared Codex round six. - Unsent words hold the selection wherever they came from. Protecting the draft only once a request had started or failed left the plainest case open: type a continuation, let a newer task arrive, and the advance changed the control identity and emptied the composer. The hold is now `continuationPending || draft is non-empty`, which subsumes the failed case and lets the extra flag go. - Emptying the field after asking to continue is the reader withdrawing the words. While the composer still belongs to that continuation it IS the draft, verbatim, so the destination no longer reinstates text they deleted; only a composer that has moved to another selection falls back to what was captured at submission. |
||
|
|
47dbcb4910
|
🎯 fix: Keep the Thinking Label on the Lightbulb's Axis While Shimmering (#15437)
The shimmer PR (#15431) nested the label inside a span carrying `.shimmer`, which is `display: inline-block`, alongside `truncate`. An inline-block whose overflow is not visible takes its baseline from its bottom margin edge rather than from its last line box, so the label row's line box grew from 18px to 23px and the text lifted 2px while the lightbulb — pinned to the row's 30px centre by the copy button — stayed put. Every streaming reasoning header sat off its own icon's axis until the label settled back to "Thoughts". The sweep now rides the label row itself. That row is a flex item, so `.shimmer`'s `inline-block` is blockified away and the text lands exactly where the un-shimmered label lands: measured against the real component, both states report an 18px box and the same text centre, where the nested form reported 23px and a 2px offset. The nested span survives only for the generated-label entrance, which cannot share an element with the sweep because both drive `animation-name`. It keeps the entrance outside — the clipped element paints the glyphs itself, so a descendant's opacity cannot fade them — and takes `align-top` so the baseline rule above cannot reopen the gap there. Found by rendering the real component in a browser; a hand-built reproduction of the same classes did not show it, and neither did the DOM assertions, which is why the regression shipped. |
||
|
|
cceb936655
|
🪗 feat: Fold a Live Run's Tool-Call Groups Into the Phase Card (#15434)
* 🪗 feat: Fold a live run's tool-call groups into one phase card A long agent run renders one row per labeled tool block, so a turn that takes twenty steps pushes twenty disclosure rows between the reader and the answer. The parent phase card already solves this — it just does not exist until the server has summarized the phase, which is after the run. Give `groupActivityPhases` the whole partition instead of only the part the server has claimed: an unclaimed run of two or more labeled activity blocks becomes a phase segment with a client-built header. `ContentParts` renders `ActivityPhaseGroup` for it exactly as it does for a real phase, so both cards share one definition of the chrome, the fold animation, the lazy body, and the approval retention — change the styling once and both move. The header carries the newest filled child label, which makes the collapsed row a ticker: it reads as the line that would have been at the bottom of the unfolded list, and the generated summary replaces it verbatim when the marker lands. The fold ends at that newest label, so the reasoning and tool call still streaming stay outside the card where they can be watched, and join on the commit their own label fills. Boundaries mirror the server's own phase rule (steer, existing marker, substantial prose) so the synthesized span and the phase card that replaces it cover the same content. Answer text is always a boundary — `groupSequentialToolCalls` absorbs only `commentary`, and nothing the reader came for may end up behind a disclosure. Toggle memory moves into the message-wide map both cards now share with `ToolCallGroup`, keyed on the span rather than either header, so opening the ticker to watch survives the swap to the summary. The rule is content-derived, so history and a live run partition identically and nothing unfolds at settle — `activityPhaseLabel` is opt-in, and a deployment with child labels but no phase summaries would otherwise drop the whole list back open at the end of every turn. Parallel content stands down: those columns own their layout and a synthesized card would pull them onto the phase path. * 🎞️ fix: Do not replay the entrance fold when a summary claims a fold A server marker landing on a span a synthesized card already covers was reading as an arrival, so the real phase card mounted with the entrance animation — which mounts a card OPEN and folds it shut over the next two painted frames. On a span the reader had already watched fold, that flashes every sub-group back onto the screen before collapsing again. Record the content indices sitting behind a synthesized card and suppress the entrance when the marker's span overlaps them. Indices rather than keys, so a start boundary the server resolves slightly differently from the client still counts as the same span. * 🙋 fix: Never fold a span holding an unresolved approval A fold summarizes work that is done. An approval waiting on the reader is the opposite: the run is blocked until they answer, and burying the request behind a disclosure they have to discover strands the turn. A server marker cannot reach this state — it is emitted after the batch resolves — but a subagent nested inside an already-labeled group can raise an approval long after its parent's label filled, which puts it inside a synthesized span. Render that span exactly as it would render without the feature until the approval clears, then fold. * 🧪 test: Assert the fold header ticks forward without re-opening * 🧷 fix: Tighten what a synthesized fold may claim and hide Four issues from review, each now pinned by a test that fails without its fix. An error, an image, or a summary between two labeled blocks was joining the run and disappearing into the card. Membership is now the allowlist `groupSequentialToolCalls` uses — tool calls, reasoning, commentary, labels — plus AGENT_UPDATE, which the server deliberately keeps inside a phase because a transfer card cannot join a tool group. Anything else is content in its own right and ends the fold. Orphan labels were counting as activities. A label that claims nothing heads no group and renders as a standalone line, so two of them are not two activities — folding them hid the first behind a card whose body was just the pair of lines. Labels now count only when they claim a non-empty block, with the claim closing at every label (blank reservations included) exactly as `claimStart` does, and handoff calls claiming nothing. Card identity was anchored to the span's first part, so it moved when the server extended a phase back across short unphased text the client treats as a boundary — dropping the reader's open card on the swap the shared identity exists to survive. It is an ordinal now: phase cards only ever append in content order. The synthesized entrance is gone. It mounts a card OPEN and folds it shut, and this component remounts mid-run — `messageId` is in the key and changes when the placeholder hydrates to the server id, and a reconnect mounts straight onto existing labeled content — so it would flash the whole fold back open partway through a run. Fold GROWTH was never animated, so formation matching it is the consistent behavior. * 🔑 fix: Key a phase card by its span so the summary reconciles in place Two more from review, and the first one condemns the mechanism rather than the instance. Ordinal card identity was not stable: an earlier server phase can hold a pending marker that yields no segment yet — its activities are handoffs, or no child label has filled — so a later fold takes `phase:1` and is pushed to `phase:2` when that marker fills, handing the reader's override to a card they never opened. Any counter has this shape, so the transfer map is gone entirely. What replaces it needs no state to hand over. Both card kinds now render under one key anchored to the SPAN, so the card that carries the summary is the same element as the fold it replaces and React reconciles it in place — the reader's open card simply stays open. A phase with no children has no span and keeps its marker key. `ActivityPhaseGroup` is byte-identical to dev again: the shared component needed nothing. The honest limit, now pinned by a test: when the server extends its span back across short unphased text the client treats as a boundary, the two spans differ, and a card covering different content is keyed and mounted as a different card. The toggle does not carry there. Keeping the stricter boundary is deliberate — `phase` is only stamped by providers that expose it, so matching the server would let a short final answer hide inside a fold. Second: `tool → transfer_to_* → label` was counting as a claimed batch, while the grouping flushes the block at the non-groupable transfer and then drops the label through `coversTransferCall`. Two such batches built a card over labels that render nowhere. The claim now resets at any part the block cannot hold, matching the flush. * 🗝️ fix: Drop messageId from the card key and let a grown span animate Two more from review; the second deletes a mechanism the span-keying had already made redundant. The assistant streams under a synthetic `${userMessage.messageId}_` for the WHOLE run and only takes its server id at `finalHandler`, so a key carrying `messageId` remounted every phase card at settle and dropped whatever the reader had open — a guaranteed loss at the end of every turn, not a narrow window. The card key is the span alone now, which is how `ToolCallGroup` has always keyed for the same reason. `replacesFold` suppressed the entrance whenever a marker's span merely OVERLAPPED a previous fold. When the server extends its span back across short unphased text the two spans differ, the card is genuinely a new one, and suppressing the entrance made the text the reader was looking at blink out of existence instead of being folded in. The suppression is gone: a same-span replacement is now reconciled rather than remounted, so it cannot animate anyway, and that was the only case worth suppressing. With it goes the `folded` index tracking it was the sole reader of. Anchoring identity to the span turned out to subsume both the expansion map and this set — the diff is smaller than it was two rounds ago. * 👥 fix: Separate a sibling switch from a message taking its server id Dropping messageId from the card key fixed settle but opened the case it had been guarding: MultiMessage renders siblings without a key and reuses this instance, so an index-anchored card carried one response's open state into an unrelated one. Neither prop tells the two apart on its own — setIsSubmitting(false) is batched with the final message swap, so that commit looks exactly like a sibling switch by message identity. `siblingIdx` does: it moves when the reader pages to another response and holds still when the same response takes its real id. * 🪪 fix: Pair the card's position with its first provider id `siblingIdx` was the wrong discriminator. MultiMessage recomputes the viewed response's positional index whenever a sibling is added or dropped around it — its own docblock calls this out — so a key carrying it remounted the card during churn that deliberately PRESERVES the response the reader is on. Its docblock also records why no message id works either: messageId and parentMessageId both move through the SSE lifecycle, which is exactly why that component reconciles positionally. The span's own position was never the problem — it is content-derived, so churn cannot touch it. It only failed to separate two siblings whose cards start at the same index. Pairing it with the first tool call's provider id fixes that and nothing else: settle and churn leave both halves alone, and the pair also keeps repeated provider ids in one message apart, which a bare id would have collided on — the existing `counts repeated legacy provider-id groups` case proved that immediately. * 🧲 fix: Anchor the card to its first tool call and spare hoisted output Anchoring the key to the span's first PART broke at settle whenever a phase opened with an empty TEXT or THINK: preserveStreamedContentIdentity drops those slots and stamps the survivor, so the anchor moved and the card remounted exactly when the run finished. `getToolGroupId` already records the answer — the tool calls themselves do not move — so the anchor is the first tool call's own position now, with its provider id still alongside to keep repeated ids apart. That also closes the limitation this PR had been documenting. A phase the server extends back across short unphased text still shares its first tool call with the fold, so the two now reconcile and the reader's open card rides through. The test that pinned the old degradation asserts the better behavior instead. Second: `ToolCallGroup` renders `groupAttachments` OUTSIDE its own panel so a generated image or file survives collapsing the group. Folding that group into a card put the hoist back inside a disclosure and the output disappeared. These attachments are message-level and never appear in `segment.content`, so the visible-part allowlist could not catch them — a span carrying any takes the same escape as an unresolved approval and renders unfolded. Hoisting them to the card would be better and would fix the same hole for server phases on dev; it is not this PR. * 🎯 fix: Give id-less spans a scope and stop the second streaming cursor Two from review. The card key's no-provider-id fallback was a bare position, which cannot separate two siblings whose cards start at the same index — the exact hole the provider id had just closed, reopened for id-less legacy calls. That branch carries the message-change counter now, which is what `getToolGroupId` does for its own id-less fallback, and it accepts the same tradeoff: the counter advances at settle, so those cards remount then. Spans that have a provider id are unaffected. `showEmptyCursor` never consulted `isLast`, so a trailing empty provider slot rendered the initial waiting state from inside its own segment even though a card was already carrying the cursor — two dots at once. Reusing `isLast` would have been wrong: it means "leaf of the tree", and the existing thinking-dot tests legitimately leave it false while submitting. The phase branch now computes which card owns the cursor and passes that down, alongside the other internal segment signals. It covers real phase markers too, which had the same duplication. |
||
|
|
146664e0d1
|
🔐 fix: Redirect OAuth Browser Navigations Instead of Rendering JSON (#15440)
* 🔐 fix: Redirect OAuth Browser Navigations Instead of Rendering JSON The /oauth/* routes are reached by top-level browser navigation, so a middleware that rejects one with a JSON body makes the browser render that body as the page. Hitting the login rate limit during a social sign-in dropped the user on a blank page showing: {"message": "Too many login attempts, please try again after 5 minutes."} Mark those requests with markOAuthNavigation and have the two middlewares that can reject them answer with a redirect to the login page carrying an error code the client localizes. The login limiter stays a single instance so the attempt budget is still shared with POST /api/auth/login, and that route keeps its JSON 429 for the SPA. * 🧪 fix: Address PR Review Bot Findings chatgpt-codex-connector: - Split the inline `type TranslationKeys` specifier out of the value import, per the standalone `import type` convention in CLAUDE.md. This also clears the Static checks import-sort failure. - Cover the new OAuth error branches in Login.spec.tsx: each mapped `?error=` code raises its localized toast and the query is cleaned, and unmapped codes including prototype members raise nothing. |
||
|
|
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. |