* 🖇️ feat: Reference Selected Chat Text with Multi-Quote Popup
Add a ChatGPT/Codex-style quote feature: selecting text in any message shows
an 'Add to chat' popup that accumulates removable quote chips above the
composer. On submit, the excerpts are merged into the user message text as
Markdown blockquotes (counted in the user message token count, not a system
message) and persisted on the message so they render on the user bubble and
survive reload.
- packages/api: add getReferencedQuotes + mergeQuotedText helpers (blockquote merge, length/count caps) with unit tests
- BaseClient.sendMessage: temporarily merge req.body.quotes into userMessage.text before buildMessages, restore clean text, persist quotes array
- data-schemas + data-provider: add optional quotes field to message schema/type
- client: pendingQuotesByConvoId atom, QuoteButton selection popup, PendingQuoteChips composer row, MessageQuotes persistent display
- useChatFunctions: drain pending quotes onto the message, carry forward on regenerate
- add localization keys and component/integration tests
* 🧪 test: Add Playwright e2e for chat quote feature
Add e2e/specs/mock/quotes.spec.ts covering select -> 'Add to chat' popup ->
chip -> send -> persistent reference block -> reload, plus multi-select
accumulation and chip removal. Selection is driven programmatically (real DOM
Range + dispatched mouseup) to summon the popup deterministically.
Add data-testid hooks (add-to-chat-button, pending-quote-chips, message-quotes)
to the quote components for stable selectors.
* 🛡️ fix: Address Codex review on quote feature
- Run PII filter + OpenAI moderation over req.body.quotes (P1): quoted excerpts
are merged into the model-facing user message, so they must clear the same
filters; a crafted quotes payload could otherwise bypass them. Adds tests.
- Carry quotes through edit/save-and-submit replays (overrideQuotes in
EditMessage), mirroring overrideManualSkills, so edited turns keep context.
- Hide the quote UI for Assistants endpoints (which bypass BaseClient merge),
so users can't queue quotes the assistant never receives.
- Clear pending quote/skill queues by resolved conversationId in useClearStates,
not the UI index, so queued-but-unsent selections don't linger in Recoil.
- Cap queued quotes client-side at 10 to match the backend QUOTE_MAX_COUNT, so
the composer never shows more quotes than are actually sent.
* 🧵 fix: Durably re-merge quotes + Codex round 2
Address Codex's re-review of the quote feature:
- Durable history re-merge (per maintainer decision): quotes are no longer
merged at request time and stripped; instead each user message's persisted
message.quotes is merged into its formatted content in AgentClient.buildMessages
(new prependQuotes helper) for current AND historical turns. The model
receives the referenced context on every prompt and the token count stays
consistent with what was persisted; stored text stays clean for display.
- Attach normalized quotes to the user message in handleStartMethods (before
getReqData/onStart) so the optimistic bubble, resumable abort metadata, and
saved row all carry them (fixes the abort-metadata gap).
- Skip the quote drain entirely for Assistants endpoints in useChatFunctions,
leaving the pending atom intact (UI is already hidden there).
- Normalize req.body.quotes via getReferencedQuotes before moderation/PII so
only the trimmed/truncated/capped excerpts the model will receive are checked.
- Tests: prependQuotes unit tests; BaseClient quote tests assert early
attachment + clean text; e2e now verifies the model receives the merged
blockquote on the current turn and re-merged from history on a later turn
(new E2E_ASSERT_QUOTE mock marker).
* 🔗 fix: Quote share/memo/abort/PII gaps (Codex round 3)
- Shared links: include quotes in the anonymized projection + SharedMessage
type (+test) so the /share view renders the same reference blocks as the
owner, mirroring manualSkills/alwaysAppliedSkills.
- MessageRender memo: compare quotes length so a server/resume copy whose only
change is the quote list re-renders (the block no longer goes stale/missing).
- Resumable job metadata: include quotes in the userMessage written to
GenerationJobManager so a reload/reconnect mid-stream reconstructs the chips.
- PII + moderation: also scan the merged blockquote+text exactly as the model
receives it, so a secret split across a quote and the typed body (each clean
alone) is caught (+cross-boundary test).
- e2e: make quote-add robust against the auto-scroll-dismisses-selection race
via a retried select+click helper.
* 🛑 fix: Keep quotes on aborted turn's request message (Codex round 4)
abortMiddleware reconstructs finalEvent.requestMessage from jobData.userMessage
but only copied ids + text; include quotes so a stopped quoted turn keeps its
MessageQuotes in the UI and a regenerate-before-reload still sends the
referenced context. Completes the resumable-metadata fix from the prior round.
* 🧮 fix: Quote recount + preliminary abort metadata (Codex round 5)
- Force a canonical token recount for messages carrying quotes in
AgentClient.buildMessages, so a plain text-only Save edit (which recomputes
tokenCount from text alone) can't leave a stale, quote-excluding count that
undercounts context on later turns — recount from the quote-merged copy
self-heals it.
- Seed normalized quotes into the preliminary userMessage metadata
(getPreliminaryUserMessage), so an abort during init/tool-loading (before
onStart) still reconstructs the stopped turn's MessageQuotes.
* ✅ fix: Add getReferencedQuotes to controller test mocks (CI)
request.js's getPreliminaryUserMessage now calls getReferencedQuotes; the
agents controller specs mock @librechat/api wholesale, so the mock must export
it or the call throws and cascades. Added a faithful mock (normalize/cap,
null when empty) to request.resumeMetadata.spec.js and jobReplacement.spec.js.
* 📐 fix: Quotes in context projection + resumable metadata (Codex round 6)
- Context-usage projection (resolveContextProjection): select message.quotes,
prepend them into the projected user text, and recount quoted messages so the
context gauge counts the same prompt the model receives (a text-only Save edit
no longer makes the gauge undercount / over-report remaining budget).
- Resumable job metadata: trackUserMessage (created-event rewrite) and abortJob
(final requestMessage) now carry quotes; SerializableJobData.userMessage and
CreatedEvent.message gained an optional quotes field. With the cross-replica
created-event spread, stopping/reconnecting a quoted turn after the created
event keeps its MessageQuotes.
* 💬 feat: Collapse multi-select quotes into one chip with hover popup
Composer feedback: the quote chip area now shows a single chip — the excerpt
text for one selection, or a collapsed "{n} selections" pill for multiple,
with a hover popup (HoverCard) listing every excerpt and a per-item remove. The
chip is taller (py-1.5/text-sm) to read less skinny. Adds com_ui_quote_selections
and com_ui_remove_all_quotes; updates unit + e2e tests (e2e drives the count via
a data-quote-count hook and exercises the hover popup).
* ♿ fix: Make multi-selection quote popup keyboard accessible
The collapsed "{n} selections" pill used a HoverCard, which Radix only opens on
pointer hover — its interactive content was unreachable by keyboard. Replaced it
with a Popover: the trigger is a real button that opens on click / Enter / Space
(focus moves into the list, each excerpt's × is tab-navigable, Escape closes and
restores focus), with hover-open preserved for mouse via controlled open state +
a close grace period. Hover-initiated opens skip auto-focus so they don't pull
focus off the composer. Adds an e2e asserting keyboard open/close.
* 📐 fix: Clamp the Add-to-chat button within the viewport (Codex round 7)
The floating selection button positioned via translate(-50%,-100%) (bottom-center
anchor) but clamped top/left as if they were its top-left, so a selection near
the viewport top or sides could render the button partly/fully offscreen. Now it
measures the button (ref + useLayoutEffect) and computes an on-screen top-left —
clamping by the full width within side margins and flipping below the selection
when there's no room above — with no transform, and stays hidden until measured
so it never flashes at an unclamped spot.
* ↩️ fix: Restore pending quotes on early-abort draft (Codex round 8)
When a turn is stopped before the created event (e.g. during tool/MCP init), the
final handler restores requestMessage.text to the draft, but the pending-quote
atom was already drained on submit — so a retry sent no quotes. The abort
requestMessage now carries quotes (preliminary metadata + abort fixes), so the
three early-abort/no-response draft-restore paths in useEventHandlers now also
re-queue pendingQuotesByConvoId from requestMessage.quotes.
* ♿ fix: Use Ariakit Popover for quote selections (keyboard focus)
The multi-selection popup used a hand-rolled Radix Popover with Popover.Anchor +
a manual button, so Radix had no trigger to return focus to — Escape dumped
focus to the page top. Refactored to Ariakit (the codebase's popover primitive,
per DropdownPopup/Fork): the `PopoverDisclosure` is the real trigger, so Escape
closes and returns focus to the composer instead of the top of the page. Keyboard
opens (Enter/Space) autofocus into the list and tab through each excerpt's remove;
hover opens for mouse with autofocus suppressed so it never pulls focus off the
composer. e2e asserts the keyboard open/navigate/Escape flow keeps focus on a
real control (never BODY).
* 🧹 chore: Update logger imports to use @librechat/data-schemas across multiple files and remove unused sleep function from queue.js (#9930)
* chore: Replace local isEnabled utility with @librechat/api import across multiple files, update test files
* chore: Replace local logger import with @librechat/data-schemas logger in countTokens.js and fork.js
* chore: Update logs volume path in docker-compose.yml to correct directory
* chore: import order of isEnabled in static.js
* 🔧 feat: Add configurable S3 URL refresh expiry time
* fix: Set default width and height for URLIcon component in case container style results in NaN
* refactor: Enhance auto-save functionality with debounced restore methods
* feat: Add support for additionalProperties in JSON schema conversion to Zod
* test: Add tests for additionalProperties handling in JSON schema to Zod conversion
* chore: Reorder import statements for better readability in ask route
* fix: Handle additional successful response status code (200) in SSE error handler
* fix: add missing rate limiting middleware for bedrock and agent chat routes
* fix: update moderation middleware to check feature flag before processing requests
* fix: add moderation middleware to chat routes for text moderation
* Revert "refactor: Enhance auto-save functionality with debounced restore methods"
This reverts commit d2e4134d1f.
* refactor: Move base64 encoding/decoding functions to top-level scope and optimize input handling
* WIP: basic route for file downloads and file strategy for generating readablestream to pipe as res
* chore(DALLE3): add typing for OpenAI client
* chore: add `CONSOLE_JSON` notes to dotenv.md
* WIP: first pass OpenAI Assistants File Output handling
* feat: first pass assistants output file download from openai
* chore: yml vs. yaml variation to .gitignore for `librechat.yml`
* refactor(retrieveAndProcessFile): remove redundancies
* fix(syncMessages): explicit sort of apiMessages to fix message order on abort
* chore: add logs for warnings and errors, show toast on frontend
* chore: add logger where console was still being used