mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-10-20 17:13:44 +00:00
4693 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
b753da163e
|
🤖 feat: Add GPT-5.6 (Sol/Terra/Luna) OpenAI Models (#14206)
* ✨ feat: Add GPT-5.6 (Sol/Terra/Luna) OpenAI models Adds the GPT-5.6 family (GA 2026-07-09) across the context, output, pricing, cache, premium, and default-model maps, mirroring gpt-5.5. - gpt-5.6 (Sol alias), gpt-5.6-terra, gpt-5.6-luna - 1.05M context / 128K output for all tiers - Standard + long-context (>272K input) tiered pricing and cache rates * 🐛 fix: Bill GPT-5.6 cache writes at documented 1.25x input surcharge OpenAI prices GPT-5.6 cache writes above the base input rate (Sol $6.25, Terra $3.125, Luna $1.25 per 1M vs $5/$2.50/$1 input). Correct the cacheTokenValues write rates so explicit prompt-caching usage is billed and reported accurately, and lock the surcharge with a test. * ✨ feat: Expose GPT-5.6 max reasoning effort + long-context cache premium Folds in the two deferred Codex findings: 1. Add `max` to the OpenAI `ReasoningEffort` enum and the reasoning_effort parameter options/labels so GPT-5.6 (Sol/Terra/Luna) can request its documented highest reasoning setting. Backend passthrough and zod validation pick it up via the nativeEnum schema. 2. Apply the long-context (>272K input) premium to cache tokens. Adds `premiumCacheTokenValues` + `getPremiumCacheRate`, threads `inputTokenCount` into `getCacheMultiplier`, and wires it through both structured-spend paths. Covers the gpt-5.4/5.5/5.6 family whose cache write/read previously stayed at flat base rates on long-context calls. * 🐛 fix: Bill GPT-5.6 cache writes + map max effort for OpenRouter Claude Addresses Codex round-3 findings: 1. (P1) splitUsage only read `cache_creation`/`cache_creation_input_tokens`, so OpenAI GPT-5.6's `cache_write_tokens` fell into inputOnly and billed at the input rate instead of the 1.25x write rate. Extend UsageMetadata and single-source the cache-creation read to also recognize `cache_write_tokens` (nested and top-level). 2. (P2) `max` was exposed via OpenRouter (spreads OpenAI settings) but the adaptive-Claude verbosity map had no `max`, so it was silently dropped. Map max -> 'max' verbosity. * 🐛 fix: Forward GPT-5.6 cache_write_tokens into emitted usage Local Codex review (P1): the cache-write fix reached balance billing (splitUsage/getCacheCreationTokens) but not the emitted-usage pipeline. ModelEndHandler built the emitted event's cache_creation from only cache_creation/cache_creation_input_tokens, so GPT-5.6 cache_write_tokens were dropped and aggregateEmittedUsage classified them as ordinary input — displayed/persisted cost undercounted and disagreed with the balance charge. Fold cache_write_tokens (nested + top-level) into the emitted cache_creation. |
||
|
|
6e8f44d72b
|
🪆 fix: Serve Nested Skill Files Through a Wildcard Route (#14191)
Some checks failed
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Has been cancelled
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Has been cancelled
GitNexus Index / index (push) Has been cancelled
GitNexus Index / post-index (push) Has been cancelled
Nested skill files (references/, scripts/, assets/) returned 404 when opened, while top-level SKILL.md worked. The file routes matched a single `:relativePath` segment, so a nested path only resolves when the encoded `%2F` reaches Node intact. Reverse proxies (nginx, Traefik, k8s ingress, ALB, Cloudflare) commonly normalize `%2F` to a literal slash before forwarding, which turns the path into multiple segments the single-segment route can no longer match. Switch the GET/DELETE file routes to an Express 5 splat (`*relativePath`) and reconstruct the path from the decoded segments, so nested files resolve whether the client sends `%2F` or a proxy decoded it to a literal slash. Security: add a shared `isSafeSkillFilePath` validator (extracted from the import validator, single source of truth) and reject traversal/absolute/ empty-segment paths at the route layer. Lookups remain exact DB matches, so the user-supplied path never touches the filesystem. Also drops a latent double-decode (Express already decodes route params). Closes #14190 |
||
|
|
9a272c283e
|
⭐ docs: update Star History section (#14198) | ||
|
|
1999f9f021
|
📎 fix: Translate Finite supportedMimeTypes Allowlists to Picker Accept (#14186)
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
GitNexus Index / index (push) Waiting to run
GitNexus Index / post-index (push) Blocked by required conditions
* 📎 fix: Translate Finite supportedMimeTypes Allowlists to Picker Accept Finite supportedMimeTypes allowlists were never reflected in the Upload to Provider file picker: only permissive configs (.*) cleared the accept filter (#12596); finite lists fell back to the hardcoded provider filter, so configured Office types (.docx/.xlsx) could not be selected. Add getConfiguredMimeAccept in file-config.ts, which resolves the picker accept from the configured allowlist by testing candidate MIME types against the actual RegExp patterns (robust to any regex shape). It collapses media to image/audio/video wildcards and maps document types to extension + MIME tokens. Returns undefined for the built-in default or an untranslatable config (keep provider filter) and '' for permissive configs. AttachFileMenu now uses it, folding all three cases into one check with the hardcoded filters as fallback. * 🩹 fix: Fall back when a configured type is unrepresentable Codex review: buildMimeAccept could emit a partial accept string when a finite allowlist mixed a recognized type with a supported-but-unrecognized one, hiding files the provider fallback filter would have shown (e.g. mp3 alongside pdf). Add a coverage guard that returns undefined unless every configured pattern maps to a recognized type, so unrepresentable configs keep the provider filter instead of a narrower partial. Widen the media samples to match fullMimeTypesList so common audio/video configs still translate rather than falling back. * 🎯 fix: Intersect picker accept with provider upload capability Codex review (3 findings): translating the validation allowlist wholesale let the picker expose types the specific provider upload path silently drops — PDFs/Office on the image-only path, audio/video on document providers that aren't Google/Vertex/OpenRouter, and broad regexes (e.g. application/.*) matching supported types the catalog can't represent. Rework the translation to intersect the configured allowlist with the categories the current upload path can send. getConfiguredMimeAccept now takes the permitted MimeUploadCategory set; buildMimeAccept scans the known-MIME universe, skips categories the path can't handle, and returns undefined (keep the provider filter) if a permitted-category match is unrepresentable. AttachFileMenu maps each fileType to its capability. * 🪨 fix: Scope Bedrock document accepts and infer Office MIME types Codex review (2 findings): - Bedrock's document path only sends bedrockDocumentFormats (pdf/csv/doc/ docx/xls/xlsx/html/txt/md), but the generic document capability exposed pptx/ODF/etc. that validate and upload yet are dropped from the payload. MimeUploadCapability now carries an optional documentMimeTypes allow-set; image_document_extended passes bedrockDocumentMimeTypes so the picker is scoped to Bedrock-supported formats. - Office files (.doc/.docx/.xls/.xlsx/.ppt/.pptx) had no codeTypeMapping entry, so inferMimeType returned '' when the browser reported no type, failing client validation with 'Unable to determine file type' before the configured allowlist could accept them. Add the extension mappings. * 🧩 fix: Add .htm/.yml aliases and cap Google docs to PDF Codex review (2 findings): - documentMimeExtensions now maps each MIME to multiple extensions so text/html emits both .html and .htm (matching bedrockDocumentExtensions and inferMimeType), and application/yaml emits .yaml and .yml. Without the alias, extension-based file dialogs hid selectable .htm files that validation accepts. - image_document_video_audio (Google/Vertex/OpenRouter) now scopes documentMimeTypes to application/pdf, matching the isProviderAttachType predicate and hardcoded fallback (files.ts:366-372); those paths only treat PDF as a viable document, so a config with docx/xlsx no longer advertises files the media path would drop. * 🎧 fix: Sync media samples to regexes and fall back on unknown patterns Codex review: a finite media allowlist with a subtype missing from the sample list (e.g. audio/webm) matched nothing in knownMimeUniverse, so it was silently ignored and the picker hid a valid audio upload the previous audio/* filter allowed. Two-part fix: - Media samples now mirror imageMimeTypes/audioMimeTypes/videoMimeTypes exactly, so every backend-accepted media type is in the universe and translates to its wildcard. - buildMimeAccept falls back (undefined) when any configured pattern matches nothing in the universe, so future sample/regex drift or an unrepresentable type yields the provider filter, never a partial that hides a supported file. * 📑 fix: Represent Excel aliases and epub/parquet in picker accept Codex review (2 of 3 findings): finite allowlists using backend-supported document types outside documentMimeExtensions fell back to the provider filter and hid the files. - Canonicalize the legacy Excel MIME aliases (application/msexcel, x-ms-excel, xls, etc. — matched by the excelMimeTypes regex) to .xls so an excel-pattern config translates instead of falling back. - Add application/epub+zip (.epub), the parquet variants (.parquet), and x-zip-compressed (.zip) to the representable set. (Third finding — pptx inference vs Bedrock — is a pre-existing backend validation gap; the picker already excludes pptx for Bedrock. Tracked separately.) |
||
|
|
29e958c35c
|
🧩 fix: Allow compatibility Field in Skill Frontmatter (#14189)
|
||
|
|
3945d293de
|
🗂️ feat: Per-Agent Memory Partitions (#14084)
* feat: per-agent memory partitions (memory_scope)
Adds an optional agentId partition to MemoryEntry so agents can opt into
isolated memory via a new memory_scope field ('user' | 'agent'). Partition
derives from agentId presence ({agentId: null} matches legacy docs, no
migration). Inline set_memory/delete_memory tools, the post-turn memory
agent, the request-scoped memory cache, and context injection are all
partition-aware; context is only injected into agents whose resolved
partition matches. Memory routes accept the partition param, scope
duplicate/token-limit checks per partition, and enrich entries with agent
names. Memories panel gains a partition filter and agent badges; the agent
builder gains an agent-scoped memory toggle.
* fix: address Codex review findings on memory partitions
- strip runtime ____N id suffixes in getMemoryAgentId so added-conversation
runs share the persisted agent's partition
- load each agent's own partition in multi-agent context injection instead
of skipping foreign partitions entirely
- clear memory_scope to 'user' on save when Enable Memory is unchecked
- fall back to 'all' when the selected panel partition no longer exists
- restrict GET /memories agent-name resolution to agents the requester can
VIEW
|
||
|
|
73c43ded25
|
💂 fix: Enforce ALLOW_EMAIL_LOGIN on the Backend Login Route (#14180)
* 🔒 fix: Enforce ALLOW_EMAIL_LOGIN on Backend Login Route
ALLOW_EMAIL_LOGIN=false previously only hid the login form; POST
/api/auth/login stayed mounted and accepted valid credentials. Add a
validateEmailLogin middleware (mirroring validateRegistration /
validatePasswordReset) that rejects login with 403 when the flag is
disabled, with an ALLOW_EMAIL_LOGIN_OVERRIDE escape hatch for
intentional direct API login (each use logged with request IP).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: Gate admin local login by email login flag
* fix: Move email login gate into api package
* test: Avoid mutating readonly request ip
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Danny Avila <danny@librechat.ai>
|
||
|
|
cb5454d364
|
🪜 fix: Apply Recursion Limit Config to Subagents (#14185)
* 🪜 fix: Apply recursion limit config to subagents Subagents ran through the SDK's SubagentExecutor path (recursionLimit = maxTurns * 3, default maxTurns 25 -> 75) which is decoupled from the top-level resolveRecursionLimit path. buildSubagentConfigs never set maxTurns, so subagents ignored the Agent Builder recursion_limit and the YAML recursionLimit/maxRecursionLimit, always capping at 75 steps. Add resolveSubagentMaxTurns (reuses resolveRecursionLimit, then max(25, ceil(limit / 3))) and set maxTurns on each SubagentConfig: self-spawn mirrors the parent, explicit children use their own recursion_limit. Floor at 25 avoids regressing below the historical 75; ceil keeps the effective graph limit within the resolved value and the maxRecursionLimit cap. Fixes #14181 * 🔒 fix: Clamp subagent maxTurns within maxRecursionLimit The default floor and ceil rounding could push a subagent's effective graph limit (maxTurns * 3) above the admin maxRecursionLimit cap while top-level agents are capped exactly (e.g. cap 20 -> 75 steps, cap 200 -> 201). Clamp maxTurns to floor(maxRecursionLimit / 3) so the effective limit never exceeds the cap, keeping a minimum of one turn. * 🎯 fix: Honor lowered recursion limits for subagents Drop the 75-step subagent floor: it kept subagents at 75 even when an admin/user lowered recursionLimit or recursion_limit below it, defeating cost/runaway control. Since resolveRecursionLimit already caps at maxRecursionLimit, deriving maxTurns as floor(limit / 3) keeps the effective graph limit at or below the resolved value, so it both honors lowered limits and never overshoots the admin cap (subsumes the prior explicit clamp). Minimum one turn. * 🧮 fix: Honor sub-3 recursion caps for subagents Drop the max(1, ...) floor on resolved subagent maxTurns. A cap of 1 or 2 previously became 1 turn (3 steps), exceeding the ceiling the top-level path enforces. floor(limit / 3) now yields 0 turns for a sub-3 cap, so the child never gets more steps than the resolved cap; the SDK returns a graceful recursion error, matching a top-level run with recursionLimit below 3. |
||
|
|
9446f3278c
|
🔡 fix: Normalize Email Case When Issuing Verification Tokens (#14172)
The user schema lowercases emails on save, but sendVerificationEmail stored the verification token with the raw registration email. For mixed-case registrations, verifyEmail's lookup by the lowercased DB email never matched the token, failing with 'No email verification data found'. Normalize once and use it for the link, recipient, and token. resendVerificationEmail already used the DB email and was unaffected. Co-authored-by: VictorEPlus <victor.ortega@eplusadvisor.com> |
||
|
|
cf9a426d29
|
🛡️ fix: Guard HITL checkpoint size against MongoDB 16MB limit (#14157)
Some checks failed
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile, librechat-dev, node) (push) Has been cancelled
Docker Dev Images Build / build (Dockerfile.multi, librechat-dev-api, api-build) (push) Has been cancelled
GitNexus Index / index (push) Has been cancelled
Sync Locize Translations & Create Translation PR / Sync Translation Keys with Locize (push) Has been cancelled
Sync Helm Chart Tags / Ignore non-main push (push) Has been cancelled
Sync Helm Chart Tags / Sync chart tags (push) Has been cancelled
GitNexus Index / post-index (push) Has been cancelled
Sync Locize Translations & Create Translation PR / Create Translation PR on Version Published (push) Has been cancelled
* 🛡️ fix: Guard HITL checkpoint size against MongoDB 16MB limit A LangGraph HITL checkpoint embeds the whole serialized message history in a single BSON document, so a large conversation (inlined base64 media, big tool outputs, long history) can serialize past MongoDB's 16MB document ceiling. `MongoDBSaver.put` would then throw a raw `BSONObjectTooLarge` at pause time and the pause would be lost with no legible error. `LazyMongoSaver` now measures the serialized checkpoint on the persist path (rare HITL pauses only — the clean-exit common path is untouched): debug-logs the size, warns past a soft 8MB threshold, and throws a typed `CheckpointTooLargeError` before the doomed write past a 15MB hard limit (16MB minus headroom for the document's other fields). Thresholds are overridable via the constructor for testing. Adds integration coverage (real serde + mongodb-memory-server) for the under-threshold, soft-warn, and hard-reject cases. * 🧱 fix: Add explicit types for isolatedDeclarations build The production build (tsdown + rolldown-plugin-dts) compiles with --isolatedDeclarations, which requires explicit type annotations on exported bindings whose initializers it can't infer syntactically. `CHECKPOINT_HARD_LIMIT_BYTES` (arithmetic over two consts) tripped TS9010; annotate it and `CHECKPOINT_WARN_BYTES` as `number`. Verified with `tsc --isolatedDeclarations` over the package. * fix: Codex review — include metadata in the size guard + flush parked bookkeeping P1 (lost bookkeeping): `put` consumes the write anchor, then AWAITS assertCheckpointFitsDocument (checkpoint serialization). A bookkeeping-only putWrites dispatched in that window sees neither the anchor nor persistedIds, so it parks — and `put` never flushed it, dropping the marker (e.g. a completed Send-sibling's __no_writes__) so a resume re-executed the sibling. Extract flushBufferedBookkeeping (shared with the anchoring putWrites) and call it after super.put in the persist path. P2 (metadata ignored by guard): MongoDBSaver.put stores the serialized checkpoint AND metadata (plus metadata_search) in the SAME document, but the guard measured only the checkpoint — a just-under-limit checkpoint with large metadata fell through to a raw BSONObjectTooLarge. Measure checkpoint + metadata; the fixed headroom now only covers metadata_search/ids/framing. Two integration regressions added (both fail without the fix, pass with it): metadata-pushes-over-the-ceiling, and flush-during-the-serialization-window. * fix: count metadata_search (raw metadata copy) in the checkpoint size guard Codex follow-up: MongoDBSaver.put stores metadata a SECOND time as `metadata_search: metadata` — the whole raw metadata object as a queryable BSON subdocument in the same agent_checkpoints document. Measuring only checkpoint + serialized metadata undercounted by that raw copy, so a large metadata.writes payload could pass the 15 MB preflight while metadata_search pushed the actual BSON past 16 MB — the raw BSONObjectTooLarge the guard exists to prevent. Add mongoose.mongo.BSON.calculateObjectSize(metadata) for the metadata_search contribution (mongoose already imported; no new dep). Headroom now only covers ids + BSON framing. New integration test sizes a case where checkpoint + serialized metadata is under the limit but the raw metadata_search copy pushes it over — green (24/24). * ci: run packages/api agents integration specs (checkpointer) in CI The checkpointer.integration.spec.ts (durable HITL checkpointer vs a real in-process MongoDB) is a *.integration.spec.ts, which test:ci deliberately excludes — and cache-integration-tests.yml only covers cache/cluster/mcp/ stream, not src/agents/**. So it ran nowhere and its regressions guarded nothing. Add: - test:agents-integration script (jest over src/agents/*.integration.spec.ts, runInBand — mongodb-memory-server is in-process, no external service); - a dedicated agents-integration-tests.yml (mirrors the proven build setup, no Redis) triggered on packages/api/src/agents/** changes; - babel-plugin-replace-ts-export-assignment as a packages/api devDep: the spec imports @langchain/langgraph-checkpoint (whitelisted for babel transform, uses `export =`), whose transform needs this plugin — it was only present under client/node_modules, unresolvable from packages/api, so the suite couldn't load. 24/24 pass locally. |
||
|
|
988a14a405
|
🙋 feat: ask_user_question - agent-initiated questions with durable pause/resume (#14139)
* feat: ask_user_question tool — agent-initiated questions with durable pause/resume The HITL runtime merged in #13942/#14024/#14025/#14123 already ships the full ask_user_question lifecycle (payload-agnostic handleRunInterrupt, resume validation via mapAskUserAnswer, reconnect rehydration, and the client question card) — but nothing ever raised the interrupt. This adds the producer: - packages/api/agents/hitl/askUserQuestionTool.ts: LLM-callable tool whose func calls the SDK askUserQuestion() helper (LangGraph interrupt() from the tool body); zod schema with length caps mirroring AskUserQuestionRequest, plus a JSON-schema twin for the schema-only registry - Registration: agentToolDefinitions, manifest.json (Tools dialog, admin filteredTools/includedTools kill switch), basicToolInstances, handleTools constructor branch - run.ts gating: checkpointer now attaches for hitlCapable runs whose agents carry the ask tool even with the tool-approval policy disabled (the interrupt needs only durability, not humanInTheLoop/hooks); the tool is stripped fail-closed from non-HITL callers (OpenAI-compat/Responses) and subagent child configs; excluded from eager event execution (interrupts must be raised inside the Pregel task frame) - resume.js: 16k length cap on the answer wire field - e2e (real Run + FakeChatModel + LazyMongoSaver + supertest resume): tool-body interrupt pauses durably with NO approval policy, answer round-trips as the ToolMessage content, tool body re-runs once on resume, sequential questions re-pause * fix: adversarial-review findings — in-graph execution, orphan prunes, endpoint scoping, real kill switch Pre-PR multi-agent review confirmed 5 defects in the initial commit; all fixed: 1. CRITICAL — the tool never paused on the real agents endpoint: production loads tools definitions-only, flipping the SDK ToolNode to event-driven dispatch, and the host ON_TOOL_EXECUTE handler runs outside the Pregel task frame (under runOutsideTracing), where interrupt() throws and becomes an error ToolMessage. Reworked: the ask tool never rides toolDefinitions/ toolRegistry — on HITL-capable top-level agents a real instance is supplied via AgentInputs.graphTools (agents#289, requires @librechat/agents > 3.2.57), the SDK's in-graph direct-tool seam; new production-shape e2e pins the event-driven mode end to end. 2. CRITICAL — ask-only runs left orphaned interrupted checkpoints (silent context duplication on every later turn): both orphan prunes were gated on toolApproval.enabled. The pre-turn prune now also fires for ask-capable agents (exported agentRequestsAskUserQuestion), and the abort-route prune fires when the aborted job carries a pendingAction. 3. MAJOR — self-spawned subagents bypassed the strip (self config resolves from the parent's _sourceInputs): fixed SDK-side (buildChildInputs clears graphTools) and the tool is now never present on child surfaces host-side. 4. MINOR — the manifest entry leaked into the Assistants tools dialog and the legacy plugins endpoint, where tools execute with no run to pause: new agentsOnly manifest flag, scoped out of both listings. 5. MINOR — filteredTools/includedTools only hid the tool from the dialog: now enforced at run build (strip + no checkpointer), making the admin filter a real kill switch for already-saved agents. * chore: update @librechat/agents dependency to version 3.2.58 in package-lock.json and package.json files * fix: reject agents-only tools at assistant create/update (Codex round 1) The tools-dialog scoping keeps ask_user_question out of the assistants LISTING, but the v1/v2 create/update handlers resolve arbitrary posted tool strings from the shared getCachedTools map — a REST client or stale saved payload could still attach it, and the assistants runtime executes tools with no run to pause, so every call would error. New isAgentsOnlyTool(tool) (manifest-driven, handles string and function-object shapes) drops such tools with a warn at all four resolution sites (v1+v2, create+update). * fix: offset resumed-run content indices past the pre-pause seed A resumed run rebuilds the graph from the checkpoint, and the fresh graph numbers content indices from its own empty contentData — starting at 0. The resume path seeds the (also fresh) content aggregator with the pre-pause parts at exactly those indices, so the resumed model turn collided with the seed: type-matching parts silently MERGED (post-resume text appended into a pre-pause text block), and type-mismatching parts (a reasoning/think part at index 0 — any Anthropic reasoning agent) dropped EVERY delta with 'Content type mismatch', losing the entire post-resume output from the live stream and the saved message. Latent since #13942 — tool-approval resumes corrupt content the same way (probe-verified); it surfaced now because ask_user_question makes pausing a first-class flow and reasoning models make the loss total. - createContentIndexOffsetHandlers(handlers, offset): wraps ON_RUN_STEP (the single point where a content index enters the pipeline — deltas resolve through the aggregator's stepMap) and ON_AGENT_UPDATE's inline index; every other handler passes through by reference. Probe-validated: resumed output now lands as a new part after the paused tool call. - resumeCompletion wires it with offset = seedContent.length. - logToolError: a GraphInterrupt unwinding out of a tool body is the HITL pause working as designed — no longer logged as a Tool Error. * fix: unblock live streaming of the resumed segment after an answer With resume indices now ABSOLUTE (server continues after the pre-pause parts), the synthetic ask-user-question card was squatting on exactly the index the resumed segment streams into: applyAskUserQuestion appends the card at the end of the message content, so on the answering device every incoming part at that index was blocked and nothing rendered between the answer submission and the finalize replacing the message. removeAskUserQuestionPart(message, actionId) strips the pause-scoped card on successful answer submission (useResumeSubmit onSuccess) — the durable record of the Q&A is the ask_user_question tool call itself. Pure helper + specs; same-reference no-op when nothing matches. * fix: displace the synthetic question card in the streaming content writer The store-level strip on answer submit wasn't enough: the SSE step handler keeps its own in-flight copy of the streaming message, so on the answering device the synthetic ask-user-question card still occupied the ABSOLUTE index the resumed segment streams into — every delta warned 'Content type mismatch' (existing ask_user_question vs incoming text) and nothing rendered between the pending_action and finalize. Displace the card inside updateContent when any real part claims its slot — the same displacement pattern as the OAuth prompt part directly above it. Covers the streaming handler's own copy, reconnecting tabs, and other devices; once real content streams, the pause is over by definition. Spec drives a runStep + text delta into the card's index and pins: no mismatch warn, card gone, text rendered. * feat: dedicated UI + durable data for completed ask_user_question calls The completed ask call rendered as a generic tool card labeled 'Cancelled' with raw (and empty) JSON args. Two layers fixed: Data: the saved tool_call part had args:'' and no output — streamed arg chunks carry no tool name so the aggregator drops them (normal tools recover via the completion event, which never fires for a tool that interrupts mid-execution and resumes on a rebuilt run with no step id). The resume controller now stamps the paused ask part with the pendingAction's authoritative question as args and the user's answer as output (attachAskUserQuestionAnswer — pure, targets the newest unanswered ask part, so sequential questions each keep their own answer). UI: Part.tsx routes ask_user_question tool calls to AskUserQuestionCall — a compact Q&A record ('Asked a question' header, question, description, 'You answered: <label>' preferring the picked option's label, or 'No answer was given' for an abandoned pause) instead of the generic card. New i18n keys; parseAskUserQuestionArgs degrades to null on malformed model args. * fix: single question UI per pause + immediate answer display Two live-turn issues with the new durable Q&A card: 1. Duplicate question on ask: during a live pause the message carries BOTH the ask tool_call part (now rendered by AskUserQuestionCall, showing a misleading 'No answer was given' while paused) and the synthetic interactive card. The durable card now defers while the turn is live and unanswered (isSubmitting) — the interactive card owns the question UI until it's answered; an abandoned pause still shows its no-answer state once the turn settles. 2. 'No answer was given' after answering: the server stamps the answer onto the part at resume seed, but the client only received that at finalize. No stream emission needed — the client knows the answer it just submitted: resolveAskUserQuestionPart (replacing the plain strip on submit success) removes the synthetic card AND stamps output/progress onto the newest unanswered ask tool_call, seeding args from the synthetic part's question when the streamed args were lost — mirroring the server-side attachAskUserQuestionAnswer, so the Q&A record shows the answer the moment the user submits. * fix: keep the Q&A record visible while the resumed segment streams The optimistic output stamp lives in the message store, but the SSE step handler evolves its own cached copy of the streaming message (created at turn start) — the first resumed event overwrites the store with that copy, wiping the stamp, so the Q&A card blinked out during streaming and only returned at finalize. Render-layer fallback instead of fighting the handler's copy: submitted answers are recorded by ask tool_call id when resolveAskUserQuestionPart stamps the part, and AskUserQuestionCall reads the recorded answer whenever the part's own output is missing — the record survives any message-copy churn until finalize delivers the server-stamped part. * feat: present Ask User as a native builtin in the tools dialog It ships with the app and pauses the run like a first-class feature, so it belongs with the builtins (Run Code, Web Search, Memory, ...) rather than in the third-party plugin list — while its mechanics stay exactly a plugin's: - BuiltinId += 'ask_user_question' (documented exception: a native TOOL, not a capability; selection reads agent.tools, the toggle emits tool-add/remove patches instead of a capability field) - buildCatalog surfaces it as a builtin gated on the same signals as before (tools capability on + the server lists the plugin, i.e. not admin-filtered) and skips it in the plugin loop so it never double-lists - On-theme icon: lucide MessageCircleQuestion in a teal chip via the builtin icon map, matching the other native entries; the bespoke purple SVG and the manifest icon field are gone - i18n'd name/description keys like the other builtins * feat: composer popover for answering questions (mentions-style) Answering moves to the composer, matching the existing mentions/prompts popover pattern: while an ask_user_question pause is live, a popover anchors above the textarea with the question as its header, numbered option rows (hover/click, or ↑/↓ + Enter from the empty composer), and an × to dismiss. The main textarea doubles as the free-form answer — its placeholder flips to 'Something else...' and form submit routes the text to the paused run as the answer instead of starting a new turn. Dismissing (× or Escape) restores normal sends; the inline transcript surfaces stay as before (interactive card while paused, durable Q&A record after) so the question remains visible in history. - findLiveAskUserQuestion (pure, spec'd): newest unanswered synthetic part across the conversation IS the popover signal — applied on on_pending_action, stripped on answer submit, so visibility tracks the pause lifecycle with no extra state - useLiveAskUserQuestion hook shared by the popover and ChatForm; dismissals in a recoil atom so both react - popover only mounts on the primary composer (index 0), mirroring QuoteButton * feat: number-key selection + return glyph in the question popover Pressing 1-9 in the empty composer picks the matching option directly, mirroring the numbered row chips; the highlighted row shows a return-key glyph as the Enter affordance. Same empty-composer guard as the arrow keys — typing a free-form answer is never intercepted. * refactor: first-class composer answer mode (useAskAnswerMode) Replaces the bolted-on integration (inline onSubmit interception + raw capture-phase keydown listeners on the textarea ref) with a single hook that owns the whole answer mode: live-question derivation, dismissal + highlighted option (shared recoil state), option selection, free-form submit routing (submitText returns whether it consumed the submission), and keyboard handling (handleKeyDown returns whether it consumed the key, composed ahead of the textarea's normal handler — no more addEventListener). The popover is now pure rendering off the hook; ChatForm wires placeholder, onKeyDown, and onSubmit through the same instance. Deliberately scoped to the composer rather than useSubmitMessage: starters/prompt-commands keep new-turn semantics (and the existing job-replacement behavior while paused). * fix: Codex round 2 — inline answer input, approval exemption, pause-time args F1 (composer submit unreachable while paused — isSubmitting keeps Stop shown and useTextarea eats Enter): redesigned around it, borrowing Claude Code's AskUserQuestion semantics. The popover now owns free-form input via an inline 'Other' row (numbered last, 'Something else…'), with select-then-confirm rows (click/arrows/digits highlight; Submit ↵, Enter, or double-click fires; Skip dismisses). The composer returns to being a plain composer — no placeholder swap, no submit interception; Stop keeps meaning stop. F2: ask_user_question is exempt from the tool-approval prompt unless the admin explicitly lists it (allow/ask/deny all win) — approving the right to ask a question was a pure double pause; the tool is side-effect-free. F3: the question is stamped onto the paused ask tool_call's args at PAUSE time (attachAskUserQuestionArgs in handleRunInterrupt), so abandoned/expired/ stopped turns persist with the question intact and the record card can render it — previously only the answer-resume path stamped args. * fix: fold model-supplied 'Other' options into the inline free-form row The model can generate its own catch-all option ('Other (type your own)', value 'other'), duplicating the popover's built-in free-form row — two other-ish rows, one pickable as a literal answer. Two layers: - Tool description now tells the model NOT to include catch-all options (the answer UI always offers free-form input on its own) - splitOtherOption (pure, spec'd) folds a catch-all option that arrives anyway out of the choice rows and uses its label as the inline input's placeholder — conservative match (value 'other', or a label reading as a free-form invitation), no false positives on real choices * fix: single question surface + clean free-form-only popover Two live-pause confusions: (1) the inline transcript card and the composer popover both rendered — the card now defers while the popover is up for its action, returning as the fallback surface when the user dismisses the popover (and in contexts without a ChatContext, where the popover can't exist); (2) an options-less question showed a pointless numbered '1 Something else…' row — free-form-only questions now render the inline input alone, with the 'Type your answer…' placeholder (a folded model 'Other' label still wins). * feat: the composer is the free-form answer box (like the main chat input) While a question pause is live, the main chat textarea composes the free-form answer — placeholder swaps to 'Something else…' (or a folded model 'Other' label), Enter with text submits the answer through answer-mode key handling (composed BEFORE useTextarea's submitting-lock, so the lock can't swallow it), and the Stop button swaps to Send (enabled despite isSubmitting) per the select-then-confirm design. The popover slims to the question header, numbered option rows, and Skip/Submit — its inline input is gone since the composer owns free-form now. Dismissing the popover restores normal composer semantics (Stop button, normal sends). * fix: Codex round 3 + real Skip semantics - Skip now ANSWERS instead of hiding UI (danny): it resumes the run with a decline notice ('The user chose not to answer this question.') so the model moves on — a client-side dismiss left the run paused until expiry, a hung turn. × / Escape remain pure dismiss (switch to the inline card surface). - P1 (resumed approval tool indices): resumed tool_calls steps whose tool_call id matches a seeded UNRESOLVED part now rebind to that seeded slot instead of offsetting — the original part resolves in place (output attaches) and no duplicate appears; message steps keep the offset, so the text-loss fix stands. createContentIndexOffsetHandlers now takes the seed array; resolved seeded calls are not rebind targets. - P2 (stale selection across questions): selection state resets when the live actionId changes; the vestigial inline-Other state ('other' selection + text atom) is gone — the composer owns free-form. - P2 (Redis abort path loses the args stamp): the abort route re-stamps the question onto the ask tool_call in the reconstructed abort content, so a Stop-abandoned question persists with its question intact. - P2 (malformed args crash): parseAskUserQuestionArgs normalizes untrusted shapes (options: {} / non-string entries) instead of throwing in render. * feat: free-form hint in the question popover footer Left-aligned in the footer row (opposite Skip/Submit): 'Or type your answer below' — points open-ended answering at the composer, whose placeholder already reads 'Something else…'. * feat: preserve composer drafts across the answer-mode swap The answer phase gets its own draft key (ask-answer:<actionId>), passed as a draftId override into useAutoSave — the key change itself drives the existing save/restore machinery, so the conversation draft (or mid-run PENDING draft) is stashed when a question pause takes the composer and restored once the user answers, skips, or dismisses. Ask keys are exempt from the PENDING migration branch, which would otherwise move-and-delete the stashed draft. A half-typed answer survives reload/navigation while its question stays live. Answer submission (option pick, free-form, skip) resets the composer via a new non-throwing useOptionalChatFormContext, so the swap-back restores into an empty box even outside ChatView-less render contexts (Share/search). * fix: rebind resumed steps for ALL seeded tool call ids The resume controller pre-stamps the user's answer onto the seeded ask_user_question part, so the unresolved-only rebind predicate treated it as settled and shifted the tool's re-run step to a fresh offset slot, leaving a duplicate ask record in streamed/saved content. Tool call ids are provider-minted per call: a resumed step bearing a seeded id can only be the interrupted batch re-executing, so rebinding every seeded id is always correct. * feat: popover UX round 4 — clickable hint, collapse, click-submit, multiSelect - Footer hint is a button that focuses the composer; reads 'Type your answer below' (no 'Or') when the question has no options. - Collapse (chevron) hides the popover WITHOUT closing the pause: answer mode stays live (placeholder, Enter routing, draft key), the chat card renders the question with a ChevronUp affordance to re-expand. x remains dismiss. - Single-select options submit on a single click; the Submit button renders only for multi-select. - multiSelect end-to-end: tool zod schema + JSON definition twin, wire type, client parse, popover check-chips, card toggles, record-card label mapping; answer = option values joined ', '; composer Enter and the multi Submit button both fold free-form text in with the checked values. - Hardening from adversarial review: in-flight status guard on every submit path (no duplicate resumes on double-click), popover locks while submitting, collapsed mode disarms invisible digit/arrow steering, the card shares the hook's checked state while the pause is live, the card folds catch-all 'Other' options, record mapping is all-or-nothing to avoid phantom labels, composer resets only when its text was consumed or the draft machinery will restore the stash. * feat: ask_user_question in model specs and ephemeral agents A librechat.yaml modelSpec can now equip the tool the same way it equips webSearch/executeCode/fileSearch/memory: modelSpecs: list: - name: my-spec askUserQuestion: true loadEphemeralAgent pushes the tool name when the spec flag (or the ephemeralAgent request flag, wired for parity) is set; everything downstream is the existing persisted-agent machinery — createRun's hitlCapable gating, graphTools injection, checkpointer attach, subagent strip, and the admin filteredTools/includedTools kill switch all apply unchanged. * feat: tense-aware Q&A record label (Asking / Asked) Shorten the record card header per feedback: 'Asking' while the question is still unanswered (abandoned/awaiting), 'Asked' once answered — replacing the single 'Asked a question' label. * fix: Codex round 4 — added-agent ask parity + preserve answer on failed resume F1 (added.ts): mirror loadEphemeralAgent's ask_user_question branch in the added-agent loader so a model spec's askUserQuestion flag (or the ephemeral request flag) equips added top-level agents too, matching execute_code / web_search / memory. Two load.spec cases added. F3 (composer): submitAskAnswer now takes an onSuccess callback and useAskAnswerMode defers clearing the selection/composer until the resume is accepted. A failed resume (16k answer-cap 400, expired action, network error) leaves status re-answerable, so wiping the composer up front lost the user's only copy of a free-form answer; now it survives for trim/retry. (F2 — a claimed Tools-capability bypass — was verified NOT reproducible: agentRequestsAskUserQuestion matches only loaded instances/toolDefinitions/ toolRegistry, all capability-filtered; a raw tools string has no .name and never triggers the install. Replied on-thread with the probe evidence.) * fix: Codex round 5 — expired question exits answer mode so its message shows An expired question (e.g. resume returns the stale-action 409) previously left the popover open with locked controls and no explanation, because the chat card — which carries the only 'this action expired' message — was suppressed by the popover-open guard. Treat 'expired' as no longer active: the popover closes, the composer reverts to normal, and the card becomes the sole surface and renders the expired message. 'error' stays active (retryable). * feat: group ask_user_question calls as their own category A homogeneous group of ask_user_question tool calls now reads 'Asked N questions' (present tense 'Asking N questions' while the turn streams) with a question glyph and no raw-name suffix — mirroring the subagent 'Ran N agents' category treatment, instead of 'Used N tools — ask_user_question'. Mixed groups keep 'Used N tools' but humanize the suffix to 'Question' and show a question icon for the ask entries (TOOL_FRIENDLY_NAME_KEYS + ToolIcon map). A group only forms at count >= 2, so the plural is always grammatical. Three ToolCallGroup.test cases cover homogeneous label/icon/suffix, present tense while streaming, and the mixed-group fallback. * fix: Codex round 6 — composer submit lock + abort stamp before emit F7 (composer status lock): the ask submit status lived on ApprovalContext, a React context mounted only around message content (ContentParts). The PRIMARY answer surface — the composer in ChatForm — renders outside it, so useApprovalContext returned the inert FALLBACK: status was always 'idle', setStatus a no-op. The in-flight double-submit guard (round 4) and the expired-exits-answer-mode fix (round 5) therefore never engaged for the composer. Move ask submit status to a global Recoil atom (useAskSubmitStatus) read/written by the composer, the popover, and the card alike, so a fast double-click/Enter is actually blocked and expired/error surfaces on every surface. Tool-approval status stays on the context (unchanged). F5 (abort stamp before emit): the abort route re-stamped a paused ask_user_question's args AFTER GenerationJobManager.abortJob had already emitted the final SSE from the unstamped content, so a Redis/cross-replica Stop left the live client showing an empty question until reload. abortJob now takes an optional transformAbortContent applied to the persistable content BEFORE the final event is built (and returned), so the live client and the saved message agree. New abort.spec case + updated call assertions. * feat: gate ask_user_question behind its own agent capability Add a first-class AgentCapabilities.ask_user_question (in defaultAgentCapabilities, on by default) so admins can enable/disable questions independently via endpoints.agents.capabilities, exactly like execute_code / web_search — not lumped under the generic tools capability. - ToolService: both filteredTools predicates (definitions-only and instance loaders) gate ask_user_question on checkCapability(ask_user_question) before the generic tools fallthrough. When off, the tool is dropped from toolDefinitions/toolRegistry, so run.ts's agentRequestsAskUserQuestion (which keys on the loaded surface) declines to install it and attach a checkpointer — the capability is enforced end-to-end at the loader, no run.ts change needed. - Tools dialog catalog: surface the ask builtin under its own capability rather than the generic tools one, so the UI matches the backend gate. - Tests: ToolService capability on/off filtering + defaults membership; catalog builtin visibility keyed on the dedicated capability. * style: sort imports in ToolCallGroup.test (CI import-order gate) * fix: Codex round 7 — surface ask-answer errors in the open popover A failed answer submission (16k reject, network error) sets the ask status to 'error', which — unlike 'expired' — deliberately keeps the question active and retryable. But the chat card that renders the error message is suppressed while the popover is open, so a composer/popover answer failed silently. Expose an 'errored' flag from useAskAnswerMode and render a warning line (com_ui_ask_answer_error) in the popover, so the user gets feedback and retry guidance without having to collapse/dismiss. It clears automatically on retry (status flips to 'submitting'). * fix: Codex round 8 — respect IME composition before submitting answers handleComposerKeyDown runs before useTextarea's composition guard, so with a CJK/IME keyboard the Enter that commits an in-progress composition was being intercepted and submitting the partial answer (and the composition buffer can leave value empty mid-compose, mis-triggering digit/arrow steering too). Bail at the top when composing — nativeEvent.isComposing, or key==='Process' / keyCode===229 for Safari's inconsistent reporting — mirroring the existing composer guard so the character commits normally. * chore: update `@librechat/agents` to v3.2.60 * 🔧 chore: Update @opentelemetry/core to version 2.9.0 and clean up package-lock.json * feat: digit shortcuts select options when the popover has focus Previously a number key (1..N) only selected an option from the empty composer (handleComposerKeyDown on the textarea) — if focus moved into the popover (a row/Skip/Submit button clicked or tabbed to), the number keys went dead. Add handlePopoverKeyDown, wired to the popover container's onKeyDown so it catches digits bubbling from the focused control: a digit activates its option exactly like a click (single-select submits, multi toggles). No highlight/Enter dance on this path — the options are buttons whose action is the click, and intercepting Enter would fight the focused button. Gated on active && !locked so it no-ops while a submit is in flight. * chore: update @librechat/agents to version 3.2.61 and @opentelemetry packages to latest versions |
||
|
|
fc67416d08
|
♊ fix: Resolve Local JSON Pointer Refs & Sanitize Tool Search Schema for Gemini (#14161)
* ♊ fix: Resolve Local JSON Pointer Refs & Sanitize Tool Search Schema for Gemini Gemini/Vertex agents fail when MCP tools are attached, in two ways: 1. MCP servers with OpenAPI-derived schemas can emit local JSON pointer refs (e.g. `#/properties/body/properties/start`). `resolveJsonSchemaRefs` only resolves `#/$defs` and `#/definitions` refs, so these survive into the request payload and Vertex rejects it with 400 'Invalid JSON payload received. Unknown name "$ref"'. Resolve any local `#/...` pointer against the schema root (RFC 6901 unescaping, cycle protection), and strip still-unresolvable `$ref` keys in `sanitizeGeminiSchema` as a safety net. 2. When deferred tools are enabled, the injected `tool_search` schema declares `mcp_server` as `oneOf: [string, string[]]`, which `zod_to_gemini_parameters` rejects ('Gemini cannot handle union types'). `buildToolClassification` now accepts the agent provider and collapses the union via `sanitizeGeminiSchema` for Google/Vertex only — the tool's runtime (`normalizeServerFilter`) already accepts both forms. Complements #13623, which sanitizes MCP tool schemas but not the injected tool_search definition, and did not cover non-$defs local pointers. Underlying converter limitation: langchain-ai/langchainjs#9691. * style: format tool search schema assignment * style: sort tool classification imports --------- Co-authored-by: Danny Avila <danny@librechat.ai> |
||
|
|
280da51004
|
🌍 i18n: Localize Token Balance Error Message (#14169)
* fix(i18n)/add: localize token balance error message * chore: alphabetical order of localization key --------- Co-authored-by: Danny Avila <danny@librechat.ai> |
||
|
|
96d213afe6
|
🧊 fix: Include Conversation Starters in Agent View and List Responses (#14142)
* fix: include conversation_starters in agent view and list responses * test: cover conversation_starters in agent view and list projections * test: include conversation_starters in agent list whitelist --------- Co-authored-by: Danny Avila <danny@librechat.ai> |
||
|
|
96367828e1
|
🧷 fix: Align Agent File Attachment Ownership (#14149)
Some checks failed
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile, librechat-dev, node) (push) Has been cancelled
Docker Dev Images Build / build (Dockerfile.multi, librechat-dev-api, api-build) (push) Has been cancelled
GitNexus Index / index (push) Has been cancelled
Sync Locize Translations & Create Translation PR / Sync Translation Keys with Locize (push) Has been cancelled
Sync Helm Chart Tags / Ignore non-main push (push) Has been cancelled
Sync Helm Chart Tags / Sync chart tags (push) Has been cancelled
GitNexus Index / post-index (push) Has been cancelled
Sync Locize Translations & Create Translation PR / Create Translation PR on Version Published (push) Has been cancelled
* fix: Align agent file attachment ownership * fix: Harden agent file unlink validation * test: Align file preview agent attachment access * test: Add agent file ownership e2e regression |
||
|
|
0347d4a7dc
|
🎯 style: Center Scroll-to-Bottom Rib in Message Nav Rail (#14151)
* fix: center scroll-to-bottom rib in message nav rail * fix: align chevron icons with rib centerline |
||
|
|
f3692b5d43
|
⏳ fix: Defer MCP Connection for Request-Scoped Placeholders on Reinitialize (#14148)
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
GitNexus Index / index (push) Waiting to run
GitNexus Index / post-index (push) Blocked by required conditions
Co-authored-by: fe2131-art <232701181+fe2131-art@users.noreply.github.com> |
||
|
|
dcdaaeac67
|
🤐 fix: Exclude Provider Secrets from HITL Pending Actions (#14136)
* 🤐 fix: Exclude Provider Secrets from HITL Pending Actions Sanitize resolved model parameters before persisting them in the pending action's resumeContext, and strip resumeContext/requestFingerprint from every client-facing copy (SSE emit, reconnect gap-fill, resume state, status route). The full record stays server-side for resume replay. * 🤐 fix: Treat Header-Carrier Keys as Sensitive Wholesale Google llmConfig places the Authorization header in customHeaders, and header names like Ocp-Apim-Subscription-Key defeat name heuristics. Match 'header' as a key fragment so every header-carrier object is dropped rather than relying on exact carrier names. * 🤐 fix: Strip Preconfigured Client Instances from Resume Params Bedrock stores a BedrockRuntimeClient on llmConfig.client when PROXY or a bearer token is configured; replaying it would also fold a mangled client object into additionalModelRequestFields on resume. |
||
|
|
47a8749428
|
🔒 fix: Prevent Role Grants on Shared Links (#14137)
* 🔒 fix: Prevent Cross-Tenant Role Grants on Shared Links The shared-link access middleware falls back to a system-wide lookup so a share owned by another tenant still resolves, then evaluates the ACL under the share owner's tenant. It was passing the viewer's `role` from their own tenant into that check. Role ACL principals are unqualified name strings, so a tenant-B user with role USER could satisfy a tenant-A private share granted to role USER (default role names collide across every tenant). Pass the viewer's role only for same-tenant views; use `null` for cross-tenant views so getUserPrincipals never builds a ROLE principal. `null` (not `undefined`) is required: `undefined` re-derives the role via an unscoped User lookup under runAsSystem for tenantId-less shares, reopening the bypass. Widen checkPermission's `role` param to `string | null`. Adds regression coverage for both the cross-tenant denial and same-tenant allow paths. * 🔒 fix: Use authenticated user tenant for same-tenant role trust Codex P2: share file routes (<img>/download) authenticate via optionalShareFileAuth from the refresh cookie and never establish tenant ALS context, so getTenantId() is undefined there. Comparing the share tenant to the ALS context wrongly denied a legitimate same-tenant viewer their ROLE grant on file/preview/download requests, even though the main share route authorized them. Compare the share tenant against the authenticated user's own tenantId, which is populated in both the JWT and cookie-auth paths (tenantContextMiddleware itself derives the ALS tenant from user.tenantId, and req.tenantId is never set, so user.tenantId is the authoritative, non-spoofable source). Adds a regression test for the no-ALS-context cookie-auth file request. |
||
|
|
9a8eca10c0
|
📦 chore: bump langfuse fanout Go dependencies (#14126)
* chore(deps): bump google.golang.org/grpc in /otel/langfuse-fanout Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.75.1 to 1.79.3. - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.75.1...v1.79.3) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-version: 1.79.3 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> * chore(deps): bump golang.org/x/net in /otel/langfuse-fanout Bumps [golang.org/x/net](https://github.com/golang/net) from 0.43.0 to 0.55.0. - [Commits](https://github.com/golang/net/compare/v0.43.0...v0.55.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-version: 0.55.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> * chore(deps): bump fanout Docker Go builder --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
bfebf0fb81
|
🧷 chore: Expose Retain Recent Summarization Config (#14134)
Some checks failed
GitNexus Index / index (push) Waiting to run
GitNexus Index / post-index (push) Blocked by required conditions
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Has been cancelled
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Has been cancelled
|
||
|
|
44d1275f36
|
⚙️ perf: reduce first-load MongoDB round trips (#14101)
* perf(api): reduce first-load database round trips * docs: move agent guidance to claude docs * refactor(api): move message validation into api package * fix(api): narrow active generation job lookup * fix(api): preserve omitted source identity |
||
|
|
a0aa1f2b9d
|
🎚️ feat: Explicit "Use All Skills" Toggle in the Skills Section (#14125)
* feat: Add explicit Use All Skills toggle to the Skills section Adding a skill to an agent could silently mean "all skills enabled" (skills_enabled on with an empty allowlist), and the only master control was the Agent Skills kill switch buried in the Advanced panel, which implied skills required a trip to Advanced at all. - Add a "Use all skills" switch directly in the builder's Skills section: on = the agent gets every skill the user can access, including skills added in the future (skills_enabled true, empty allowlist). While on, the per-skill list and Add button are hidden and the header shows an All badge; turning it off restores the previously selected skills. - Remove the kill switch from the Advanced panel. - Simplify skillsEnabledTransition so the master flag tracks the selection on every edit, which also re-enables agents left with a selection but a disabled flag by the old kill switch. * fix: Normalize legacy disabled allowlists and scope the use-all stash per agent Agents saved with the removed Advanced kill switch off (or predating the skills_enabled flag) load a non-empty allowlist with the master flag not true. The builder rendered that selection as active while the runtime ignored it, with no control left to re-enable. Normalize on form load so the flag matches the displayed selection. The use-all stash lives in a ref and ToolsSection is not remounted when switching agents (only the form resets), so toggling off on another agent could restore the previous agent's allowlist. Clear the stash when the agent id changes. |
||
|
|
8fcb77fe6f
|
🧵 fix: Preserve Fenced Markdown Artifacts (#14121)
Some checks failed
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile, librechat-dev, node) (push) Has been cancelled
Docker Dev Images Build / build (Dockerfile.multi, librechat-dev-api, api-build) (push) Has been cancelled
GitNexus Index / index (push) Has been cancelled
Sync Locize Translations & Create Translation PR / Sync Translation Keys with Locize (push) Has been cancelled
Sync Helm Chart Tags / Ignore non-main push (push) Has been cancelled
Sync Helm Chart Tags / Sync chart tags (push) Has been cancelled
GitNexus Index / post-index (push) Has been cancelled
Sync Locize Translations & Create Translation PR / Create Translation PR on Version Published (push) Has been cancelled
* fix: Preserve fenced markdown artifacts * fix: Satisfy artifact CI checks * fix: Handle longer artifact fences in updates |
||
|
|
a03c574bef
|
🧢 fix: Raise Claude Sonnet 4.6 Output Cap (#14115)
* fix: raise Claude Sonnet 4.6 output cap * fix: handle Sonnet 4.6 token edge cases * fix: align Sonnet token aliases * test: update Bedrock Sonnet output cap expectation * fix: align future Sonnet token aliases * fix: cap Bedrock Sonnet 4.6 default output * fix: support double-digit Sonnet 4 minors * style: format Sonnet token helpers * fix: match number-first Sonnet aliases |
||
|
|
2d4ef52c22
|
🧮 fix: Prevent String Corruption of Numeric Agent Model Parameters (#14119)
* 🧮 fix: Prevent String Corruption of Numeric Agent Model Parameters * 🧮 fix: Support Partial Numeric Input and Cover Parameter Aliases |
||
|
|
1f7b38e972
|
🧠 fix: Format Reasoning Exports (#14116)
* fix: Format reasoning exports * chore: Sort export imports * fix: Refine reasoning export formatting * chore: Simplify export test localization * fix: Handle export summary labels |
||
|
|
13a01dee9b
|
🗄️ fix: Return Cached Transaction-Support Value on Cache Hit (#13999)
getTransactionSupport initialized transactionsSupported to false and only assigned it on a cache miss (transactionSupportCache === null), so on a cache hit it returned false regardless of the cached value. After the first bulkUpdateResourcePermissions call populated the cache with true, every later call reported false and the bulk permission writes silently stopped running inside a MongoDB transaction, losing atomicity. Return the cached boolean on a cache hit; the cache-miss path still probes via supportsTransactions. Adds a regression test. |
||
|
|
5789f89a3f
|
🌏 fix: guard IME composition on Enter in prompt name/labels and dynamic tag inputs (#13996)
* 🌏 fix: guard IME composition on Enter in prompt name/labels and dynamic tag inputs
* style: sort imports in DynamicTags per repo import-order convention
---------
Co-authored-by: greymoth <246701683+greymoth-jp@users.noreply.github.com>
|
||
|
|
61e1633b84
|
fix: fall back to _id for user id in createSafeUser (#14083)
* fix: fall back to _id for user id in createSafeUser * chore: reorder imports in env.spec.ts --------- Co-authored-by: Danny Avila <danacordially@gmail.com> |
||
|
|
f11b7379a1
|
🏁 fix: Ignore Finalized SSE Transport Errors (#14114)
* fix: Ignore Finalized SSE Transport Errors * test: Rename Resumable SSE Suite |
||
|
|
e8d76542b6
|
📡 feat: add rum browser page-load diagnostics (#14106)
* feat(rum): add browser navigation diagnostics * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * feat(rum): add browser navigation diagnostics * refactor(rum): extract bootstrap diagnostics * test(rum): fix bootstrap spec typings * fix(rum): keep stale asset recovery inline * fix(rum): simplify bootstrap recovery split * fix(rum): discard early queue when unsampled * fix(rum): restore emitter after re-enable * fix(rum): ignore optional bootstrap failures * fix(rum): preserve proxy queue until token --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> |
||
|
|
446b73bb6b
|
🧯 fix: Harden Principals Cache Invalidation and Lock-Wait Paths (#14120)
* 🧯 fix: Harden Principals Cache Invalidation and Lock-Wait Paths Follow-up hardening for the group-membership principals cache: - Coalesce deferred transactional invalidations into one session `ended` listener so many membership writes in a transaction cannot trip the emitter's max-listeners warning - Re-attempt the build lock inside the lock-wait poll so readers take over when the holder skips its write (invalidated away, failed, or crashed) instead of stalling for the full wait budget - Read cache builds from the primary so secondaryPreferred deployments cannot pin a lagging secondary's pre-mutation memberships for the TTL - Decouple the delayed stale-rewrite eviction pass from build locking via a crossProcess store marker, so USER_PRINCIPALS_LOCK_TTL_MS=0 disables locking only - Treat aggregation-pipeline bulk updates touching memberIds as indeterminate (namespace clear) instead of silently skipping invalidation * ⏲️ fix: Floor the Stale-Eviction Delay for Lockless Cache Builds The second invalidation pass ran at only 500ms when build locking is disabled (USER_PRINCIPALS_LOCK_TTL_MS=0), so a cross-container build slower than that could still re-cache revoked memberships until the TTL. The store now supplies an explicit staleEvictionDelayMs (lock wait plus one build round-trip, floored at 3s), keeping the default locked budget unchanged. |
||
|
|
edd614bbff
|
🧰 feat: Redesign Agent Builder with Unified Tools Marketplace, Skills & Orchestration (#13952)
* feat: redesign the agent builder tools, skills, and advanced panels
Replace the stacked capability/MCP/skill/tool/action form sections with a unified tools marketplace, per-item configuration dialogs, and a consolidated Advanced panel.
- unified tools marketplace (catalog, sidebar, polymorphic cards/rows) covering built-in capabilities, plugins, MCP servers, and actions, each with a detail/config dialog
- dedicated Skills picker and a Tools section with selected-item summaries and empty states
- redesigned action editor and authentication dialog (method cards, segmented controls)
- rebuilt Advanced panel: orchestration hub (subagents, handoffs, chain), max steps, skills kill-switch, copyable agent id
- restyled version history (timeline, tool/capability counts, in-app restore confirmation)
- shared component updates (Radio, Input/Textarea, dropdown z-index, dialog primitives) and keyboard-only focus rings via useInputModality
- format-hint placeholders for tool credential fields
- sanitize numeric parameter inputs to prevent comma truncation
* feat: refine agent builder tools, actions, and MCP sections
* feat: restore Memory capability toggle in agent builder tools catalog
* feat: refine agent tools picker (skills, MCP connect/OAuth, web search)
- Skills picker: per-card visibility (public) and shared-author badges,
category filtering, and an in-place Create skill flow that auto-attaches
the new skill without leaving the builder
- MCP: inline Connect button in the first dialog plus a dedicated OAuth
dialog (continue, copyable URL, QR code) shown only when OAuth is required
- Web search: auth-aware affordance, settings cog when user-provided and an
info icon when system-defined
- Remove orphaned com_ui_unavailable/com_ui_initializing keys and the dead
Tools/MCPToolItem component
* refactor: streamline MCP OAuth dialog
- Remove the Cancel button (the flow auto-closes on connect / times out)
- Show the URL in a read-only single-line scrollable input (cursor moves
through it, not fully visible) with the shared CopyButton's smooth
Copy/Check icon swap, matching the OAuth callback-URL field
- Put the primary Continue with OAuth action (icon trailing) and an
icon-only QR toggle together in a row at the bottom, below the URL
- The QR reveals between the description and the URL with a smooth height
animation (grid-rows 0fr to 1fr, matching MCPToolItem's reveal)
* feat: smoothly collapse MCP connect button once connected
* feat: cross-fade MCP tools between loading, list, and empty states
* feat: show MCP server icon in OAuth dialog title
* fix: vertically center OAuth dialog title against the MCP icon
* feat: smoothly animate auth field changes in the MCP server dialog
* feat: match Code Interpreter file upload to the File Search dropzone
Swap Code Interpreter's thin btn-neutral bar for the same dashed dropzone
(DropzoneContent + dropzoneClassName) File Search already uses, so the two
capabilities' upload UIs are consistent.
* feat: show a saving spinner and allow cancelling credential edits
Drive the tool credential Save button from the real mutation state so it
shows a spinner while the request is in flight, and add a Cancel button
when re-editing already-saved credentials so the edit can be dismissed.
* feat: make the skills create button a compact icon button
* fix: restore MCP attach semantics and confirmations in the tools marketplace
Connecting an MCP server from the item dialog now enables all of its tools
once the connection settles, deselect-all keeps the server attached via its
placeholder token instead of detaching it, adding a server writes the token
so a zero-tool attachment survives a save, and removing a server from the
tools list asks for confirmation again. Consume-only servers are excluded
from the catalog, matching the old select dialog.
Also share the catalog/selection pipeline between ToolsSection and the
marketplace through useAgentItems, hoist NEW_ACTION_ID next to ActionItem,
drop unused status/view union members and stale TranslationKeys casts,
document the phase-2 Favorites/Made-by-you views, fix the needs-setup dot
semantics and card focus suppression, remove the redundant close button in
CreateSkillDialog, move useInputModality into @librechat/client so external
consumers can mount it, and delete dead files and orphaned translation keys.
* fix: scope tooltip elevation to dialogs and restore dialog close button size
Tooltips go back to z-150 globally; inside a dialog they now borrow the
depth-aware popover z-index so they still clear nested dialogs (the Tool
Library item dialog) without outranking freshly opened modals everywhere
else. The default dialog close icon returns to its original size, and the
lc-field pointer-focus suppression ships with the package next to Input and
Textarea so external consumers get the whole mechanism from @librechat/client.
* feat: add favorites for marketplace tools, MCP servers, and skills
Reintroduce the favorite star from the old skill picker, generalized to
every marketplace item kind except per-agent actions. Cards in the Tool
Library and Skills dialogs get a hover-revealed star (always visible once
favorited), and the existing Favorites views in both dialogs now filter to
starred items.
Favorites persist in a dedicated ToolFavorite collection, one document per
(user, itemType, itemId) with a unique compound index, exposed through
atomic per-item PUT/DELETE endpoints under /api/user/settings/favorites/
tools. Per-item writes are idempotent and race-free across tabs/devices
(the unique index backstops concurrent toggles), reads are a single
index-backed query capped at 100 favorites per user, and the client keeps
React Query as the source of truth with optimistic updates. Handlers live
in @librechat/api with a thin route wrapper; methods follow the
data-schemas factory pattern with tenant isolation.
The favorites filter now matches on compound kind:id keys instead of bare
ids, closing a cross-kind collision where a tool and a skill sharing an id
would both match. The skill-favorites data-service stubs and the reserved
TUserFavorite.skillId field are replaced by the new tool-favorites service.
* feat: anchor the favorite star at the card's right edge
Swap the ToolCard action-bar order so the star sits rightmost with the
configure/info icon to its left. Every card can be favorited but only some
are configurable, so anchoring the star keeps it in a consistent position
across the grid.
* chore: remove translation keys orphaned by the tool library redesign
* fix: gate marketplace creation entries and resolve off-page selected skills
The Create New menu exposed MCP server creation to users without the
MCP_SERVERS create permission and action creation on deployments with the
actions capability disabled; both entries are now gated like their
pre-redesign counterparts, and the button hides when neither applies.
Selected skills missing from the first catalog page (limit 100) were
dropped from the Skills section entirely, leaving them impossible to
inspect or remove. useResolvedSkills restores the per-id lookup: off-page
skills are fetched individually and confirmed misses (deleted or no longer
shared) stay visible under an Unavailable skill placeholder so the stale
allowlist entry remains removable.
* fix: refetch favorites when toggled before the list loads, lint fixes
An optimistic favorite written over an unpopulated cache seeded the list
with only the toggled item, and cancelQueries killed the initial fetch
that would have corrected it, hiding existing favorites until reload. The
optimistic write now only applies over known data; otherwise onSettled
invalidates so the authoritative list is refetched.
Also unnest the version date-label ternary and drop an unused form watch
flagged by CI.
* fix: sync skills_enabled with selection edits and hydrate agent file entries
skills_enabled is the master opt-in for the skill allowlist, and an empty
allowlist with the flag on means the full accessible catalog. Selection
edits now sync the flag on empty/non-empty transitions via a shared
skillsEnabledTransition helper: picking the first skill enables it so the
choice takes effect on save, and removing the last one disables it so the
agent doesn't silently escalate to every skill. Mid-selection edits leave
the flag alone, preserving the Advanced kill switch's
disable-without-clearing behavior.
Agents loaded from the API carry only tool_resources.*.file_ids; the
client-only context/knowledge/code file entry arrays were read directly,
so existing attachments rendered as empty and could not be removed. A new
useAgentFileEntries hook restores the legacy derivation (agent files query
merged into the file map via processAgentOption) and now feeds AgentConfig,
the item dialog, and the selected-items pipeline.
* fix: hide plugin tools from the marketplace when the tools capability is off
buildCatalog gated built-ins, MCP, and skills on their capabilities and
permissions but pushed regular plugin tools unconditionally, so deployments
that removed the tools capability still offered attachable tool cards in
the marketplace. The loop now requires AgentCapabilities.tools, matching
the old Add Tools gate.
* fix: strip legacy MCP tokens on removal, guard action creation, model button spacing
MCP selection accepts every historical token format (server placeholder,
raw server name, mcp_-prefixed, and per-tool ids in prefix/suffix shapes)
but removal only filtered the new placeholder plus the server's current
tool ids, so a legacy token left the server permanently selected and its
tools still expanded after save. Selection and removal now share a
matchesMcpServer predicate.
Creating an action from the marketplace on an unsaved agent opened an
editor whose save was guaranteed to fail; it now surfaces the existing
save-the-agent-first error, matching the action-removal guard.
The model picker button keeps its tight px-1 with a provider icon but gets
px-3 in the empty Select-a-model state so the placeholder is not flush
against the border.
* fix: strip legacy prefix MCP tokens in useRemoveMCPTool
The hook only filtered the raw server name and suffix-delimiter tokens,
so confirming removal in the selected-tools section left persisted
prefix-format tokens (mcp_<server>, mcp_<server>_<tool>) in the form and
the row reappeared as selected. It now shares the matchesMcpServer
predicate with the selection logic so removal can never lag selection.
* fix: exact MCP token matching and keep errored skill lookups removable
The mcp_<server>_ prefix clause in matchesMcpServer was invented by the
redesign, not a persisted format (mcp_prefix is only ever used as the
exact mcp_<serverName> pluginKey), and it claimed longer server names
sharing a prefix: with servers github and github_extra, removing github
also stripped github_extra's tokens. The predicate now only matches exact
or delimiter-bounded shapes.
An off-page selected skill whose per-id lookup failed with a transient
error (retry disabled) vanished from the selected list until remount. Any
settled lookup failure now keeps the placeholder entry so the allowlist id
stays visible and removable; only in-flight lookups are briefly hidden.
* fix: route file-backed built-in removal to the file manager
Code Interpreter and File Search stay selected while they hold code_files
or knowledge_files, so removing them by flipping the capability flag left
the row visible and unremovable. Their removal now opens the config dialog
where the files are managed, mirroring the file-only context built-in;
with no files attached the flag still toggles off for a clean removal.
* fix: preserve negative values in numeric parameter inputs
sanitizeIntegerInput stripped every non-digit, so typing -1 in a numeric
parameter field became 1. That broke Google thinkingBudget, where -1 is
the dynamic/auto-thinking sentinel (range min is -1): users could no
longer select auto and risked sending a one-token budget. The sanitizer
now takes an opt-in allowNegative flag that keeps a single leading minus,
and DynamicInput passes it when the field's range permits negatives.
Thousands-separator cleanup is unchanged for all other fields.
* fix: keep in-progress negative numeric input and localize the actions heading
Typing a leading minus in a negative-capable numeric parameter (Google
thinkingBudget) sanitized to a lone '-', which was then coerced by
Number('-') to NaN, so the sign could not be typed before the digits. The
lone '-' is now stored as a string until a digit resolves it to a number,
matching how the empty-string case is already handled.
The agent builder actions panel heading hard-coded 'Add'/'Edit actions';
it now uses com_assistants_add_actions and a restored
com_assistants_edit_actions key so non-English locales translate it.
* chore: fix import order drift flagged by CI
* fix: treat pending web-search auth verification as needs_setup
While useVerifyAgentToolAuth is still loading, data is undefined so
web_search was not marked needs_setup, and the marketplace card takes the
direct-enable path only when status is not needs_setup. On a slow
connection a click before the response arrived enabled web_search without
collecting the required user-provided key. The auth map now flags
web_search needs_setup while the query is loading, routing the click to
the config dialog; once verification resolves, a system-defined deployment
or a satisfied key clears the flag for a direct toggle.
* test: update agent builder e2e selectors
---------
Co-authored-by: Danny Avila <danny@librechat.ai>
|
||
|
|
84fa6aa820
|
🧹 feat: Eager HITL Checkpoint Cleanup (Expiry + Deletion) & Full-Wiring E2E (#14123)
* feat: eager HITL checkpoint cleanup on expiry + deletion, full-wiring e2e Follow-up to the lazy checkpointer (#14024): two paths still left a paused run's durable checkpoint to the 24h Mongo TTL, and no test exercised the whole HITL seam with real components. 1. Approval expiry: GenerationJobManager.setApprovalExpiredHandler(fn) — a non-destructive host hook fired after expireApproval's CAS succeeds (periodic sweeper AND stale-submit path), safe on startups that run constructor defaults. Both startups (index.js configureGenerationStreams, experimental.js) register a handler that prunes the checkpoint, resolving config lazily per expiry (streamId === conversationId === thread_id). 2. Conversation deletion: deleteConvos now returns the deleted conversationIds; the three deletion paths (DELETE /convos, DELETE /convos/all, account deletion) prune them via the new bulk deleteAgentCheckpoints (one $in deleteMany per collection). The delete routes gain configMiddleware for the checkpointer config. 3. Full-wiring e2e (hitlCheckpoint.e2e.spec.js): real SDK Run driven by FakeChatModel calling a gated tool, real PreToolUse/humanInTheLoop wiring, real LazyMongoSaver over mongodb-memory-server, real GenerationJobManager, real /resume controller via supertest. Asserts: clean turn persists nothing; error turn persists nothing; pause -> HTTP approve -> gated tool executes exactly once -> finalize prunes the checkpoint; expiry prunes the abandoned pause eagerly. Tests: 3 handler unit tests (pendingAction.spec), 2 bulk-prune integration tests (checkpointer.integration.spec), convos route + deleteUser specs updated, 4 e2e scenarios. 207 tests green across changed areas. * fix: tenant-scoped expiry prune, store-won expiry relay, resilient deleteConvos ids Codex round 1 on #14123 — all three valid: 1. The approval-expired handler now receives the expired JOB so both startups resolve config in the paused job's tenant/user scope (getAppConfig({userId, tenantId})) — a tenant checkpointer override no longer sends the prune to the base config's collections. expireApproval fetches the job best-effort. 2. Multi-replica: when RedisJobStore.cleanupRequiresActionIndex wins the expiry CAS on another replica, this replica's sweeper relay branch now runs the approval-expired cleanup too (prune is idempotent) — store-driven expiry no longer bypasses the hook. 3. deleteConvos: post-delete cleanup (deleteMessages, project stats refresh) is now best-effort — the conversations are already gone, so throwing hid the deletion and dropped the conversationIds the checkpoint prune needs, unrecoverable on retry. Updated the existing tag-decrement-on-failure test to the new contract (ids still returned). Tests: handler-receives-job, store-won relay path, ids-survive-cleanup-failure. 134 tests green across changed suites. * fix: relay cleanup independent of cached errorEvent; enter tenant ALS context Codex round 2 on #14123: 1. The sweeper's relay branch gated BOTH the terminal-error emit and the new checkpoint cleanup on !runtime.errorEvent — but a reconnect seeds errorEvent from the aborted job (runtime-state creation), which then suppressed the cleanup entirely. The emit stays gated; the idempotent cleanup now runs independent of the cached error, once per runtime lifetime (approvalCleanupRan flag — the aborted job is swept repeatedly). 2. Passing userId/tenantId to getAppConfig only keys the config cache; the Config query is ALS-scoped by the tenant-isolation plugin. Both startup handlers now ENTER the paused job's tenant context via tenantStorage.run before resolving config + pruning, so a tenant checkpointer override is honored in strict and non-strict modes. Tests: relay-cleanup-with-cached-error (reconnect simulation), repeated sweeps run the cleanup once. 27+4 green. * fix: dedup expiry cleanup across winner and relay paths Codex round 3 (P3): expireApproval ran the handler without marking the runtime's approvalCleanupRan flag, so the next sweep's relay branch (the aborted job outlives expiry for the completed-job TTL) ran the cleanup a second time. The dedup now lives inside runApprovalExpiredHandler — the single choke point both paths call — set-before-run, once per runtime lifetime. Test: local expiry followed by a sweep fires the handler once. |
||
|
|
7b7fa496aa
|
🗃️ perf: Cache Group Memberships for ACL Principal Resolution (#14075)
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
GitNexus Index / index (push) Waiting to run
GitNexus Index / post-index (push) Blocked by required conditions
* 🗃️ perf: Cache Group Memberships for ACL Principal Resolution Continues #14069: caches resolved group-membership ids per member key in a new USER_PRINCIPALS namespace so repeated permission checks skip the group query, with exact invalidation on every membership mutation, same-process build dedup, and optional Redis cross-container build locks. Co-authored-by: Peter Rothlaender <peter.rothlaender@ginkgo.com> Co-authored-by: Joachim Keltsch <joachim.keltsch@daimlertruck.com> * 🧵 fix: Defer Transactional Invalidation and Gate No-Op Bulk Clears Codex round-2 findings on the principals cache: membership writes inside a transaction now defer cache invalidation until the session ends so concurrent readers cannot re-cache pre-commit state with no later correction, and bulkUpdateGroups skips invalidation entirely when MongoDB reports no modified or upserted documents. userGroup.spec.ts now runs on a single-node replica set so the deferral is covered by a real transaction. * 🌐 fix: Evict Cross-Process Stale Rewrites and Scope Entra Sync to Tenant Codex round-3 findings: a delayed second invalidation pass (lock wait plus a short grace) now evicts membership entries that a build in another container re-cached after the first delete, and syncUserEntraGroupMemberships establishes the user's tenant ALS context when the OAuth callback runs it pre-middleware, so tenant-scoped principal keys are invalidated (and sync queries and created groups are scoped) exactly like authenticated reads. Deferred transactional invalidations snapshot the caller's ALS so they keep tenant scoping. --------- Co-authored-by: Peter Rothlaender <peter.rothlaender@ginkgo.com> Co-authored-by: Joachim Keltsch <joachim.keltsch@daimlertruck.com> |
||
|
|
ed8547018c
|
⚡ perf: Persist HITL checkpoints only on pause (lazy checkpointer) (#14024)
* ⚡ feat: Persist HITL checkpoints only on pause (skip clean-exit writes) With `durability: 'exit'` (set by the SDK whenever a checkpointer is active) LangGraph persists ONE checkpoint at the exit boundary on EVERY run — paused or not. So a non-paused HITL turn writes a dead checkpoint whose only fate is to be pruned by deleteAgentCheckpoint: pure write+delete churn on the common path, given HITL only ever resumes an *interrupt* checkpoint. `InterruptOnlyMongoSaver` (a MongoDBSaver subclass) persists only interrupt checkpoints and discards clean-exit ones, so a non-paused turn writes nothing. How it tells them apart (verified empirically against @langchain/langgraph, not docs): when a run interrupts, the runner calls `putWrites` with the `INTERRUPT` ("__interrupt__") channel for the checkpoint it's about to create, and that write's `config.checkpoint_id` equals the `checkpoint.id` of the `put` that immediately follows. A clean exit calls `put` with no preceding interrupt `putWrites`. So we record the checkpoint id of any interrupt `putWrites` and persist a `put` only when its `checkpoint.id` was so marked. Keying on the globally-unique checkpoint id (not thread_id) keeps this correct even when two runs race on the same conversation (the job-replacement scenario). Correctness is preserved end-to-end: interrupt checkpoints + their pending writes persist exactly as before (resume unchanged); clean checkpoints were only ever written-then-pruned, so not writing them is observationally equivalent. The eager prune stays as the backstop. Tests (mongodb-memory-server): a bare put() is discarded; an interrupt-seeded checkpoint is persisted with its __interrupt__ pending write; and an end-to-end real-graph run writes 0 checkpoints on a clean completion and a resumable one on interrupt. NOTE: a non-paused turn's deleteAgentCheckpoint now finds nothing to delete (a 0-match no-op) — a follow-up can skip that call entirely once the lingering-abandoned-pause cleanup role is reassigned to the TTL + expiry sweeper. * ⚡ feat: Drop the redundant clean-path checkpoint prune With the lazy checkpointer (InterruptOnlyMongoSaver) a non-paused turn no longer writes a clean-exit checkpoint, so the post-completion prune in chatCompletion's finally had nothing left to delete. It was also already redundant: every fresh turn runs a pre-run prune (`deleteAgentCheckpoint` before `processStream`) that clears any checkpoint orphaned by a prior abandoned pause — verified empirically that a lingering interrupt checkpoint WOULD otherwise poison a fresh turn (LangGraph continues the abandoned state + re-interrupts), and that the pre-run prune is what prevents it. The Mongo TTL remains the backstop, and the resume path still prunes after a successful finalize. Removing the clean-path prune also deletes its job-replacement race surface (round-17 F21): an older run's late finally can no longer delete a newer paused run's checkpoint, because there is no longer a clean-path prune to race. Dropped the now-dead F21 predicate test. Net per non-paused HITL turn: from {pre-run prune + checkpoint write + post-run prune} down to {pre-run prune} — no write, no post-completion delete. * 🛡️ fix: Anchor any pending-write checkpoint; stale-only eviction (Codex) Broaden the lazy saver's keep-rule from "interrupt-only" to "persist any checkpoint that carries pending writes" (renamed InterruptOnlyMongoSaver → LazyMongoSaver). This makes it robust to delta-channel graphs without changing behavior for LibreChat's graph: - K1 (P1): a delta-channel graph can write a synthetic PARENT/anchor checkpoint (no __interrupt__ mark) that the interrupt checkpoint then points at, with the delta writes stored under the parent id. The old rule discarded that parent, breaking delta-state resume. Now any checkpoint that received putWrites is persisted, so the anchor parent and its writes survive and resume can walk the chain. - K3 (P2): for the same reason, clean delta-write rows are no longer orphaned — their checkpoint is persisted alongside them. (For LibreChat's standard Annotation/messages graph a clean run makes no putWrites at all — verified empirically — so the common path still writes nothing and the optimization is unchanged.) - K2 (P2): the 1024 FIFO cap could evict a valid in-flight id whose put() was just behind Mongo I/O, mis-classifying its interrupt checkpoint as a clean exit. Replaced with time-based eviction: only ids older than 5 min (a put always follows its putWrites within ms) are swept; a recent in-flight id is never dropped, and the map grows rather than evict a valid id if nothing is stale. New integration test: a checkpoint anchored by a NON-interrupt write is persisted. Full agents/HITL suites green (108). * style(checkpointer): fix import order to satisfy sort-imports CI * fix(checkpointer): don't persist failed-turn (error-only) checkpoints LazyMongoSaver anchored on ANY pending write, so a non-paused turn that errors (LangGraph records an __error__ write then a put) was persisted and, with the clean-path prune removed, lingered until the next fresh-turn prune or the Mongo TTL. Anchor only on resumable writes — INTERRUPT or a real (non-__-prefixed) state/delta channel — so error/bookkeeping-only checkpoints are discarded at the source. Addresses Codex P3. Codex P2 (delta-stub parent orphan) is not reachable: the SDK graph uses standard Annotation/MessagesAnnotation channels (no DeltaChannel), and under durability:'exit' putWrites precedes put with a parentless boundary checkpoint — probe-confirmed against @langchain/langgraph@1.4. Documented the durability:'exit' invariant the saver depends on. Tests: error-only put discarded; e2e throwing graph persists 0 checkpoints. * fix(checkpointer): drop bookkeeping-only write batches, not just the checkpoint The prior fix stopped the failed-turn CHECKPOINT from persisting, but putWrites still forwarded the __error__ batch to MongoDBSaver.putWrites — writing a row to agent_checkpoint_writes whose parent checkpoint is then discarded. With the post-run deleteThread removed, that orphan row lingered until the Mongo TTL or the conversation's next pre-run prune. putWrites now drops a non-resumable (bookkeeping-only) batch entirely instead of forwarding it. Probed against a real MongoDBSaver (mongodb-memory-server): a throwing graph now leaves 0 checkpoints AND 0 write rows (was 0 + 1 orphan), while interrupt->resume is unaffected — the __interrupt__ write is resumable so it is still forwarded. Addresses Codex P2 (round 3). Tests: error-only put leaves no checkpoint and no write row; e2e throwing graph leaves both collections empty; new e2e interrupt->resume completes with the approval value. * fix(checkpointer): un-anchor a checkpoint whose putWrites failed; freshen comments Self-review findings on the converged PR: 1. LangGraph dispatches put() concurrently with putWrites (probe-confirmed on 1.4.5), and put() still completes when putWrites rejects — so a transient Mongo failure during the interrupt write could persist a checkpoint whose __interrupt__ row is missing (an unresumable phantom pause). putWrites now deletes the write anchor on rejection (best-effort) and rethrows, so that put() discards the checkpoint instead. The pre-recorded anchor stays where it is — recording after the await would drop slow-I/O interrupts on the success path, which the same probe showed is reachable. 2. Renamed leftovers: two comments still said InterruptOnlyMongoSaver; the class is LazyMongoSaver. 3. Documented why the pre-run prune is deliberately unconditional per HITL turn (any cheaper gate can go stale across replicas and skip the prune exactly when an orphaned interrupt exists). Test: failed putWrites → subsequent put persists nothing (14/14 green). * fix(checkpointer): bookkeeping write batches follow their checkpoint's fate The round-3 rule dropped bookkeeping-only putWrites batches (__error__/ __resume__/__no_writes__) unconditionally — batch-scoped, when the decision must be checkpoint-scoped. Probe-confirmed (langgraph 1.4.5, durability:'exit'): a Send fan-out that pauses on one sibling records the completed siblings as pure __no_writes__ batches on the RETAINED interrupt checkpoint; dropping those markers makes resume re-execute the completed siblings (side effects measured twice). Addresses Codex M2 (P2). putWrites now PARKS a bookkeeping-only batch in memory until the checkpoint's fate is known: forwarded when the checkpoint is anchored (or was just persisted — put is dispatched concurrently), dropped when put discards it. Net: an errored turn still leaves nothing durable (0 checkpoints, 0 write rows), and a retained checkpoint stores byte-for-byte what a plain MongoDBSaver would. Codex M1 (__resume__ lost on re-pause) did not reproduce: the re-pause emits [__interrupt__,__resume__] as ONE batch (anchored, forwarded whole) and a second resume on a rebuilt graph replays both answers correctly — but the fate-scoped buffering now covers a lone __resume__ batch in any ordering too. Tests: bookkeeping preserved on a retained checkpoint in either arrival order; e2e Send-sibling pause/resume with side-effect counters (was {a:2,c:2} under the drop rule, now {a:1,c:1}); error-only turn still leaves both collections empty. 16/16 green. |
||
|
|
424ccffd83
|
🪝 feat: Configurable Tool-Approval Policy via Programmatic Hooks (#14025)
Some checks failed
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Has been cancelled
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Has been cancelled
GitNexus Index / index (push) Has been cancelled
GitNexus Index / post-index (push) Has been cancelled
* 🪝 feat: Programmatic tool-approval hook seam (configurable beyond on/off) Adds a process-wide registry so host code can plug context-aware PreToolUse decision hooks into the tool-approval policy, composing with the static `endpoints.agents.toolApproval` config instead of replacing it. - `registerToolApprovalHook(factory, { matcher? })` — register a factory that builds a PreToolUse hook per run from a ToolApprovalHookContext (userId, conversationId, tenantId, appConfig); return undefined to opt the run out. Returns an unregister fn. - `buildHITLRunWiring(policy, context)` now registers the static-config policy hook as the baseline, then layers each resolved host hook after it. Decisions fold in the SDK as deny > ask > allow, so a host hook can only TIGHTEN a configured ask/deny — it can never silently auto-approve past policy (to loosen, change the static policy). updatedInput / allowedDecisions follow the SDK's last-writer-wins, so host hooks win over the baseline. - `createRun` threads the per-run context (user / conversation / tenant / appConfig) into the wiring; non-HITL and HITL-disabled runs never invoke any factory. This unlocks dynamic policy the static name-lists can't express — per-args (e.g. ask before write_file outside a workspace, the SDK's createWorkspacePolicyHook shape), per-agent, per-user. Inert until tool approval is enabled and the caller is hitlCapable. Tests: registry register/unregister/opt-out/order (hooks.spec.ts) + wiring composition, context passthrough, and disabled-path inertness (runtime.spec.ts). Full HITL suite green. * 🪝 feat: Config-driven tool-approval hook loader (librechat.yaml → hook modules) Lets operators declare programmatic tool-approval hooks in config instead of code, so the registerToolApprovalHook seam is usable without a custom build. - Config (data-provider): `endpoints.agents.toolApproval.hooks[]`, each entry `{ module, matcher?, options? }`. `module` is a bare package name or a path (resolved against the app root); its default export is a builder `(options?) => ToolApprovalHookFactory`. - Loader (@librechat/api `loadToolApprovalHooks`): imports each module, builds the factory with the entry's options, and registers it (with its optional tool-name matcher). Reload- safe (each call first unregisters its previous batch, leaving code-registered hooks alone) and robust — an unimportable module / non-function export / throwing builder is logged and skipped, never crashing startup or blocking the other hooks. Importer is injectable for tests. - Startup (api/server/index.js): loads the configured hooks once after appConfig resolves. SECURITY: modules are dynamically imported + executed in-process; this is admin-level config, documented as trusted-code-only. Tests: 9 loader cases (default/no-default export, options passthrough, bad-export skip, builder-returns-non-function skip, import-failure resilience, continue-past-bad-entry, reload de-dup). Full HITL suite green (80). * 💄 style: Sort imports in HITL hook spec files (CI sort-imports:check) * 🛡️ fix: Harden tool-approval hook loader (Codex review) Six P2 findings on the hook loader / startup wiring: - CJS/transpiled interop: unwrap a nested `default` (TS/Babel `exports.default = fn` surfaces through import() as `{ default: { default: fn } }`) before rejecting a module, so documented default-export hook modules actually load. - Validate the matcher regex at load time and skip invalid ones — the SDK compiles it with `new RegExp` at run-build time, where a bad pattern would throw out of buildHITLRunWiring and break EVERY HITL run instead of just skipping the one bad hook. - Honor the `enabled` kill switch: startup now passes hooks to the loader only when toolApproval is enabled, so a disabled endpoint imports/runs nothing (and unregisters any prior batch). - Resolve app-root-relative paths without a leading dot: a bare specifier that is a real file under basePath (e.g. `config/hooks/workspace.js`) resolves as a path; scoped/other bare names still import as packages. - Base-config-only: documented that hooks register once process-wide at startup and are NOT reloaded from per-role/user/tenant overrides — encode per-tenant logic inside the hook. - Wire the loader into the clustered startup path (api/server/experimental.js) too, not just the standard server. Tests: CJS-interop unwrap, invalid-matcher skip (+ sibling still loads), and specifier resolution (app-root file / bare package / ./relative). Full HITL suite green. * 🛡️ fix: Read tool-approval hooks from base config in clustered startup (Codex) The clustered experimental.js path read toolApproval from getAppConfig() (which merges DB __base__ overrides with no principal), so a DB override could enable/disable/replace toolApproval.hooks and import those modules in every worker — violating the base-config-only contract and diverging from the standard server. Fetch getAppConfig({ baseOnly: true }) specifically for the hook loader, matching api/server/index.js. |
||
|
|
53b3e166d8
|
📉 perf: Skip Redundant Permission Queries on MCP Servers List (#14077)
Some checks failed
Docker Dev Images Build / build (Dockerfile, librechat-dev, node) (push) Has been cancelled
Docker Dev Images Build / build (Dockerfile.multi, librechat-dev-api, api-build) (push) Has been cancelled
GitNexus Index / index (push) Has been cancelled
Sync Locize Translations & Create Translation PR / Sync Translation Keys with Locize (push) Has been cancelled
Sync Helm Chart Tags / Ignore non-main push (push) Has been cancelled
Sync Helm Chart Tags / Sync chart tags (push) Has been cancelled
GitNexus Index / post-index (push) Has been cancelled
Sync Locize Translations & Create Translation PR / Create Translation PR on Version Published (push) Has been cancelled
|
||
|
|
ac7dc490a7
|
🪭 refactor: safe structured failure logging for langfuse fanout gateway (#14050) | ||
|
|
b6c0bc7c0d
|
⚡ perf: Skip Role Route Capability Probe for Own and Default Roles (#14073) | ||
|
|
f1ea4159af
|
🎣 fix: Surface Resumable Stream Start Errors (#14072)
* fix: surface resumable stream start errors * style: format stream start error check * fix: handle crlf stream start errors * fix: parse only stream error event data |
||
|
|
8f0756ed9e
|
fix: stop the artifact-edit retry loop on unrecoverable 400s (#14060) | ||
|
|
477ee3439c
|
🧪 ci: De-flake ConversationsSection memoization spec on slow runners (#14071)
waitFor resolves as soon as BookmarkNav's data hook has fired once, but the Suspense resolution commit can still have a trailing render pass pending on slow runners (Windows CI shards). The first stream tick then flushes that leftover pass alongside the tick, inflating the memoized children's render counts past the captured baseline (Expected: 1, Received: 2). Flush pending commits with an empty async act() before capturing baselines. |
||
|
|
0eef64344d
|
🌍 i18n: Update translation.json with latest translations (#14070) | ||
|
|
e452a130e9
|
⚡ perf: Minimize group membership query in principal resolution (#14055)
getUserPrincipals resolves a user's ACL principals on nearly every
authenticated request. It fetched full group documents (including entire
memberIds arrays) only to read each group _id, and always issued a
separate User lookup for idOnTheSource.
- Project { _id: 1 } on the memberIds group query so it returns only ids
and can be served from the { memberIds: 1 } index instead of fetching
and decoding whole group docs.
- Accept role and idOnTheSource from the already-loaded request user and
thread them from the capability middleware, collapsing the hot path to
a single indexed group query (idOnTheSource: null means known-local).
|
||
|
|
6b049c2eed
|
🧠 fix: Default Bedrock thinking maxTokens to model max output (#14058)
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
GitNexus Index / index (push) Waiting to run
GitNexus Index / post-index (push) Blocked by required conditions
* 🧠 fix: Default Bedrock thinking maxTokens to model max output
Thinking tokens share the maxTokens output budget with tool-call
arguments (e.g. a create_file content), so the low Bedrock defaults
(8192 for enabled thinking, ~4096 server-side for adaptive when unset)
truncated large authored files mid-argument — surfacing as
OutputTruncationError once reasoning actually emits.
Default maxTokens to the model's full max output via
anthropicSettings.maxOutputTokens.reset(model), mirroring the
direct-Anthropic path. Explicit maxTokens/maxOutputTokens are respected.
* fix: canonicalize number-first Claude aliases before resolving max output
|
||
|
|
8683eccbbc
|
🧠 fix: Apply Bedrock thinking config to bare inference-profile model IDs (#14054)
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
GitNexus Index / index (push) Waiting to run
GitNexus Index / post-index (push) Blocked by required conditions
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 Locize Translations & Create Translation PR / Sync Translation Keys with Locize (push) Waiting to run
Sync Locize Translations & Create Translation PR / Create Translation PR on Version Published (push) Blocked by required conditions
Sync Helm Chart Tags / Ignore non-main push (push) Waiting to run
Sync Helm Chart Tags / Sync chart tags (push) Waiting to run
* 🧠 fix: Apply Bedrock thinking config to bare inference-profile model IDs The Bedrock request parser gated thinking config, sampling handling, and the anthropic_beta headers on the model ID literally containing `anthropic.`. When a deployment uses an application inference profile, the LibreChat model ID is a bare `claude-*` (e.g. `claude-sonnet-5`) that maps to the profile ARN — so the gate never matched, no `thinking` config was sent, and reasoning models returned empty thinking blocks (most visibly: Claude Sonnet 5 never streamed reasoning, while `us.anthropic.claude-opus-4-8` did). Match on the `claude` family token instead of the `anthropic.` prefix so prefixed (`anthropic.`, `us.`, `global.`) and bare inference-profile IDs are handled identically. Verified e2e against live Bedrock via the agents SDK: a bare `claude-sonnet-5` now sends `{type:'adaptive', display:'summarized'}` and streams reasoning. Non-Claude Bedrock models (llama/cohere) and pre-thinking Claude (3.5 sonnet) are unaffected. * 🧹 fix: Strip stale thinking fields for non-thinking Claude Bedrock IDs Follow-up to the bare-ID matching change: broadening the anthropic guard to match bare `claude-*` meant a non-thinking Claude profile (e.g. a bare `claude-3-5-sonnet` inference profile) took the Claude cleanup branch, which kept persisted `thinking`/`anthropic_beta`/`output_config` from a previously-selected thinking model — leaking unsupported fields after a model switch. Extract `isThinkingModel` and, in the Claude cleanup branch, strip the thinking fields when the model isn't thinking-capable. Also fixes the pre-existing prefixed `anthropic.claude-3-5-sonnet` case (which already kept stale thinking). Thinking-capable models (sonnet-5, 3.7-sonnet) still keep their config. * 🩹 fix: Preserve user anthropic_beta on non-thinking Claude cleanup The non-thinking stale-cleanup deleted amrf.anthropic_beta, but that is the generic Bedrock Anthropic beta field and may carry a user opt-in (e.g. max-tokens-3-5-sonnet-2024-07-15 for extended output on Claude 3.5). Strip only the thinking-specific fields (thinking/thinkingBudget/effort/output_config) and leave anthropic_beta intact. * fix: clear persisted AMRF (output_config, thinking, generated betas) on bare Bedrock profiles * fix: preserve persisted effort on resume + strip stale thinking/betas across bare profiles * fix: normalize string/comma-delimited anthropic_beta before stripping generated betas |
||
|
|
53ee82fe5d
|
🩹 fix: Coerce Stringified edit_file Edits (JSON-in-JSON) (#14056)
Models sometimes pass edit_file's `edits` as a JSON-encoded string (or stringify individual edit entries) instead of a real array. That failed validation with "Provide old_text and new_text, or a non-empty edits array" and forced a full retry round-trip. normalizeEditArgs now JSON-parses a stringified `edits` value (and stringified entries) before validating. Non-strings and unparseable strings are left untouched, so the existing explicit errors still fire. |
||
|
|
89931baf22
|
🚪 fix: Support Admin Redirect Detection for Same-Origin Subpaths (#14040) |