Repository navigation
Prepare LibreChat v0.8.8 compatibility candidate - #6
Merged
dschenk merged 1175 commits intoOct 1, 2026
Merged
Conversation
dschenk
force-pushed
the
agent/prepare-librechat-v0.8.8-rc1-1217541308456676
branch
from
September 3, 2026 05:30
1f89549 to
7434ceb
Compare
* fix: preview extracted upload text * fix: preserve extracted-text preview metadata * fix: complete extracted preview recovery paths * fix: retain preview metadata across attachment recovery * fix: preserve preview metadata across legacy steering * fix: hydrate legacy steer preview metadata * fix: hydrate reconnect steer metadata * fix: preserve text preview metadata in resumed shares * fix: hydrate legacy preview metadata safely * fix: close preview lifecycle and recovery gaps * fix: preserve preview ownership and wire metadata
…#15940) * 🪂 feat: Opt-In Text Fallback for Files No Tool Can Read An upload routed to tools (`llmDeliveryPath: none`) reaches nothing when the agent handling the turn runs neither Run Code nor File Search, or runs one that cannot read the type. With `fileConfig.endpoints.<endpoint>.textFallbackWithoutTools` (also accepted at the top level and inherited from `default`), such a turn delivers the text extracted at upload instead. The setting is off by default, which keeps today's behavior. - Upload: when the setting is on and an inferred `none` route finds no reading tool, the built-in document parser or native text reader stores the text on the file, best effort, under the extracted-text size cap and content policy. - Turn: `resolveTurnLLMDeliveryPath` re-resolves each inferred route against the tools the agent runs, and `applyTurnTextFallback` marks copies of the records a turn loads, so endpoint filtering, limits, inspection, history replay and steer media all see the text the turn delivers. The stored route is never rewritten. - Explicit destinations, records predating routing, and turns whose tools are unknown are left as they are. * 🧪 test: Resolve Custom Endpoint Fallback Under the Endpoint Name Uploads route a custom endpoint agent by its saved provider, the endpoint name. initializeAgent marks turn copies before getProviderConfig swaps the provider for the backing client, and the endpoint keeps the name afterward, so the client and child encoders resolve the opt-in under the same endpoint. Cover both the initialization path and the post-initialization agent shape. * ♻️ refactor: Share One Extracted-Text Storage Cap Across Uploads The fallback defined its own copy of the 15 MiB cap context uploads already apply to extracted text. Export MAX_STORED_EXTRACTED_TEXT_BYTES from files/extract and use it in both places, documenting that it bounds what a MongoDB file document can hold rather than operator policy. * 🧭 fix: Keep Fallback Text Wherever a Later Turn Can Deliver It Extraction skipped uploads filed under a reading tool, so a handoff agent without that tool, or the same agent after its tools or grants changed, had no text to fall back to. Store fallback text for every inferred route on a message attachment; files kept on an agent's tool resources never reach a prompt and still skip it. Turn marking only ran while the fallback was enabled, so a stored tool-routed record whose type the endpoint now routes to text stayed marked `none` and `extractFileContext` skipped it. applyTurnTextDelivery (renamed from applyTurnTextFallback) now marks any stored `none` record with text that the turn resolves to text, and returns early when no such record is loaded. * 🚦 fix: Admit Every Attachment by the Route Its Turn Delivers A record upload routed to tools that the current endpoint sends to the provider stayed marked `none` on the turn copy, so admission skipped it while BaseClient encoded its bytes, bypassing attachment count and size limits. applyTurnDelivery (renamed from applyTurnTextDelivery) now gives every record whose route upload inferred the route the turn resolves, before endpoint filtering, limits and inspection, matching the run-file encoder. hasInferredLLMDeliveryPath shares the inferred-route rule with the resolver. Fallback extraction selected the document parser inside the CJS upload path. resolveUploadFallbackText now takes the upload and picks parseDocument or parseTextNative itself, with the extractors injectable, so process.js passes request data only. Its tests run the real parser against the xlsx fixture and the real native reader against temporary files. * 🎚️ fix: Read Custom Endpoint Dialect From Config on Both Sides of Init initializeAgent materializes turn routes before getProviderConfig swaps the saved custom endpoint name in agent.provider for its backing client, so routing read that name as a non-OpenAI dialect. Media a custom endpoint opted into could then be materialized off the provider path, skipping admission, while the finalized client still sent the bytes. resolveAgentDeliveryRouting now reads the dialect with getCustomEndpointProvider, as upload does, which holds before and after the swap. The upload fallback plan now keys on the chosen-destination marker the turn resolver reads, so an upload recorded as chosen, including every legacy chooser upload, never pays for text a turn cannot deliver. * 🛡️ fix: Apply Turn Routes Only While the Record Stays Admitted Resolution while initializing and at delivery can disagree: delivery reads a Responses API choice only the finished client config holds. Applying a route that takes a record out of admission, off the model path or to text it never stored, would then skip the limits for a file the client still sends. applyTurnDelivery now applies a route only while the record stays model-bound; admitting a record the turn leaves out cannot slip past a limit. * 🕹️ feat: Run Declared Project Actions on Attached Workers (LibreChat-AI#15943) * feat: use declared attached project actions * Complete named action definitions and approval previews * Sort named environment imports * Sort native environment fixture imports * 🔓 fix: Keep Code Approval Mode Selectable Mid-Run (LibreChat-AI#15938) * 🔓 fix: Keep Code Approval Mode Selectable Mid-Run The composer's code approval mode selector was disabled while a run streamed, and a mode picked during a run was reverted when the final or abort event merged the server conversation back. The selector now stays usable in flight and both merges retain a mid-run selection, which the next send carries. * 🔓 fix: Retain Mid-Run Approval Mode Across Cache and Recovery Stamp the live approval mode onto the submission conversation at send so the retention baseline matches what was read, keep a retained mode in the conversation query cache on final and cancel, and rebuild failed or aborted conversations from a preset that carries the mid-run pick. * 🔓 fix: Scope Retained Approval Mode to the Submitted Conversation Retention now requires the live conversation to be the one the run submitted, or the id the server assigned to a new chat, so navigating elsewhere mid-run cannot write that conversation's mode back. A single recovery helper rebuilds failed or aborted conversations from the retained preset and patches the detail cache for every recovery site. * 🔓 refactor: Keep the Local Approval Mode on Every Server Merge Replace per-path retention with one rule: a locally picked code approval mode is newer than any server copy of the same conversation, so the final and abort merges and the error recovery preset keep it. The selector writes the pick into the conversation's detail cache, which navigation rebuilds from, so the pick is conversation-scoped without reading the index-global atom from the SSE handlers. * 🔓 fix: Prefer the Live Conversation When Caching the Final Merge The final handler's detail-cache merge now takes the local approval mode from the open conversation when it is the one that finished, falling back to the cached record only for a conversation that is no longer on screen. * 🔓 fix: Keep the Local Approval Mode Through Settled-Start Reconciliation The resumable transport's settled-start reconciliation and replacement handoff wrote a fetched conversation straight into conversation state and the detail cache. Both now apply the same local-mode rule as the event handlers, treating a new chat's pending id as the settled conversation's own. * 🔓 fix: Seed the Detail Record When a Mode Is Picked A conversation that has its id but no detail record yet now gets one from the live conversation when a mode is picked, the same key the resumable transport seeds optimistically, so recovery and navigation find the pick. A chat with no id yet still keeps the pick in conversation state alone. * ⬆️ chore: bump agents to 3.8.7 (LibreChat-AI#15947) * 🧭 refactor: Settle Turn Delivery Routing Once in Agent Initialization `initializeAgent` resolved the provider and its client options only after the turn's files were loaded and admitted, and the two delivery readers each rebuilt the attachment routing from the agent object at their own moment: `BaseClient` took the custom-endpoint dialect from the already-swapped `agent.provider`, the run-file encoder from whatever child config it was handed. Move `getProviderConfig`/`getOptions` ahead of file discovery, where nothing in between fed them, and settle one `deliveryRouting` value with every input final: the file policy under the endpoint's own name, the dialect its config declares, the Responses API decision the model call uses, and the transcription setting. `InitializedAgent`, the child encoder and `BaseClient` consume that value; `resolveTurnLLMDeliveryPath` is the one place a stored route is resolved again. * fix: Preserve attachment reachability across handoffs and late steers * test: Type the untrusted attachment reference fixture --------- Co-authored-by: Ravi Kumar L <upman@users.noreply.github.com>
* ⏱️ feat: Reveal Queued Follow-Up At Run Completion In an agent conversation the server admits a queued follow-up on its own clock, and the client learned of that successor only through the receipt poll, the active-job poll and a resume attach, so the user's words appeared as the latest turn a second or more after the reply above them ended. The queued follow-up is now drawn as the newest user turn the moment its predecessor completes cleanly, with its chip reduced to a remove action until the server admits it. The drawn turn is presentation intent only: it never enters the message cache, anchors no send, edit, fork or rerun, and shows only while the completed response it follows is the thread's tail. The intent ends once anything parents on that response, on a cancelled, dead or indeterminate receipt, or on the user's own removal. The composer queues behind it and the completed response offers no rerun while it stands. The receipt poll also stays alive while the client holds a server-owned row the projection has not settled, so a successor that starts and finishes inside one active-job poll gap is no longer noticed only on the next focus. * ⏱️ fix: Re-select the Reveal When Its Head Settles and Stop Polling Expired Rows A revealed head that is cancelled or dies before admission leaves the server-owned queue non-empty and its terminal evidence outside the settled receipts, so nothing re-ran the drain for the row the backend moves on to; the drain now subscribes to the admissible head and the reveal itself. An uncertain row whose reconciliation window has expired is held for manual recovery only and no longer keeps the receipt poll alive. * ⏱️ fix: Route Dictated Submissions Through the Composer's During-Run Path A transcription completing while a queued follow-up was about to start called the send path directly and could race the turn the server already owed. The form's routing (answer mode, then steer or queue during a run or a pending reveal, then an ordinary send) now lives in one function that typed and dictated submissions share; a refused during-run submit keeps the text in the composer. * fix: preserve queued turn ownership across admission handoff * fix: preserve queued handoff lineage and ownership through recovery * fix: delegate draft consumption and preserve refused prompts
* 🧷 feat: Carry Ask User Answers Past Compaction The user's answers to `ask_user_question` lived only in the tool result that received them. A checkpoint summary, the tool-output pruner or a full context window could remove that message, and the agent then asked again or acted on a summary's paraphrase. Every answered ask on the conversation branch is now rendered verbatim into the run's dynamic instructions, on the first request and on every resume, where the answer just given is read from the seeded content. Older answers drop first past a token budget; the newest set is always kept. New lever: `endpoints.agents.askUserQuestion.retainedAnswers` (`enabled`, `maxTokens`), on by default with a 4096-token budget. * 🧷 feat: Carry Ask User Answers Past Compaction The user's answers to `ask_user_question` lived only in the tool result that received them. A checkpoint summary, the tool-output pruner or a full context window could remove that message, and the agent then asked again or acted on a summary's paraphrase. Every answered ask on the conversation branch is now quoted verbatim at the top of the current user turn's prompt copy, in the user's own role: rebuilt from the stored rows every turn, never persisted, excluded from the memory copy, and counted in the prompt total with the active encoding. A warm event-actor turn reads the branch on demand. Older answers drop first past a token budget that includes separators; the newest set is always kept. New lever: `endpoints.agents.askUserQuestion.retainedAnswers` (`enabled`, `maxTokens`), on by default with a 4096-token budget. * 🧷 refactor: Read The Warm Branch Inside The Module The warm event-actor read lived as a conditional and a `getMessages` call in the CJS client. The typed module now takes `historyLoaded`, the identity and the query as dependencies, decides when to read, and owns the failure policy for both the read and the block itself; the client passes plumbing only. * 🧷 fix: Complete The Branch Behind A Summary Cut `loadHistory` already stops its walk at a checkpoint summary, so the rows `buildMessages` receives never held the answers this feature carries. The module now detects a branch that stops short of its root, whatever cut it, and completes it through the injected stored-row query, stored rows winning. The block goes on the latest user-authored row so a continued generation keeps it, and the render measures the whole block before reserving the omission note, so a set is only dropped when something truly has to go. * 🧷 fix: Reuse Loaded History And Read Every Ask Stamp A post-compaction turn read the conversation twice: once in `loadHistory`, which cuts at the checkpoint summary, and again in the module to complete the branch. The loader now keeps the rows it read and the module prefers them, querying only for a turn that made no history read. Two stored shapes were missed: a pause record whose `args` is the bare question string, now read as the legacy question, and a call whose input failed validation, whose persisted error output is no longer quoted as an answer. * 🧷 fix: Keep The Block Off The Stored Row And Out Of Memory Copies Array-form content is replaced instead of edited through the array the stored row shares, the block's cost is measured as two fresh counts of the same copy rather than against a possibly calibrated stored count, the memory copy is only built when memory extraction will read it, the loaded rows reach the agents client through a hook and are released once the block exists, and the tool-call-id merge is gone: rows are unique by message id and providers may reuse ids across turns. * 🧷 refactor: Apply The Answers Block Inside The Module The placement lookup, the prepend, the count delta and the memory-copy decision lived in the client after the context kickoff. They now live in `applyRetainedAnswers`, which receives the rows, the prompt copies, the count map, the counter and the memory dependency; the client keeps one call and passes plumbing. * fix: Apply Retained Answers After Summary Replay 🧷 * perf: Count Retained Context Exactly Within Its Budget 🧮 * fix: Preserve Retained Counts Across Checkpoint Recounts 🧾 * fix: Isolate Retained Context Counting and Provenance 🧵 * fix: Replace Warm Retained Context and Verify Answer Provenance 🪪 * fix: Use Workspace-Compatible Array Traversal 🧭 * fix: Refresh Retained Context at the Warm Turn Boundary 🧲
* fix: render agent descriptions consistently * fix: harden marketplace descriptions * fix: preserve rich description blocks * fix: preserve angle-bracket description text
…reChat-AI#15952) Registry names must be lowercase, so derive the GHCR image and Helm chart repository from a lowercased GITHUB_REPOSITORY_OWNER instead of github.repository_owner. Compare a11y PR heads against github.repository rather than a hardcoded owner, and remove the unused Azure self-hosted runner workflows.
dschenk
force-pushed
the
agent/prepare-librechat-v0.8.8-rc1-1217541308456676
branch
from
September 15, 2026 04:56
0e8f691 to
22dde90
Compare
…5962) * feat: carry schedule context into MCP token providers * fix: restore scheduled token context after approval pauses * fix: make scheduled identity boundaries explicit * test: keep owner-drain leases live through CI delays
…at-AI#15963) * fix: Preserve Code Environments Across Approval Pauses 🧷 * test: Match Agent Request Conversation Identity 🪪
* 🧱 fix: Keep Streamed Content When a Turn Fails A failure the server reported outside the run — a rejected start on a resumed turn, an error SSE event, a lost connection — rebuilt the response row from the submission's empty placeholder, so the failure replaced the reasoning, text and tool calls that had already streamed and stood alone as if it were its own message. A failure inside the run never did this: the server appends it as an ERROR content part and the row keeps its parts. resolveErrorTurn now builds every client-constructed failure the same way: the in-flight response keeps what it streamed, less stream holes and the empty text part a run opens before its first token, and takes the failure as one more ERROR part. A turn that fails before anything streamed is still the whole row. * 🪪 fix: Keep Render Identity and Skip Empty Slots in the Failed Row The streamed tail is compacted with isEmptyContentPart, so a comparison run's type: '' placeholders and a text or think part opened before its first delta no longer count as streamed content — a run that failed before either lane produced anything is the whole-row error again. Kept parts are stamped with preserveStreamedContentIdentity before the failure is appended, as the final path stamps them, so compacting a hole does not remount the settled row; the appended part is keyed past every existing key so a carried stamp cannot collide with it. The spec fixtures are typed so the client typecheck passes. * 🛤️ fix: Keep a Comparison Lane's Placeholder When the Other Lane Streamed Empty slots now decide only whether anything streamed. Once something did, every non-hole part stays, so a comparison run whose one lane produced output before the failure keeps the other lane's type: '' placeholder — without it the parallel grouping saw a single agent and demoted the surviving output to the sequential flow under the primary agent's name. * 🪪 fix: Keep the Streamed Row's Envelope When Appending a Failure The failed row keeps the author, model, icon and creation time it streamed under and takes from the failure envelope only its metadata. A server payload names System as its sender and the schema dates a fresh envelope now; spreading either over the row changed its attribution and timestamp when only a part was being appended.
* 🪃 fix: Remember the Last Code Approval Mode The code approval dropdown reopened on "Ask before changes" after every new chat and reload. `useCodeApprovalMode` derived its selection from `conversation.codeApprovalMode ?? 'ask'`, and a new conversation never carries that field: `buildDefaultConvo` runs the last setup through `parseConvo`, whose agents schema does not pick `codeApprovalMode`, so the pick was dropped. The menu now records each pick in a localStorage-backed Jotai atom, and the hook seeds its default from it when the conversation carries no mode of its own. The remembered value is a preference and not a grant: it passes the same policy gate as a stored mode, so anything current policy no longer allows falls back to `ask`, and sign-in/sign-out clears it so a shared browser does not hand the next account a permissive default. * 🎨 style: Format the Code Approval Preference Files --------- Co-authored-by: Lia <lia@librechat.ai>
…nts Module (LibreChat-AI#15910) types/assistants.ts held ~60 exports of which only ~11 were Assistants API types. The rest were core message-content types and the Agent entity types, so schemas.ts and agents.ts both imported foundational types from a file named after the Assistants API. Split into types/tools.ts (tool primitives, action naming) and types/content.ts (message content parts, annotations, tool calls), moved the Agent entity types into the existing types/agents.ts, and left a slim Assistants-only types/assistants.ts. No behavior change: every symbol keeps its public export from the package barrel, so no consumer outside data-provider changes. Co-authored-by: Lia <lia@librechat.ai>
* 👆 fix: Thumb-Sized Send Button on Touch Devices * fix: floor the submit target wherever touch is reachable, not only on a coarse primary pointer --------- Co-authored-by: Lia <lia@librechat.ai>
…breChat-AI#15983) * 🧦 fix: Restore OpenID Refresh After the Session Store TTL Expires express-session raises `failed to load session` only when the store holds no record for the session id. LibreChat-AI#15655 treated that as terminal, so a refresh whose cookie refresh token was still valid ended in 401 OPENID_SESSION_MISSING once the OpenID session store TTL (15 minutes by default) elapsed before the IdP access token (~1 hour with Entra ID). Reload the persisted record through one tolerant helper: an absent record is an empty session, not a session that advanced past this result, so publication seeds a new record. Store outages still propagate, and logout still fails a retired refresh token through its durable revoked publication flight. * 🧪 test: Pin the logout fence around the tolerated session reload Tolerating an absent session record leaves the durable revoked publication flight as the only thing between a logged-out refresh token and a resurrected session, so cover both of its checks with an expired record: the tombstone read that precedes the reload, and the completion that follows it. Drop a needless optional chain in publishResolvedSessionTokens, whose `req` parameter is not optional. --------- Co-authored-by: Lia <lia@librechat.ai>
* 📓 fix: Keep Typed Text When Starting a New Chat * 🧷 fix: Keep the Draft Claim Through the Empty-Files Write --------- Co-authored-by: Lia <lia@librechat.ai>
…5992) * fix: Enable programmatic Bash in selected attached workspaces * Centralize project PTC construction and preserve recognized capabilities * Prevent stale list refreshes from restoring removed pins
…Chat-AI#15982) * 🎚️ fix: Allow Opting Out of the MCP OAuth `resource` Parameter Adds `mcpServers.<name>.oauth.send_resource_parameter`, defaulting to true, so operators whose authorization server rejects RFC 8707 `resource` can complete the MCP OAuth flow. Suppression applies at send time only: Protected Resource Metadata is still discovered, still validated against the server URL (RFC 9728 §3.3) and still recorded on the stored client binding. * 🧹 chore: Drop Accidentally Tracked node_modules Symlinks Worktree-local symlinks were committed because .gitignore matches node_modules/ as a directory, not as a symlink of that name. They pointed outside the repository, so CI dependency installation failed and every downstream check with it. * 🧪 test: Cover the MCP OAuth `resource` Opt-Out at Token Exchange The authorization_code exchange reads the decision from flow metadata rather than live config, so a code obtained without `resource` is never exchanged with one. Covers both the opted-out flow and a flow initiated before the field existed, which must keep sending the parameter. * 💅 style: Apply Prettier to the Token Exchange Test * 🔒 fix: Make the MCP OAuth `resource` Opt-Out Actually Take Effect Addresses three Codex findings on the opt-out: - `startAuthorization` copies the configured `authorization_url` verbatim, so a `resource` an admin left in that URL survived into the request and Entra kept rejecting the flow. Opting out now deletes the parameter on both authorize paths instead of only declining to add one. - `.omit()` let a user-managed submission carrying `send_resource_parameter` parse successfully while discarding the field, so the opt-out was silently ignored. It is now `z.never().optional()`, matching the documented contract and the `audience` precedent. - A pending flow built with `resource` could be replayed for the full pending window after an operator set the option, reissuing the request they had just reconfigured away from. `isCurrentServerOAuthFlow` now compares the captured decision with live config. Stored tokens are unaffected, so changing the option still does not force re-authentication. * 🧯 fix: Suppress Inherited MCP OAuth `resource` and Stale Replays Addresses two further Codex findings on the opt-out: - The MCP SDK uses `token_endpoint` verbatim and the refresh paths post to the resolved token URL as-is, so a `resource` an admin left in `token_url` (or one on a discovered token endpoint) still reached the provider. The outbound URL is now sanitized at the exchange and in all three refresh paths. Stored metadata keeps its configured form so the client binding still matches. - Pending-flow replay was only guarded inside MCPConnectionFactory, so the browser initiate endpoint could still redirect to the stored URL. The shared pending helper now takes optional live config and refuses a replay whose captured decision no longer matches; the route passes it. * 🪤 fix: Scope the MCP OAuth `resource` Opt-Out to Paths It Can Serve Correctly The browser initiate route compared the pending flow against `resolveConfigServers`, which deliberately omits unmodified YAML servers (see `mcp/context.ts`). For the very config this option targets that read a captured `false` against `undefined`, reported drift on every initiation, and then returned 400 because factory-written flow states carry no `oauth` property to rebuild from. Reverted rather than patched: doing it right needs effective-config resolution behind a `packages/api` helper, per the `/api` wiring-only rule, and this sandbox cannot run that route suite. Retained and corrected: - The pending-flow guard no longer rides on `isCurrentServerOAuthFlow`, which also gates reuse of a recent COMPLETED flow. It was invalidating already-issued tokens, contradicting the promise that changing this option never forces re-authentication. Now a PENDING-only predicate at the two replay sites. - `ServerConfigsDB.sanitizeUserManagedOAuthConfig` strips `send_resource_parameter`, so a DB-backed or imported user config cannot activate the admin-only opt-out. --------- Co-authored-by: Lia <lia@librechat.ai>
…t-AI#15991) * 🧼 fix: Strip Cache-Bust Query Before Local Vision Encode Reused code-interpreter images persist filepath with a ?v= suffix. prepareImagesLocal now strips it before disk encode, matching crud/share. Co-authored-by: Zsanz3 <Zsanz3@users.noreply.github.com> * 🧪 test: Isolate prepareImagesLocal query-strip coverage Mock sharp and resize so the local vision-encode test does not load the rest of the file-strategy graph. Co-authored-by: Zsanz3 <Zsanz3@users.noreply.github.com> * 🪒 fix: Strip Cache-Bust Suffixes From Every Local File Read Reused code-interpreter outputs persist a `?v=<timestamp>` suffix on the file document's filepath, and local storage resolves that field into a filesystem path. prepareImagesLocal already handled it after the cherry-picked commits; getLocalFileStream did not, so every download-stream consumer (the download route, provisioning, agent and skill file reads) still hit ENOENT for a regenerated image. Move the strip into one documented helper and call it from all three local read paths. * 🧱 refactor: Move Cache-Bust Stripping Into the Storage Package `/api` holds wiring, not behavior, so `stripCacheBust` moves out of a new CJS helper and into `packages/api/src/storage/path.ts`, beside `resolveDownloadPath` — the function that already decides what a stored filepath means to a download. Its doc comment now states why the two differ: a remote strategy's query string can carry a presigned signature, so only the local paths may strip. The local storage modules keep just the call into it. Semantics are covered by the storage package's own test; the `/api` specs pin the wiring. Addresses the Codex P1 finding on 5f8dbf3. --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Zsanz3 <Zsanz3@users.noreply.github.com> Co-authored-by: Lia <lia@librechat.ai>
…16008) * Load authorized workspace repository instructions into agent context * Unify repository instruction loading across agent ingresses * Await cancellation regression assertion directly * Annotate the shared instruction loader for declaration builds * Expose repository read budget and inject the host-owned cache
* fix: Clarify Background Tool Handoffs and Validation Feedback * Preserve background handle wire compatibility * Align error logging regression assertions
Co-authored-by: Lia <lia@librechat.ai>
…t-AI#15986) * 🏛️ fix: Resolve Global Access Roles for Tenant Owner Grants * fix: Execute the base-role fallback inside the system context * fix: Resolve every tenant role lookup from one visible set * fix: Merge the visible role set before any filtering * perf: Resolve Visible Tenant Roles in One Query 🏘️ --------- Co-authored-by: lia-by-librechat[bot] <328778573+lia-by-librechat[bot]@users.noreply.github.com> Co-authored-by: Lia <lia@librechat.ai> Co-authored-by: Danny Avila <danny@librechat.ai>
…#15928) * 🔌 feat: OpenAPI spec and docs for the public Agents API * 🔧 fix: address review on Agents API OpenAPI spec and docs: - adapter: mark JSON request bodies as required - openapi.js: move gate, config read and spec serving into packages/api as an injected router factory; leave /api as wiring only - docs: compute URLs in-browser and use a relative servers url so docs work under a base path (e.g. /chat) - build: copy the generated spec into dist so the multi-stage image ships it - agents: reuse the enforced list envelope for the docs so the AgentList schema cannot drift from the runtime cursor and strictness * 🔧 fix: align Agents API OpenAPI contract with enforced runtime Second review round on the public Agents API spec. Three cases where the published contract described something the server does not do, so generated clients would fail against the live API: - Upload form: document the `purpose` field (enum: file_search, execute_code, context) that POST /agents/{id}/files actually requires, instead of the internal-only `tool_resource`; requests following the old form were rejected with 400 because `purpose` was absent. - Auth: drop the `apiKeyBearer` scheme from the management and skill endpoints. They gate on OIDC/M2M via requireAgentManagementAuth and deliberately reject the API-key fallback; the API key belongs to the out-of-scope OpenAI-compatible endpoints and returns when those are documented. - Skill update: document the 409 conflict PATCH /skills/{id} returns on a stale expectedVersion. The agent update path collapses its internal 409 to 400, so it correctly keeps no 409. * 🔧 fix: serve docs on experimental server and document the 401 Third review round on the public Agents API spec. - Experimental server: mount routes.openapi in api/server/experimental.js before its /api 404 handler. Without it, npm run backend:experimental returns 404 for /api/docs and /api/openapi.json even when openapi.enabled is true, because that server never mounted the router. - Auth failure: document the 401 that requireAgentManagementAuth returns on a missing, expired, malformed, or unbound token. Its body is { "error": "Unauthorized" }, a flatter shape than the management error schema, so add a dedicated UnauthorizedError schema and a shared 401 response to the agent and skill contracts rather than reusing the standard error schema. * 🔧 fix: document skill-update, auth-conflict, upload-limit, and watch-copy gaps * 🔧 fix: added error shapes for permission denied and internal server errors in API responses docs * fix: cover the auth-layer 500 shape and pin the account-deletion code * fix: update OpenApiRouterDeps documentation and simplify request handling in isEnabled function * 🔧 fix: gate the Swagger asset mount behind the OpenAPI toggle * 🔧 fix: accept the malformed-JSON 400 envelope in the contract * fix: accept the tenant-isolation 403 envelope in the contract * fix: add description for content property in SkillFileUpdateRequest to clarify byte limit
…ns` (LibreChat-AI#16029) Regenerates packages/api/openapi/agents.openapi.json from the code so openapi:check passes again. LibreChat-AI#16008 added repositoryInstructions to the agent schema and merged before LibreChat-AI#15928, whose committed spec was generated from a branch that predated the field, leaving dev red. Co-authored-by: Lia <lia@librechat.ai>
…16531) * 🧘 fix: Keep Prose Mounted While Activity Labels Update * test: Advance Activity Ticker in Prose Identity Regression * test: Cover Activity Prose Fade Stability in Browser --------- Co-authored-by: Lia <lia@librechat.ai>
* feat: Scope YAML Custom Endpoints by Tenant * fix: Hide Tenant Model Specs and Preserve Scoped Base Reads * fix: Finalize Tenant Endpoint Isolation and Interface Defaults * test: Complete Tenant Endpoint Schema Fixtures * fix: Invalidate Merged Configurations When Tenant Scopes Change * fix: Flatten Tenant Identity Resolution and Finalize Regression Coverage * test: Match the YAML Endpoint Name Contract * test: Use the Exact Custom Provider Name * fix: Distinguish Missing Tenant Context in Override Cache Keys --------- Co-authored-by: Lia <lia@librechat.ai>
…#16536) * fix: bind per-user MCP API keys to request destinations * fix: bind user keys after restoring OAuth destinations * fix: include OAuth revocation endpoints in user key bindings * fix: preserve MCP key readiness across equivalent requests * fix: preserve explicit MCP API key-prefixed variables * test: supply required MCP variable descriptions --------- Co-authored-by: Lia <lia@librechat.ai>
* fix: Normalize Native File Edits Before Approval * fix: Preserve Reviewed Edit Proposals on Resume --------- Co-authored-by: Lia <lia@librechat.ai>
…at-AI#16562) Co-authored-by: Lia <lia@librechat.ai>
Co-authored-by: Lia <lia@librechat.ai>
* fix: Rate Limit Authenticated 2FA Management * fix: Explain 2FA Rate Limits With Localized Retry Guidance * fix: Preserve Minimum 2FA Setup Budget
…at-AI#16572) Every send moved the thread's tail (isSubmitting, latestMessageId, latestMessageDepth, and a handleContinue rebound per turn), and the memoized row context listed all of them as dependencies, so each move handed every row a new context object and re-rendered the whole thread: three commits of ~12,000 component renders on a 246-message thread, one of them inside the Enter keypress. - Keep the row context object stable; the tail values are ref-backed getters for call-time reads, and rows receive them as props that the row comparator (isSameTailRelation) only reacts to when the row enters or leaves the tail. - Fork reads the split target through a getter at click time. - HoverButtons subscribes to a boolean isSubmitting context and a stable operations context instead of the combined messages-view context; Fork and Feedback are memoized so the per-send toolbar pass stops there. - Make handleContinue referentially stable. - Cache the Intl formatters behind getMessageTimestamp per locale and clock. - Add a submit-lag benchmark (e2e/benchmarks-tree) that reports long tasks, Enter-to-row latency, a CPU profile and per-commit render reasons.
Co-authored-by: Lia <lia@librechat.ai>
Co-authored-by: Lia <lia@librechat.ai>
* chore: bump dompurify * chore: bump agents sdk to v4.0.1
Co-authored-by: lia-by-librechat[bot] <328778573+lia-by-librechat[bot]@users.noreply.github.com>
dschenk
force-pushed
the
agent/prepare-librechat-v0.8.8-rc1-1217541308456676
branch
from
October 1, 2026 05:31
f7638b8 to
cfc473c
Compare
dschenk
marked this pull request as ready for review
October 1, 2026 08:36
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Prepares Gridonic's compatibility candidate on official v0.8.8, published 1 October and still marked prerelease at review. This replaces RC4 in the existing preparation PR; it does not authorize production rollout.
Rollout task · Configuration PR #17 · Acceptance and maintenance checklist
Why and exact delta
Gridonic currently runs patched v0.8.7. The release includes almost all those fixes. Only LC-005 still needs an implementation patch: a sessionless Streamable HTTP MCP can have working POST requests while its optional SSE GET fails. Upstream incorrectly emits a connection error for that optional-channel failure.
Upstream base:
e8f3be08623663d4ad7f7241e693c94469b63bb0.Tested head:
cfc473cba4c778ae80c7cc280822a2792f651f90.Rebased the existing branch; no merge commit or rewrite of main.
Patch retirement
LC-001/003/004/007/008 implementations are upstream. LC-002 becomes a configured-UID runtime-readability check. LC-006 is replaced by upstream Dockerfile.multi. Retire production patch-registry entries only after accepted rollout. LC-005 remains until its reproduced defect is fixed upstream.
Validation of this head
gridonic/librechat:v0.8.8-gridonic.cfc473cba, 653,697,771 bytes. Local ID:sha256:41f1f6adeb268f6b5b9b47381d6e5b0b70a723535c42151fafcb3f2392cebf4f.Remaining rollout gates
Fresh GitHub CI is running on this head: 12 passing checks, 32 queued/running at the latest snapshot. The inherited axe-linter failure is confirmed: GitHub cannot resolve
dequelabs/axe-linter-action@v2, before repository checks execute. Review the remaining checks before approval; old RC4 results do not transfer. Both PRs remain draft. No automatic reviewer requested.Production backup/identity refresh and migration need a separately approved maintenance window. Real provider/OAuth, browser, RAG and publication acceptance remain. The approved lean local-backup/fix-forward plan remains; no production backup or recovery-time claim. Paused conversations still cannot survive API restart without durable job storage; this accepted limitation is documented.
No merge, production deployment/restart, live credentials/data access or production migration. Disposable rehearsal resources were removed; only the local candidate image is retained for the planned rollout.