mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-09-01 11:33:44 +00:00
785 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
547bd8c4bf
|
🧵 feat: Persist View-Only Subagent Threads (#14957) | ||
|
|
7480e93181
|
🌐 fix: Scrollable Language Dropdown in Shared Chat Settings (#14954)
* fix: make the shared chat language dropdown scrollable and use available height The language dropdown in the shared chat settings dialog could not be scrolled with the wheel and was capped at 256px, so most of the language list was unreachable. Radix wraps the dialog overlay in RemoveScroll with its shards limited to DialogContent, so wheel events over a popover portaled to document.body were cancelled. That same portal placement also left the popover inside Radix's aria-hidden treatment, hiding the whole option list from assistive technology. Render the popover inside the dialog and let that dialog's content overflow so the popover is not clipped by it. Drop the hardcoded max-height so the popover uses the available height reported by the positioner. This also restores flipping, because the positioner can now see that the natural height overflows and place the popover above the trigger when there is more room there. Remove declarations that never took effect: max-h-[80vh] and overflow-y-auto on the popover, both shadowed by .popover-ui later in the same stylesheet, and the --anchor-max-height and --anchor-max-width custom properties, which nothing reads. Move the theme and language selectors into their own directory so the public share page no longer imports through the Nav settings tabs. * chore: drop the redundant nested winston entry from the lockfile packages/data-schemas declares winston as a peer dependency of ^3.17.0, which the root winston 3.19.0 already satisfies, so npm deduped the nested 3.17.0 copy. * refactor: give Dropdown separate wrapper, trigger and popover class props className was spread onto three elements at once: the positioning wrapper, the trigger button and the popover. A caller styling the trigger silently restyled the popover as well, and because className was merged after sizeClasses it also beat the popover's own sizing. LangfuseConnection asked for a popover the width of its anchor and got a full width one instead. className now applies to the wrapper only, triggerClassName styles the trigger and sizeClasses continues to style the popover. Call sites that relied on the old spread pass the class to the part that needs it, so the rendered result is unchanged apart from the LangfuseConnection width. Also add portalElement so a caller can render the popover into a specific container rather than document.body. * fix: align the packaged popover radius with the app stylesheet .popover-ui is declared both in the component's own stylesheet and in the app's, and the two had drifted: the packaged copy used a 1rem radius while the app used 0.7rem. The app copy wins inside LibreChat, so consumers of @librechat/client saw a different corner radius from the app itself. * fix: keep the shared chat settings dialog scrollable The dialog content was made overflow visible so the language popover would not be clipped, which meant the dialog itself could no longer scroll. If it ever grew past the viewport its content would have been unreachable. Move the scroll onto an inner region and portal the popover into the dialog content, outside that region. The popover still sits inside DialogContent, so it stays within the scroll lock shard and out of the aria-hidden subtree, while the rows above it can scroll on their own. * style: format the locales README Applies the repository Prettier style, which the file did not satisfy. Formatting only, no content changes. * chore: remove the unused DropdownNoState component The file defined a HeadlessUI based dropdown that nothing imported. It was absent from the package barrels and from the generated type declarations, so it was never part of the published API and no consumer can be relying on it. It carried the same defect the Ariakit Dropdown just had, spreading className onto the wrapper, the trigger and the popover, so deleting it is preferable to fixing code that never runs. * fix: declare the dependencies packages/client imports InputNumber imports the ValueType type from @rc-component/mini-decimal and the generated declarations re-export that import, but the package never declared it. It resolved only because npm hoists it as a transitive dependency of rc-input-number, so a consumer on a strict or nested layout would fail to resolve the type. Declare it as a peer alongside the other externals, using the same range rc-input-number asks for. The theme test requires tailwindcss directly, so add it to devDependencies rather than relying on hoisting there too. Also mark the ValueType import as a type import, matching the convention used elsewhere. * style: group the ValueType import with the package imports Type-only imports belong before local imports, as in Avatar.tsx. |
||
|
|
fe4615591b
|
📦 chore: bump @librechat/agents to v3.6.3 (#14941)
|
||
|
|
c519f26904
|
📦 chore: bump @librechat/agents to v3.6.2 (#14905)
|
||
|
|
7d850c308a
|
🧠 feat: Add Live Reasoning Labels (#14893)
* feat: add live reasoning labels * fix: Stabilize reasoning label checks * fix: Address reasoning label review findings * chore: Bump Agents SDK for reasoning labels * fix: Reset reused reasoning step evidence * fix: Reconcile cleared reasoning labels * fix: Fence reasoning label resets * fix: Reset reasoning ownership before gap labels * fix: Preserve THINK type through label reset * test: Expect run-global reasoning revision |
||
|
|
d411512a98
|
⬆️ chore: Bump @librechat/agents to v3.6.0 (#14890)
* ⬆️ chore: Bump `@librechat/agents` to v3.6.0 Bumps the pin in `api` and `packages/api` from `^3.5.1` to `^3.6.0`. The caret on `^3.5.1` cannot cross the minor, so both manifests and the lockfile need the explicit bump. v3.6.0 contains three changes over v3.5.1, all additive: - `fix: Close Subagent Child-Graph Run Steps` — subagent child graphs run via `workflow.invoke()` outside `Run.processStream`, so the terminal sweep never reached their steps. They now close on both the success and error paths, which is what makes `on_run_step_closed` reliable for subagent tool cards. - `fix: Restore Run Steps Across Process Resumes` — open run-step lifecycle state is now persisted in LangGraph checkpoints, so a step opened by one process closes correctly after a resume on another. - `feat: route code execution per agent profile` — new optional `codeSessionKey` partition for code-session ids and file refs. No breaking changes: every new field on the public type surface is optional, and the package's own dependency set is unchanged between the two versions (verified against the registry), so the lockfile diff is limited to the `@librechat/agents` entry itself. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014vLhxCFMYkCaTsoFTiAjJ5 * 🔒 chore: Sync `bun.lock` with the agents v3.6.0 bump `bun.lock` still recorded both workspace requirements and the resolved package as `@librechat/agents@3.5.1`, which no longer satisfies `^3.6.0`, so `bun install --frozen-lockfile` would reject the committed state. `bun install --lockfile-only` cannot run in this environment: bun stores no integrity for the `xlsx` URL dependency and therefore re-fetches `cdn.sheetjs.com`, which the sandbox network policy denies (403 on CONNECT). The entry was updated directly instead, which is exact here because the package's dependency graph does not move between the two versions: its `dependencies`, `peerDependencies` and `optionalPeers` at 3.6.0 are identical to 3.5.1 (checked against the registry), so only the version, the resolution id and the integrity hash change. The integrity matches the one npm resolved into `package-lock.json`, and the two existing `@librechat/agents/*` hoisting overrides stay valid because the dependency set they resolve is unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014vLhxCFMYkCaTsoFTiAjJ5 --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
edc6cf5936
|
🩹 fix: Stop Archived and Shared Chats Dialogs Crashing on Open (#14886)
* fix: stop the virtualized data table looping on render Opening Archived chats or Shared chats with 50 or more rows threw "Too many re-renders". DataTable passed an inline getItemKey to useVirtualizer, and virtual-core lists that option among the deps of its getMeasurementOptions memo, whose onChange notifies. getVirtualItems() is read during render, so every render built a new closure, notified, and dispatched a render-phase update on the component that was still rendering, until React gave up at 25 passes. It only fired past the 50-row virtualization threshold, which is why both dialogs looked fine while empty. Memoize getItemKey and estimateSize so their identity tracks their inputs. DataTable.spec had mocked @tanstack/react-virtual away, attributing the same error to jsdom, which hid this from CI. Keep that mock, since its row assertions need every row rendered, and add a spec that drives the real virtualizer and fails without the fix. Also restyle both dialogs, which is what made them look unfinished: - add the 19 keys these components pull from @librechat/client but the app locale never defined, so the empty state rendered com_ui_no_data verbatim - rename Shared links to Shared chats, matching the sibling Archived chats - transparent table with a rounded hover highlight painted on the cells, since border-radius does not apply to a table row, which needs separated borders - row height 56 to 40, dividers dropped, skeletons follow the same height - row hover uses surface-secondary-alt: plain surface-secondary is 247 against a 255 dialog in light mode and reads as nothing - row action buttons use surface-hover-alt, because surface-hover is also 227 in light and would vanish against the row highlight - drop the focus ring from the dialog containers and stop Shared chats seating focus in its search field, so neither flashes an outline on open - narrow both dialogs and let the table height follow its content * Fix compact row actions and selection count * fix: update selected count translation test to match interpolated output |
||
|
|
1d789c41a5
|
🧩 fix: Normalize MCP UI Resource Rendering (#14868)
* fix: normalize MCP UI resource rendering * fix: filter unsupported MCP UI resources * fix: preserve MCP UI marker examples * fix: handle MCP UI resource edge cases * fix: harden MCP UI marker sanitization * fix: scope MCP UI marker sanitization * fix: parse MCP UI marker contexts * fix: align MCP UI sanitizer parsing * fix: match MCP UI renderer syntax * fix: align blockquote marker spans * fix: decode MCP UI text node sources * fix: sanitize nested subagent markers * fix: bound MCP UI sanitizer traversal * fix: keep MCP UI marker mapping linear * style: sort security patch imports * fix: harden nested MCP UI sanitization * fix: mirror citation cleanup for MCP UI markers * fix: clean decoded citation markers * fix: clean assembled citation markers * fix: align MCP marker sanitization with rendering * fix: match persisted MCP marker render paths * fix: preserve highlighted citation boundaries * fix: align MCP markers across content renderers * fix: preserve citation renderer boundaries * fix: match legacy thinking trim semantics |
||
|
|
c06fbff475
|
📦 chore: bump @librechat/agents to v3.5.1 (#14830)
* 📦 chore: bump `@librechat/agents` to v3.5.0
* chore: bump agents sdk to v3.5.1
|
||
|
|
eaef87fa26
|
🚀 chore: Prepare v0.8.8-rc1 (#14394)
* 🚀 chore: Prepare v0.8.8-rc1 release * 📚 docs: Complete v0.8.8-rc1 operator references * 📚 docs: Mark stateful sessions experimental * 📚 docs: Clarify background code capability * 📚 docs: Refresh v0.8.8-rc1 operator guidance * 📚 docs: Highlight v0.8.8-rc1 features in README * 📦 chore: Bump publishable packages again * 📚 docs: Add streaming question progress * 📦 chore: Bump publishable packages again * 📚 docs: Refresh v0.8.8-rc1 release highlights * 📦 chore: Bump publishable packages again * 📚 docs: Refresh v0.8.8-rc1 release guidance * 📦 chore: Bump publishable packages again * 📚 docs: Highlight batched Agent questions * 📦 chore: Bump publishable packages again * 📦 chore: Bump publishable packages again * 📦 chore: Bump publishable packages again * 📦 chore: Refresh v0.8.8-rc1 package versions * 📦 chore: Refresh v0.8.8-rc1 package versions * 📦 chore: Refresh v0.8.8-rc1 package versions * 📄 docs: Note PowerPoint template support * 📦 chore: Refresh v0.8.8-rc1 package versions * 📄 docs: Note latest provider and file support |
||
|
|
2f0cd2eb75
|
🔌 chore: Bump the MCP SDK to 1.30.0 and Parse Content-Type Instead of Searching It (#14820)
`@modelcontextprotocol/sdk@1.30.0` is a small maintenance release on the 1.x line (upstream's active line is now the 2.0.0 scoped packages). The range was already `^1.29.0`, so only the lockfile pinned the old version; the manifests move too so the floor matches what we test against. Nothing in it is breaking. The four changed type declarations are additive — optional `maxBufferSize` on `StdioServerParameters`, an optional third constructor argument on `StdioServerTransport`, optional options on `ReadBuffer`, optional `keepAliveMs` on the server transport — and the only manifest change is `@hono/node-server` widening to `^1.19.9 || ^2.0.5`. No new dependencies. Two behavior changes are worth knowing about even though neither is an API break. `ReadBuffer` now caps a single stdio message at 10 MB (previously unbounded) and errors the transport instead of growing, which is reachable through `StdioClientTransport` if a stdio server returns a very large single result; it takes `maxBufferSize` if that ever needs raising. And Content-Type handling switched from substring search to parsed media types, client and server. Most of the release is Streamable HTTP server hardening we do not run — a 15s SSE keep-alive, `X-Accel-Buffering: no` on SSE responses, guards so a stale stream's cancel cannot tear down its successor, and `_closed` checks so a transport closing mid-request stops registering streams into swept maps. None of it changes how we behave as a client. In particular it does not address the stale-stream 409 in #14816: that keep-alive runs in whichever server we connect to, not here. The same substring-vs-parse mistake the SDK corrected exists in our streamable HTTP response guard, which classified a response as SSE with `contentType.includes('text/event-stream')`. A `Content-Type` naming the SSE type in a parameter — `text/plain; boundary=text/event-stream` — is not an event stream, but matched. The guard then took `canEmitFallbackSSEError`, so an oversized body was answered with a synthetic SSE error frame the caller reads as a well-formed response body, rather than the throw a non-SSE response gets. The check now compares the parsed media type, via a `mediaTypeEssence` helper added to the header utils where `mergeHeaders` already lives. Verified against 1.30.0 rather than assuming: the package was staged into the worktree's own `node_modules` so it shadowed the shared install, and `packages/api` `src/mcp` ran green on it — same four pre-existing red suites as on 1.29.0 (`MCPReinitRecovery` plus three Redis `cache_integration` suites that need a live Redis), no new failures. |
||
|
|
24d111fde9
|
⚡ feat: Add Gemini 3.7 Flash Support (#14818)
* ⚡ feat: Add Gemini 3.7 Flash Support Adds first-class support for Google's Gemini 3.7 Flash (`gemini-3.7-flash`) for both the Gemini API (AI Studio) and Google Cloud Gemini Enterprise Agent Platform, following the Gemini 3.6 Flash integration (#14369). - Context window (1,048,576) in googleModels; API + cache pricing in tx.ts. - Model dropdown (config.ts) and GOOGLE_MODELS examples for both integrations. - Register the model in the Flash-family handler so it inherits the existing strip of deprecated sampling params (temperature/topP/topK), rejected penalty params, and thinkingBudget, and defaults to `medium` thinking. - Generalize that handler's enumerated table from a [id, level] tuple to a rule object, so a model can also declare thinking levels it rejects. Gemini 3.7 Flash errors on `minimal` (which the Google endpoint offers in its thinkingLevel slider), so an explicit `minimal` is substituted with the nearest supported level, `low`. Explicit low/medium/high pass through unchanged. - Apply Google's introductory pricing ($0.75 in / $3.75 out / $0.075 cached, per 1M) to Gemini 3.7 Flash and correct Gemini 3.6 Flash to the same rates. Both revert to $1.50 / $7.50 / $0.15 on 2027-01-01; noted at both call sites. Resolves #14802 Ref: https://ai.google.dev/gemini-api/docs/models/gemini-3.7-flash Ref: https://ai.google.dev/gemini-api/docs/pricing * 📝 docs: Match the House Style for Promotional Rate Comments Align the Gemini 3.6/3.7 Flash introductory-pricing notes with the existing Sonnet 5 convention in the same file: one comment per group, naming the models and the exact values to restore, so the manual follow-up is unambiguous. No rate changes. * ⬆️ chore: Bump `@librechat/agents` to 3.4.7 for Gemini 3.7 Flash Prefill Unblocks this PR. `NO_PREFILL_GEMINI_MODELS` is model-enumerated in the agents SDK, so 3.4.6 does not know `gemini-3.7-flash` forbids a trailing `model`-role turn — editing an assistant reply and resubmitting would reach Google as a prefill and return HTTP 400 on a model this PR adds to the default list. 3.4.7 (danny-avila/agents#412, released via #413) adds it. Verified the published tarball: `3.4.6...3.4.7` touches only `dist/{cjs,esm}/llm/google/utils/common.*` — the prefill array and its comment. `dist/types` is byte-identical, so there is no API surface change. Raises the declared range in both workspaces alongside the lock. `^3.4.6` already permitted 3.4.7, but the fix is required rather than merely compatible, so the floor should say so. |
||
|
|
298a3d9ee9
|
📦 chore: Update @librechat/agents to v3.4.6 (#14781)
|
||
|
|
7347cfc195
|
🍡 feat: Batched User Questions With A Single Bounded Answer Form (#14737)
* feat: support batched user questions * test: align batched question fixtures * fix: harden batched question lifecycle * test: submit batched HITL answers in e2e * fix: address batched question review findings * fix: preserve invoke return typing |
||
|
|
f7d9f36922
|
🎨 feat: Refine Client Colors and Sharing Dialogs (#14734)
* Refine client colors and settings interactions * Align dark dialog theme tokens * Preserve custom hover themes and badge contrast * feat: redesign sharing dialogs * fix: preserve theme compatibility and role menus * fix: address review findings and static checks |
||
|
|
a3cec67e08
|
🪆 feat: Add Parent Activity Phase Summaries (#14721)
* feat: add activity phase summaries * fix: preserve activity phase lifecycle semantics * fix: satisfy activity phase type checks * fix: simplify activity phase status mapping * style: format activity phase changes * fix: rebase activity phase bounds after shaping * fix: link activity phase trace ancestry * fix: reconcile activity phase bounds * style: format activity phase reconciliation test * style: align activity phase assertion * fix: retain reasoning across commentary * fix: preserve activity phase boundary state * fix: detect renderable phase children * test: type parallel phase assertion * chore: bump agents SDK for activity phases * fix: retain unphased lane reasoning * fix: preserve tool group expansion across phases * style: format phase expansion regression * fix: preserve phase interaction state efficiently * perf: skip sparse phase segment holes * perf: partition phase segments with offsets * fix: preserve phase boundaries and cursor state * test: align activity phase regressions with CI * test: keep phase context mock hoist-safe |
||
|
|
87a8b9aa12
|
📡 feat: Route Web Search and Scrape Egress through the SSRF-safe Agent (#14606)
* feat(web-search): route outbound search and scrape requests through the SSRF-safe agent Build the SSRF-safe agents at the web-search tool-assembly site and pass them into the search tool config so outbound search and scrape connections are validated at connect time against their resolved IP, on every hop including redirects, consistent with the other outbound clients. Add allowedAddresses to webSearchSchema, reusing allowedAddressesSchema, so self-hosters can permit a deliberately-private search or scrape endpoint (for example a private SearXNG instance). The field is resolved directly from the webSearch config at the createSearchTool call site, not through loadWebSearchAuth, because it is config and not an auth credential. webSearchSchema is flat (providers are chosen by enums, not by counting keys), so the field is inert with respect to provider selection. Document the field and its operator warning in librechat.example.yaml, and assert the wiring in handleTools.test.js: the SSRF-safe agents are threaded into the search tool config, allowedAddresses is passed through when set, and omitting it still threads the agents with no exemptions. TODO awaits @librechat/agents release with the httpAgent hook: this consumes optional httpAgent/httpsAgent fields on the search-tool config that are not yet in a published @librechat/agents. package.json is intentionally left at the current version; bump it to the release that ships the hook before this lands. Validated locally against a revendored @librechat/agents build, not a published release. * 🛡️ fix: Apply allowedAddresses to the Web Search SSRF Preflight The connect-time SSRF agent already honors webSearch.allowedAddresses, but loadWebSearchAuth ran the isSSRFUrl preflight without it, so an admin-permitted private search or scrape URL was stripped before the agent could ever use it. Thread allowedAddresses and the URL's effective port through isSSRFTarget and resolveHostnameSSRF so the exemption is consistent across both SSRF layers. * 🛡️ fix: Validate Web Search Destinations and Defer to Configured Proxies Handing agents to createSearchTool covered only the connect-time DNS lookup, which Node skips for IP-literal hosts, and a configured proxy connects on our behalf without running that check. A literal private target such as http://169.254.169.254 could therefore reach the network. Route every resolved web-search destination through the existing applySSRFSafeAgentIfDirect contract so a blocked literal target throws before any request is made, and withhold the agents when a proxy owns egress, since one agent pair is shared by every provider and a direct-connect agent on a proxied connection would break the request while asserting protection the proxy's network context cannot provide. * 🛡️ fix: Keep Web Search SSRF Agents Under a Proxy and Restore Pooling Withholding the agents whenever a proxy was configured removed protection from every direct and NO_PROXY destination in exchange for preventing a failure that cannot occur: for an https target Axios substitutes its own CONNECT tunnel, so the injected agent is never used for the proxy connection. Only a plaintext http target keeps our agent and repoints it at the proxy, and only a proxy whose hostname resolves private then trips the connect-time check. Always pass the agents and exempt the proxy endpoint instead, deriving host:port from the same PROXY, HTTP_PROXY, and HTTPS_PROXY resolution the rest of LibreChat uses so the proxy hop stays reachable while destinations remain guarded. Axios already applies NO_PROXY per request, so bypassed routes keep enforcement with no extra logic. Drop the load-time destination validation. It duplicated the isSSRFTarget preflight for user-provided URLs, rejected admin values that were previously legal, and threw from inside loadTools, where both loader wrappers swallow the error and drop every tool for the turn rather than degrading web search alone. Build the agents with keepAlive and cache them per exemption list. A bare http.Agent does not pool, so the previous code replaced the pooled global agents for every search, scrape, and rerank call and allocated a fresh pair per turn. * 🛡️ fix: Reject IP-Literal Private Targets on Web Search Connections Node resolves nothing for a literal host, so the connect-time lookup never saw one: a destination or a redirect target given as http://169.254.169.254 reached the network. Redirect hops pass through the same createConnection, so checking the literal there covers both cases and removes the need for a maxRedirects control that createSearchTool cannot accept. Gate it behind blockLiteralHosts so only web search opts in. A caller that reaches a proxy or a deliberate private service by literal address must exempt it first, and the merged consumers of createSSRFSafeAgents have no such exemption, so enabling this everywhere would break configurations that work today. * 🛡️ fix: Keep IPv6 Brackets on Derived Proxy Exemptions The exemption parser accepts an IPv6 entry only as [ipv6]:port, so stripping the brackets produced fd00::1:3128, which carries three colons and is dropped as malformed. An IPv6 proxy therefore stayed unexempted and the connect-time check rejected it, failing every web-search request routed through it. Use the URL hostname as parsed, which already carries the brackets. * 🛡️ fix: Exempt Proxies Configured Through ALL_PROXY Axios resolves a proxy through proxy-from-env, which falls back to all_proxy in either case after <protocol>_proxy, so ALL_PROXY on its own is enough to route a request through a proxy. Exemptions were derived from PROXY, HTTP_PROXY, and HTTPS_PROXY only, leaving such a proxy unexempted and rejected with ESSRF. Derive the exemptions from the full set of variables that can put a proxy in front of these requests instead. The installed proxy-from-env 2.1.0 reads no npm_config variables, so those are deliberately not included. * 🛡️ fix: Drop the Unearned PROXY Exemption and Harden the Web Search Guard Nothing on this path consumes PROXY: Axios resolves proxies through proxy-from-env, which reads only <protocol>_proxy and all_proxy, and web search never calls applyAxiosProxyConfig. Exempting it therefore granted a bypass rather than preserving a working route, and a user-settable search URL that redirects to that address reached it and returned the body. Remove PROXY and proxy, and skip a socks endpoint for the same reason, since Axios cannot proxy through one. Tolerate a non-array allowedAddresses instead of spreading it, which threw out of loadTools and dropped every tool for the turn. The YAML path is schema-validated but the admin override path merges without parsing, so the value is reachable. Separate cache keys with NUL rather than a newline, so an entry containing a newline cannot collide with two separate entries, and bound the cache. Give the agents the idle timeout the global agents carry, which keepAlive alone did not restore. Reject a unix socket, which carries no host to validate. Also treat fec0::/10 site-local as private, matching the fe80::/10 handling beside it. Exercise the real resolver in handleTools.test.js rather than mocking it, so the wiring test now fails if the agents it threads do not actually block a private target. * 🛡️ fix: Derive Proxy Exemptions Through Axios's Own Resolver Unioning every populated proxy variable exempted addresses that never carry a request. proxy-from-env picks a protocol-specific variable before all_proxy and lowercase before uppercase, so an ignored value became a trusted host:port that a redirect onto a direct route could reach. It also normalizes a scheme-less value such as proxy.internal:3128 to an http URL, where parsing the raw string yielded an empty hostname and no exemption at all, breaking the proxy hop. Resolve through getProxyForUrl, the entry point Axios itself calls, so precedence, scheme normalization, and NO_PROXY match exactly and cannot drift. NO_PROXY covering everything now yields no exemption, since nothing is proxied. Declared locally rather than adding a types package, alongside the existing declaration in the same directory. Also revert the fec0::/10 site-local change. domain.spec asserts that boundary deliberately to prove the fe80::/10 mask does not over-reach, and the shared address schema still classifies fec0 as public, so a runtime block there would leave operators unable to configure the exemption. It belongs with those two together, not in this PR. * 🛡️ fix: Resolve Proxy Exemptions Against the Real Destinations Resolving against placeholder probe hosts applied destination-specific NO_PROXY rules to a host nobody dials. With NO_PROXY matching the probe domain but not a real provider, no exemption was derived even though Axios still proxied the actual request, so the agent rejected the private proxy hop with ESSRF. Resolve per configured destination instead, passing the values loadWebSearchAuth already resolved. Only plaintext http destinations are considered, since for an https destination Axios substitutes its own CONNECT tunnel and never uses the injected agent for the proxy connection, which is also why provider defaults need no exemption: every one of them is https. * 🛡️ fix: Accept Embedded-IPv4 IPv6 Forms in the Address Exemption Schema The runtime guard blocks 6to4, NAT64, and Teredo addresses whose embedded IPv4 is private, but the schema's local copy recognized only ULA, link-local, and the dotted IPv4-mapped form, so an entry such as [64:ff9b::a00:1]:8080 was dropped as a public literal. An operator reaching a private endpoint that way could not configure the exemption at all. Mirror hasPrivateEmbeddedIPv4 in the schema helper, which the surrounding comment already asks to keep in sync. Public embedded addresses stay rejected, since an exemption there has no defensive purpose. |
||
|
|
7cf4c3f73f
|
🧪 test(e2e): add Bombadil property exploration (#14462)
* test(e2e): add Bombadil property exploration * fix(e2e): address Bombadil review feedback |
||
|
|
1bd4455c2d
|
🧭 fix: Make MCP Catalog Redis Cluster-Safe (#14717)
* fix: make MCP catalog Redis startup cluster-safe * fix: stabilize Redis readiness gate * fix: type Redis readiness export * style: apply canonical import order |
||
|
|
ef38f362ec
|
📦 chore: bump @librechat/agents to v3.4.2 and npm audit (#14702)
* 📦 chore: bump `@librechat/agents` to version 3.4.2 * 📦 chore: bump `mermaid` to version 11.16.1 and update related dependencies * 📦 chore: bump `js-yaml` to version 4.3.1 in package-lock and data-provider * 📦 chore: bump `nanoid` to version 3.3.18 in package.json and package-lock.json across multiple packages * 🔧 fix: Remove stray `api/tsconfig.json` breaking e2e `~` alias An empty `api/tsconfig.json` was accidentally committed with the agents bump. Playwright's require hook resolves path aliases from the nearest path-config, checking `tsconfig.json` before `jsconfig.json` in each folder, so the empty file shadowed `api/jsconfig.json` — the only place `"~/*": ["./*"]` is defined. Every e2e spec that calls `cleanupUser` then failed on `Cannot find module '~/cache/getLogStores'` from `api/models/index.js`. - delete the stray file and gitignore it so tooling can't re-commit it - register `module-alias` in `cleanupUser` so backend requires resolve regardless of which path-config Playwright happens to find * 📦 chore: bump `@librechat/agents` to version 3.4.3 in package.json and package-lock.json |
||
|
|
45cc53c40b
|
🛰️ chore: bump @librechat/agents to v3.4.0, cover streamed subagent results e2e (#14647)
* test: cover streamed subagent results end to end * test: assert real e2e conversation id * test: harden streamed subagent e2e * test: stop incompatible subagent fixtures * chore: update @librechat/agents to version 3.4.0 in package.json and package-lock.json |
||
|
|
7775f25b0d
|
🪢 fix: disable central fanout media uploads for langfuse (#14642)
* fix(langfuse): disable central fanout media uploads * test(langfuse): cover fanout media policy in run config * chore(deps): bump agents for Langfuse media policy * chore(deps): bump agents to 3.3.13 * fix(langfuse): gate central fanout media uploads |
||
|
|
3f0a1ec8d9
|
🛡️ fix: Run message-filter PII patterns on a linear-time regex engine (ReDoS) (#14554)
* 🛡️ fix: Run message-filter PII patterns on a linear-time regex engine The messageFilter.pii middleware compiled admin-configured customPatterns with the native RegExp engine and ran them synchronously against every message on the shared event loop, so a catastrophic-backtracking pattern such as (a+)+$ could stall the entire process (native RegExp takes tens of seconds at roughly 32 characters) and take the instance down for every user. Compile these patterns with RE2JS, a linear-time RE2 port with no native addon, so catastrophic backtracking is impossible regardless of the pattern rather than something the code tries to detect. Patterns using features RE2 does not support, such as backreferences, fail to compile and are dropped and logged exactly as an invalid pattern already is. The filter only tests for a match, so this is a drop-in engine swap with no behavior change for valid patterns. * 🛡️ fix: Reject RE2-incompatible messageFilter patterns at config load The customPatterns regex was validated with native RegExp at config load, but the runtime now compiles it with a linear-time engine (RE2) that does not support backreferences or lookaround. Such a pattern passed validation, then failed to compile and was silently dropped at request time, quietly removing PII protection after upgrade. Reject backreferences and lookaround during config validation with an explicit message, and document RE2 syntax in the example config instead of "JavaScript-flavor". The runtime engine remains the authoritative boundary and still drops-and-logs anything this load-time check misses. * 🧹 test: Use direct MessageFilterPiiConfig annotations in the PII specs The added ReDoS cases satisfy the exported MessageFilterPiiConfig type directly, so the `as unknown as` assertions were unnecessary. Annotate the config objects directly, matching the repo's type-safety guidance. * 🧹 fix: Reject named backreferences in messageFilter patterns at config load Extend the config-load check to also reject named backreferences (\k<name>), which are valid JavaScript regex but unsupported by the linear-time runtime engine, so they surface at load rather than being dropped at request time. Together with the existing numeric-backreference and lookaround checks this covers the RE2-incompatible construct set; the runtime engine remains authoritative. * 🛡️ fix: Preserve Unicode whitespace matching in messageFilter starter patterns RE2's \s is ASCII-only, so after the engine swap the built-in api-key and Bearer starters no longer matched a secret separated by non-ASCII whitespace (e.g. a non-breaking space), which native RegExp did match. Broaden the whitespace classes to [\s\p{Zs}] so those patterns keep their original coverage, and add a regression test for a non-breaking-space separator. * 🛡️ fix: Validate messageFilter patterns with the RE2 engine at config load Replace the syntax blacklist (numeric/named backreferences, lookaround) with authoritative validation: config load now compiles each custom pattern with the same linear-time engine the runtime uses, so any RE2-incompatible construct (including control escapes like \cA) is rejected at load with a clear error instead of being silently dropped at request time. The validator is swappable and defaults to native RegExp so browser builds add no engine; the server wires the RE2-backed check at startup via configureMessageFilterRegexValidator in both entry points. * 🛡️ fix: Match the full whitespace set in messageFilter starter patterns RE2's `\s` omits the vertical tab and `\p{Zs}` omits U+2028, U+2029, and U+FEFF, so a separator built from one of those characters slipped past the `api-key` and `Bearer` starter patterns and reached the model. Broaden the starter whitespace class to the full JavaScript whitespace set so those separators are covered again. * fix: fail closed when messageFilter.pii compiles to zero patterns DB and admin config overrides bypass the RE2 schema validation (it only runs at YAML load), so an override whose only pattern is RE2-incompatible was dropped at compile time, left zero patterns, and let the request through. compile() now returns a failClosed flag when a config declared patterns but every one failed to compile; the middleware returns 400 and findPiiMatchInMessages returns a distinct misconfigured match that the OpenAI and Responses controllers surface with an admin-facing message. * 🛡️ fix: Fail closed when any messageFilter.pii custom pattern drops compile() previously set failClosed only when every pattern dropped (patterns.length === 0 && dropped > 0). With the default starters present, a single RE2-incompatible custom override incremented dropped but left patterns.length > 0, so the filter silently enforced only the surviving subset and text matching only the dropped rule passed. failClosed now keys off dropped > 0, so any dropped custom pattern blocks with the misconfigured 400. YAML patterns are RE2-validated at load, so dropped stays 0 for valid configs and only unvalidated DB or admin overrides can trip it. Reframed the two keeps-others-active specs to assert fail-closed and added a default-starters partial-drop regression. * 🧹 fix: Correct the misconfigured JSDoc and drop redundant casts in the PII specs The misconfigured flag now means any configured custom pattern failed to compile, not that every pattern failed, so its JSDoc on PiiMatch is updated to match. The partial-drop regressions now use direct MessageFilterPiiConfig annotations instead of as-unknown-as casts, keeping the specs type-checked, consistent with the rest of the suite. |
||
|
|
1bbe7dfe83
|
📦 chore: npm audit (#14640)
* chore: Update undici dependency to version 8.10.0 * chore: npm audit fix * chore: downgrade undici dependency to version 7.29.0 |
||
|
|
ccf4301093
|
🚦 feat: Configurable Circuit Breakers for Runaway Streamed Tool Args (#14613)
* 🚦 feat: Configurable Circuit Breakers for Runaway Streamed Tool Args * docs: forewarn create_file about the streamed tool-argument limit The breaker failing a near-limit write should not be the model's first exposure to the bound. Both create_file variants now state the default 64 KB per-call limit and the incremental pattern (create the first section, extend with edit_file) in the tool description and the content parameter description. * fix: keep skill create_file description under the provider advisory cap The limit-guidance paragraph pushed the skill-aware description to 1169 chars, past the 1024-char advisory bound where providers may truncate. The skill variant now carries the guidance only in its content parameter description, which sits closest to the generated payload and is not at truncation risk; the shorter code-sandbox variant keeps the full paragraph. * 🚦 feat: per-tool streamed-arg limits with a create_file default Thirty days of production data show create_file is the only tool class with legitimate near-limit arguments (p99 80.6 KiB; every other tool p99 under 10 KiB). Rather than loosening the global 64 KiB cap for all tools, the yaml gains maxToolCallArgBytesByTool (per-tool overrides, keyed by model-facing tool name, 0 disables that tool's guard) and LibreChat ships { create_file: 131072 } by default; yaml entries merge over and can replace it. Pairs with maxToolCallArgBytesByTool support in the agents SDK and stays inert until the dependency bump. * test: pass per-tool spec configs as plain Partial literals The as-TAgentsEndpoint casts fail TS2352 for object-valued fields: comparability does not grant nested literals the implicit index signature that plain assignability does, so casts carrying maxToolCallArgBytesByTool never sufficiently overlap. The mapper already accepts Partial<TAgentsEndpoint>, so the new cases pass uncast literals instead. * chore(deps): bump @librechat/agents to 3.3.12 |
||
|
|
96499f0765
|
🔒 chore: Upgrade react-router-dom to v7.18.2 (security) (#14582)
* 📦 chore: Upgrade react-router-dom to v7.18.2 (security) Fixes GHSA-wrjc-x8rr-h8h6 (open redirect via backslash in Link/useNavigate, CVE-2025-68470 bypass) and GHSA-337j-9hxr-rhxg (deserializeErrors constructor injection). Neither has a 6.x patch; v7's react-router-dom is a shim re-exporting react-router, so all existing imports work unchanged. - vite manualChunks: match react-router so the routing chunk still captures the router (v7 moves all code out of the react-router-dom package) - jest: add test/polyfills.js (TextEncoder/TextDecoder + minimal Request); v7's CJS bundle constructs TextEncoder at module scope and builds a Request per navigation, neither exists in jsdom - auth specs: v7 types drop the synthetic default export; use a namespace import and mark the mock factory __esModule so the useOutletContext spy patches the object components actually read - isSafeRedirect: reject backslashes as defense in depth for the same open-redirect class the router patch addresses * 📦 chore: Regenerate stale bun.lock bun.lock predated months of package.json drift and still pinned react-router 6.30.3. Regenerated with bun install --lockfile-only so bun installs match current manifests, including react-router 7.18.2. * 🗂️ fix: Commit project-chip URL updates synchronously under router v7 v7 wraps router state updates in React.startTransition unconditionally, so the chip's paired updates tear: the conversation draft (Recoil) commits synchronously while the ?projectId removal defers. ChatRoute's draftProjectMismatch re-init sees draft != URL in that window and restores the removed project. The flushSync navigate option commits both in one pass, matching v6 ordering. Caught by the projects e2e specs. * 🧹 chore: Drop unused banner-query spy variable in Registration spec Pre-existing warning, but the changed-files eslint gate runs with --max-warnings=0 so it blocks this PR. The spy call stays; only the never-read variable goes. |
||
|
|
b253b623fe
|
📦 chore: update sanitize-html to latest (#14573)
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
* 📦 chore: update `sanitize-html` to latest
* chore: add additional modules to esModules for Jest configuration
|
||
|
|
b9ca391b84
|
📦 chore: bump @librechat/agents to v3.3.11 (#14562)
|
||
|
|
60ca751a7f
|
🧠 fix: Preserve Deferred Tool Schemas Across HITL Resume (#14552)
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: Preserve deferred tool schemas across HITL resume * 🧪 test: Harden deferred tool resume regression * 📦 chore: bump @librechat/agents to v3.3.10 |
||
|
|
f5e8feba80
|
📦 chore: bump @librechat/agents to v3.3.9 (#14548)
|
||
|
|
e8a943c7e8
|
📦 chore: bump @librechat/agents to v3.3.8 (#14525)
Activates activity-label continuity end to end. The host side landed with the activity-groups feature (#14391) — the per-run accumulator that reads committed headers at request-build time, the `previousLabels` payload field, resume seeding, and the bridge passthrough — but the SDK had no field to receive them, so the traced generation path ignored the context and only the direct fallback rendered it. v3.3.8 carries danny-avila/agents#356, which adds `previousLabels` to `RunActivityLabelOptions` and renders it as the label prompt's first section (capped at 3, each entry whitespace-collapsed and clipped at 200 chars so one malformed header cannot forge prompt sections or inflate every later request in the run). Effect: consecutive same-activity batches now extend the run's story instead of restating a line already on screen, and setup batches stop being labeled with conclusions their tools had not yet established. Also included between v3.3.7 and v3.3.8: danny-avila/agents#354, which anchors summary coverage to a source message id. Lockfile carries no transitive churn — 3.3.8's dependency tree is identical to 3.3.7's. Verified against the installed package: 73 activityLabels tests and 218 api agents-controller tests pass, `tsc --noEmit` clean on packages/api, and the published build renders the capped, sanitized header section (oversized labels clipped, embedded newlines flattened to inert text). |
||
|
|
cc813f430e
|
🎯 feat: Tool Intent Label Capability (tool_intents) (#14499)
* 🎯 feat: Tool Intent Label Capability (tool_intents) Adds the fourth member of the per-tool capability family (defer_loading, allowed_callers, run_in_background): an admin capability AgentCapabilities.tool_intents plus a per-tool tool_options[name].describe_intent flag. Opted-in tools get an optional intent string injected as the FIRST property of their schema — one model-authored sentence per call, streamed to the client as the call's live status label (args already reach the client verbatim, so no new event plumbing). Native host tools (web_search, create_file/edit_file, set_memory/delete_memory, ask_user_question) default on while the capability is enabled; explicit false opts out. SDK-native intent schemas (@librechat/agents coding suite) are recognized and left alone. - packages/api/src/agents/intent.ts: structural sibling of background.ts — first-key non-mutating injection with registry parity (covers deferred/tool_search discovery), eligibility and PTC-only skips, arg read/strip helpers, self-spawn strip for defs and registry, ephemeral/model-spec synthesis with a tool_options merge so the background and intent toggles compose. - handlers.ts: intent runs BEFORE background injection so the label stays the first streamed key when a tool carries both (pinned by test); the arg is stripped before invocation unless the tool's own schema declares it, on both the foreground and background-dispatch paths; PTC target schemas are sanitized like background's. - Capability plumbing through all four routes (endpoint initialize, openai + responses controllers, the exported OpenAI-compatible service) plus handoff discovery and added-convo agents, and the intentToolNames execution channel via configurable. - describe_intent on toolOptionsSchema (all three written-out Zod annotations), ToolOptions, TEphemeralAgent, TModelSpec (+ zod), and data-schemas doc comments (tool_options is Mixed — no migration). - intent.spec.ts: 28 tests cloned from background.spec.ts structure, including the intent+background key-order composition. * 🧯 fix: Codex Review — Opt-Out Strips SDK-Native Intent, Skip mcp_all Placeholders - An explicit describe_intent: false now REMOVES an SDK-native intent property from the definition and registry entry, so the per-tool opt-out actually disables the arg's token cost for tools like web_search that carry the schema natively (SDK bodies tolerate its absence). Previously the early return left the property in place. - synthesizeIntentToolOptions skips lazily-expanded mcp_all placeholders instead of recording options under names that applyIntentLabels' exact-name matching can never match, and documents the limitation (parity with synthesizeBackgroundToolOptions). The P1 about the client not rendering the label is the documented slicing: the UI streaming-label PR follows once #14391's ToolCallGroup changes merge — args already reach the client, so that slice is purely rendering. * 🧯 fix: Codex Re-Review — Label Marker Guard, Capability Kill Switch, Late Defs, Service Threading - removeIntentParam is now marker-guarded (the label contract's opening instruction discriminates it), so an MCP/action tool's own business `intent` parameter is never stripped by an opt-out or the disabled path — previously an explicit false could remove a real, possibly required argument. - New sanitizeIntentLabels pass runs AFTER every registration step (the skill catalog appends its SDK definition post-injection): with tool_intents disabled it strips SDK-native intent labels from all definitions and registry entries, making the capability a real kill switch over their token cost; with it enabled it enforces explicit per-tool opt-outs on late-registered definitions. - ask_user_question removed from the native default-on set: its graph tool is rebuilt in run.ts from its own Zod schema (also the HITL card's wire shape), so definition-level injection never reached the model. Its intent support lands with the HITL slice, which threads the label into the interrupt payload deliberately. - The exported OpenAI-compatible service now threads intentToolNames into the run configurable, so the executor's PTC path can strip host-injected intent schemas on that route like the in-repo controllers do. * 🧯 fix: Codex Round 2 — Post-Skill Injection, PTC Native Strip, Service Boundary, Honest Docs - Intent injection now runs LAST in initializeAgent, after the skill catalog — which both appends its own definition and REPLACES upgraded ones (skill-aware read_file), clobbering an earlier injection while intentToolNames still listed the tool. Injection PREPENDS while background APPENDS, so intent stays the first schema property under the new ordering (pinned by a reverse-order composition test). - The PTC target-schema strip is now marker-guarded strip-ALL: SDK- native intent labels (which are deliberately never in intentToolNames) are removed from sandbox-advertised schemas alongside host-injected ones; business intent params survive. - toolIntentsAvailable on the exported service documents the loader boundary: a custom LoadToolsFn returning only structured instances bypasses definition/registry injection and sanitize by construction. - librechat.example.yaml describes tool_intents as backend groundwork with UI rendering in an upcoming release rather than promising a live label today. * 📦 chore: bump `@librechat/agents` to v3.3.6 Brings in the SDK half of tool intent labels (danny-avila/agents#347, #349): intent-first schemas on the coding suite across all three engines, plus web_search / subagent / skill / tool_search, and the outcome / outcome_patch result channel. Activates three host paths that were inert while no SDK tool shipped an `intent` property — verified against the real 3.3.6 schemas: - capability OFF now strips SDK-native labels (a real admin kill switch) - explicit `describe_intent: false` removes them per tool - host injection stays idempotent against an SDK schema, keeping `intent` first and never double-injecting * 🔬 test: Real-Provider Verification for Tool Intent Labels Adds the live check the unit tests structurally cannot perform: whether a real model actually authors the injected arg, places it FIRST, and gives sibling calls to one tool distinct labels. Reuses the existing real-provider harness (in-memory Mongo, seeded user, credential neutralizer) and the existing stdio MCP fixture as a genuine tool, so no external service is involved. - e2e/config/librechat.real.yaml: adds the e2e-memory MCP server and the tool_intents capability, giving the real model something to call. The sibling spec asserts only relative token growth, so the extra schemas do not perturb it. - e2e/playwright.config.real.ts: optional Langfuse passthrough. The LANGFUSE_* keys match the credential-neutralizer pattern and were being blanked before the server booted; they are preserved explicitly, read from the invoking environment only, and never written to the generated config. - e2e/specs/real/tool-intents.spec.ts: two facts stored in one turn, both through the same tool, asserting intent is the first key of each call and that the two labels differ. Args are read from persistence rather than the DOM deliberately — no UI renders the label yet, and persistence is what a reloaded conversation and the trace both read. First run against claude-haiku-4-5 produced 'Recording the location of the OAuth callback router' and 'Recording the location of the MCP connection pool configuration' — distinct, first-position, no tool name. Also updates tool-intent-spec.md: records the 3.3.7 removal of the tense verb map with the evidence that motivated it, the trimmed description and the marker's role as an API, and a new mandatory requirement that client-side label rendering be gated on a server-sent signal rather than the presence of an intent key (a tool's own business 'intent' parameter would otherwise render as a status label). * 📦 chore: bump `@librechat/agents` to v3.3.7 and dedupe the intent contract Picks up danny-avila/agents#353: the tense verb map is gone (a bare intent now displays unchanged, with completion carried by UI state), the model-facing description is trimmed 502 → 289 chars, and both the marker and the description are exported. Stops redeclaring the SDK contract here: - INTENT_LABEL_MARKER is imported instead of duplicated as a string literal. Every removal path in this module keys on it, and a local copy that drifted from the SDK's would make them all stop recognizing SDK-native labels — failing OPEN, with labels left in schemas and per-tool opt-outs silently inert. - INTENT_DESCRIPTION is imported too, so host-injected tools and SDK-native tools present the model with one identical instruction. Keeping the old local copy would also have meant host-injected tools still paying ~126 tokens per schema while SDK tools paid ~72. Verified live against real Anthropic after the trim: two sibling calls to one MCP tool produced 'Storing the OAuth callback router file location' and 'Storing the MCP connection pool configuration file location' — first-position and distinct, so the shorter description holds compliance. |
||
|
|
3edb497502
|
📦 chore: bump @librechat/agents to ^3.3.5 (#14506) | ||
|
|
6dae785e31
|
🌯 chore: Retire Rollup-Era devDependencies After tsdown Migration (#14496)
Removes 26 of the 32 Rollup-era devDependency declarations left behind when these packages moved to tsdown, plus two stale config references and an override that went inert in #14483. - Drop all 8 from `packages/api`, all 8 from `packages/client` (including `concat-with-sourcemaps`), and all 10 from `packages/data-schemas`. None of their tsdown configs import anything from rollup, and none has a rollup script or config file. - Keep all 6 in `packages/data-provider`. Five of them back the `rollup:api` script, which the "Circular dependency checks" CI job runs to surface rollup's circular-dependency warnings, and `@rollup/plugin-replace` is imported directly by that package's tsdown config. - Drop `rollup.config.js` (exists nowhere in the repo) and `server-rollup.config.js` (real, but never read by the `build` task, so listing it only caused spurious cache invalidation) from `turbo.json`. - Drop the `**/rollup.config.js` glob from `eslint.config.mjs`. It matches nothing, and never matched `server-rollup.config.js`. - Drop the root `svgo` override, dead since #14483 removed `rollup-plugin-postcss`, the only thing that pulled svgo into the tree. |
||
|
|
9c95bf445f
|
🍂 chore: Prune Deprecated Packages From the Dependency Tree (#14483)
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
Removes three of the eight deprecation warnings emitted on `npm install`.
- Drop `@types/winston` from `packages/api` and `packages/data-provider`.
The published tarball ships no type declarations at all, so `winston`'s own
types were already being used. Declare `winston` as a devDependency instead,
since both packages `import type { Logger } from 'winston'` and were relying
on hoisting to resolve it.
- Drop `rollup-plugin-postcss` from `packages/client`. It is unreferenced since
the package moved to tsdown, and pulled in `cssnano -> postcss-svgo -> svgo@2`,
which is the only consumer of the deprecated `stable`.
- Override `test-exclude` to ^8 so `babel-plugin-istanbul` stops resolving
`test-exclude@6`, which pins the deprecated `glob@7`.
The remaining five warnings (`ldapjs`, `whatwg-encoding`, `node-domexception`,
and workbox-build's `glob`/`source-map`) are transitive with no non-deprecated
version available upstream.
|
||
|
|
f7bc50ae5b
|
📦 chore: bump @librechat/agents to v3.3.4 (#14482)
Some checks failed
Publish `@librechat/data-schemas` to NPM / pack (push) Has been cancelled
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
Publish `@librechat/data-schemas` to NPM / publish-npm (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
|
||
|
|
74f46f90a1
|
🗺️ chore: Bump PostCSS to 8.5.18 to Patch Source Map Traversal (#14463)
Closes GHSA-r28c-9q8g-f849 (CVSS 7.5, CWE-22), a path traversal in previous source map auto-loading via sourceMappingURL that allows arbitrary .map file disclosure. Affected range is <=8.5.17, so the prior 8.5.13 pin was flagged high by npm audit. Raises both the root overrides entry, which governs the single copy in the tree, and the client devDependency floor. |
||
|
|
531fecc82b
|
🍃 chore: Bump Mongoose to 8.24.1 to Patch Prototype Pollution (#14461)
Closes GHSA-664h-wqgq-64gw (CVSS 6.5, CWE-1321), a prototype pollution in update casting via a __proto__-prefixed dotted path. Affected range is >=8.0.0 <8.24.1, so 8.23.1 was flagged by npm audit. |
||
|
|
a53936d273
|
🧭 test: Cover Agent Handoffs End to End (#14428)
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
* test: cover agent handoffs end to end * style: sort handoff imports * fix: normalize missing agent handoff edges * chore: update package dependencies and versions in package-lock.json and package.json * chore: bump agents SDK |
||
|
|
ca6ffb33fd
|
📦 chore: Update @librechat/agents to v3.2.68 (#14380)
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
|
||
|
|
cbaa2fe2e3
|
⚡ feat: Add Gemini 3.6 Flash and Gemini 3.5 Flash-Lite Support (#14369)
* ⚡ feat: Add Gemini 3.6 Flash and Gemini 3.5 Flash-Lite Support Adds first-class support for Google's Gemini 3.6 Flash (`gemini-3.6-flash`) and Gemini 3.5 Flash-Lite (`gemini-3.5-flash-lite`) for both the Gemini API (AI Studio) and Google Cloud/Vertex integrations. - Context window (1M) in googleModels; API + cache pricing in tx.ts. - Model dropdown (config.ts) and GOOGLE_MODELS examples for both integrations. - Generalize the Gemini 3.5 Flash overrides into a flash-family handler that strips deprecated temperature/topP/topK and applies each model's default thinking level (3.6 Flash: medium, 3.5 Flash-Lite: minimal), with longest-prefix resolution so flash-lite does not collide with flash. Ref: https://ai.google.dev/gemini-api/docs/latest-model#api-changes-and-parameter-updates * 🩹 fix: Strip unsupported penalty params for Gemini Flash family Gemini 3.6 Flash, 3.5 Flash-Lite, and 3.5 Flash reject presencePenalty/ frequencyPenalty with HTTP 400 ("Penalty is not enabled for this model", verified live). These pass through llmConfig via knownGoogleParams, so add them to the flash-family strip list alongside the deprecated sampling params. * 🩹 fix: Strip Flash-blocked params on custom Google endpoint path For custom OpenAI-compatible endpoints with defaultParamsEndpoint=google, getOpenAIConfig strips Flash-blocked params via getGoogleConfig but then transformToOpenAIConfig re-applies raw addParams, undoing the strip. Filter addParams through stripGeminiFlashBlockedParams before the transform so the deprecated sampling / rejected penalty params cannot reach the provider. * 🔧 chore: Update sharp package to version 0.35.3 in package-lock.json, api/package.json, and packages/api/package.json * 🔧 chore: Update dependencies in package-lock.json to latest versions for @google/genai (2.13.0), @hono/node-server (1.19.14), fast-uri (3.1.4), hono (4.12.31), and svgo (2.8.3) * 🔧 chore: Update dependencies in package.json and package-lock.json for @librechat/agents (3.2.67), @opentelemetry/sdk-node (0.221.0), and add new dependencies for @opentelemetry/propagator-jaeger (2.10.0) and protobufjs (7.6.5). Update monaco-editor version in client package.json to 0.56.0. * 🔧 chore: Upgrade turbo package to version 2.10.5 in package.json and package-lock.json, and update schema reference in turbo.json * 🩹 fix: Resolve CI breakage from bundled dependency bumps Not related to the Gemini models — both are fallout from the dep bumps on this branch: - monaco-editor 0.56 changed IEditorHoverOptions.enabled from boolean to 'on' | 'off' | 'onKeyboardModifier'; update ArtifactCodeEditor to match (mirrors the sibling occurrencesHighlight/matchBrackets pattern). - sharp 0.35.3 fails resize+encode on a degenerate 1x1 PNG (vipspng: libpng read error); the provider-file e2e fixture was 1x1, so use a 16x16 PNG. Normal images are unaffected (verified 64x64 resize/encode/jpeg all OK). * 📝 docs: Correct e2e image-fixture comment (bad IDAT CRC, not a sharp bug) Root cause was the old 1x1 fixture's corrupt IDAT CRC (verified: IHDR/IEND CRC OK, IDAT CRC BAD), which sharp 0.35.3's stricter libpng correctly rejects. Not a dimension/resize edge case and not a sharp bug; comment now reflects that. |
||
|
|
913540d00a
|
📦 chore: Update @librechat/agents to v3.2.66 & npm audit fix (#14361)
Some checks are pending
Docker Dev Images Build / build (Dockerfile, librechat-dev, node) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile.multi, librechat-dev-api, api-build) (push) Waiting to run
GitNexus Index / index (push) Waiting to run
GitNexus Index / post-index (push) Blocked by required conditions
Sync Locize Translations & Create Translation PR / Sync Translation Keys with Locize (push) Waiting to run
Sync Locize Translations & Create Translation PR / Create Translation PR on Version Published (push) Blocked by required conditions
Sync Helm Chart Tags / Ignore non-main push (push) Waiting to run
Sync Helm Chart Tags / Sync chart tags (push) Waiting to run
* 📦 chore: Update `@librechat/agents` to v3.2.66
* chore: npm audit fix
|
||
|
|
4321f68f29
|
📦 chore: Update @librechat/agents to v3.2.65 (#14263)
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
|
||
|
|
9bb351ad9c
|
🧭 feat: Mid-Run Steering and Queued Messages for Agent Runs (#14220)
* 🧭 feat: Mid-Run Steering and Queued Messages for Agent Runs Steering: submit a message while a run is generating; the server queues it in the job store (cross-instance) and a run-scoped PostToolBatch hook injects it into graph state at the next tool-batch boundary, records an inline 'steer' content part on the response (replayed as a user message on later turns), and streams on_steer_applied to the client. Queuing: messages composed during a run auto-send as normal follow-up turns after clean completion (one per final event, FIFO); user aborts leave them as chips unless armed by interrupt-and-send. Requires hook injectedMessages support in @librechat/agents (danny-avila/agents#299); hard-gated via a capability probe so older SDKs 501 the steer route instead of draining and dropping messages. * 🧵 fix: Harden Steering Against Finalization Races and Route Guard Gaps Addresses local Codex review findings on the steering feature: - Close-and-drain the steer queue atomically at finalization (final event, abort) so a steer POST racing teardown is rejected instead of 202-ACKed and then silently cleared; the closed flag lives on the job hash and is reset when a replacement job reuses the stream id. - Clear inherited steer queues on createJob — a job replacement must not drain the replaced run's messages. - Keep steers queued across a HITL pause instead of draining them into ephemeral client state: resumeState re-seeds chips on reload and the resumed run injects them at its first tool boundary (steers key TTL now extends to the approval window; on_steers_pending event removed). - Queue the NO_ACTIVE_RUN steer fallback while the final SSE is still settling — a direct send would be dropped by ask()'s in-flight guard. - Reconcile the 202 ACK against on_steer_applied events that beat it over the SSE, so a chip can't be re-minted after its removal event passed. - Allow the per-send Steer override when the default action is queue. - Apply the configured message rate limiters and the PII filter to POST /chat/steer — a steer is model-bound user text. * ✅ ci: Assert Steering Capability Probe Against the Installed SDK CI installs the published @librechat/agents pin (pre-injectedMessages), where isSteeringSupported() is legitimately false — the probe test now asserts it mirrors the installed SDK's capability flag instead of hardcoding the capability-bearing build's value. Verified against both the published 3.2.61 dist and the agents#299 build. * 🛟 fix: Preserve Steer Text Across Run-End, Error, and Abort Races Codex round 2 (4 P2s): - Applied-steer-id set survives run end (capped at 100) and converted ids join it, so a 202 ACK that lands after final/abort drops its chip instead of re-minting a stranded pending one. - Failed runs no longer strand acknowledged chips: both error paths convert local pending chips to queued follow-ups (chip text is client-local), and the server closes the steer queue before emitting the error so a racing steer POST gets 404 fallback instead of a 202 whose payload dies with the job. - sendQueuedNow keys on steer availability, not the default action — send-now on a queued chip is an explicit override for queue-preferring users. - Stop path consumes pendingSteers from the abort HTTP response as a fallback for the SSE final event it may close before processing; conversion is deduped so double delivery is a no-op (shared useSteerConvert hook). * 📎 feat: Carry Attachments Through During-Run Queued Messages Steering stays text-only (SDK injection, inline STEER part, and replay are all text), so a during-run submit with media now queues the whole message as one unit instead of silently stranding the files: - QueuedMessage gains `files`; composer attachments are consumed into the queued item at queue time (steerFromComposer / queueFromComposer / interruptAndSend), fixing the latent hazard where lingering composer files glued onto whatever `ask` vacuumed up next. - Enter-steer with attachments degrades to queue with an explanatory toast; the per-send menu routes through the same composer-aware wrappers. - The drain and sendQueuedNow pass the item's files as `overrideFiles`; media items never steer (send as a normal turn when idle, re-front otherwise). ask() no longer clears composer state for caller-supplied overrideFiles — only regenerate keeps that behavior. - During-run submits hold while uploads are in flight, mirroring the send button's filesLoading gate; queued chips show a paperclip count. * 🎛️ feat: Rework During-Run Chips into Action Rows Full-width rows above the composer (reference-UI parity): each queued message shows a primary Steer/Send-now action, delete, and a "…" menu with Edit message (restores text + attachments into the composer) and a Turn on queueing/steering toggle that flips the Enter default. Steer rows share the layout with status text; failed steers keep retry / edit / queue-convert. The per-send menu gains the same default toggle. Queued file refs now retain filename + bytes so edit-restore rebuilds real composer entries (draft-recovery shape). * 🖇️ feat: Steer With Attachments (Multimodal Mid-Run Injection) Steering now carries media end-to-end instead of degrading to queue: - The steer POST accepts sanitized attachment refs (cap 10; only file_id is trusted — the drain re-fetches owner-scoped and re-derives everything else). SteerQueueItem/TPendingSteer/SteerContentPart carry `files` refs; encoded data is never persisted or queued. - New api/server/services/Files/steering.js decouples attachment building from the request path: encodeSteerContent reuses the exact per-turn pipeline (addFileContextToMessage + processAttachments' single-pass categorize/encode, SDK formatMessage assembly, prependFileContext for extracted text) with zero new encoding code. buildSteerMedia feeds the drain hook's new buildMedia seam (any failure degrades that steer to text-only — words always land); stampSteerPartMedia re-encodes past steer parts per turn with ONE batched owner-scoped fetch and stamps a transient `media` array, replaced immutably so it can never leak into a save. Replay honors resendFiles like regular message media. - The SDK's formatAgentMessages (the formatter agents actually use) gained the steer replay branch on the PR branch; the local formatMessages.js branch now mirrors the media preference. - Client: steerFromComposer consumes composer files into the POST, chips/seeding/conversions carry files everywhere (retry, queue convert, abort/error recovery), queued media items steer for real, and SteerBubble renders the steered attachments inline. * 🧵 fix: Harden Steer Recovery Races and Drain Isolation Codex round 3 (7 fixes): - A 202 ACK landing after the run ended converts straight to a queued follow-up (server queue is gone; no event will ever resolve a pending chip for a finished run). Covers stream errors with in-flight POSTs. - A Stop that lands pre-completion can arrive as a final with unfinished:true and no aborted flag — runEnd now treats it as aborted so queued messages are not auto-sent against the user's Stop. - Leftover-steer conversion merges chronologically by createdAt instead of appending, preserving the order the user composed. - Auto-drained queued messages pass explicit (possibly empty) overrideFiles/overrideQuotes/overrideManualSkills: a drain can no longer vacuum up files, quotes, or skill picks staged in the composer for the user's NEXT message (ask() treats overrideFiles != null as authoritative). - Failed-steer Retry and resume-on-load chip restoration keep the steer's attachments. - The job-replacement guard moved INSIDE the store's atomic drain/close-and-drain (Lua createdAt compare; in-memory equivalent): a stale run's hook or finalization can neither consume, close, nor steal a replacement job's steer queue, and the drain hook drops its separate check-then-drain round trip. * 🧰 refactor: Typed Steer Controller, Single-Query Media Pass, Round-4 Fixes Codex round 4 + efficiency tightening in one pass: - Moved the steer guard ladder (validation, file sanitization via a shared toSteerFileRef picker, ownership/tenant checks, status-guarded enqueue) into packages/api as handleSteerRequest; api/steer.js is now a thin wrapper. Ladder covered against the REAL in-memory job manager in request.spec.ts; the api spec pins only the wrapper contract. - Folded the steer replay stamp into the turn's ONE historical-files query: collectHistoricalFileRefs also gathers steer-part refs, the owner-scoped doc map rides client state, and stampSteerPartMedia consumes it (no second round trip) while encoding parts in parallel. - Stamped steer media now counts against the run budget (existing multimodal counter over the non-text parts, folded into indexTokenCountMap/promptTokens after the stamp). - Steer route runs the PII filter BEFORE moderateText, matching chat.js so blocked sensitive text never reaches the external moderation API. - Interrupt & send survives the abort-response-beats-SSE-final race: stopGenerating writes the run-end signal itself when the one-shot interrupt flag is armed and no signal landed (double-fire safe). - Resume reconciles chips against the server's still-queued list even when EMPTY, clearing chips for steers applied while disconnected. - The local formatter's steer flush preserves non-text assistant parts (array-content AIMessage) instead of folding to text. * 🔒 fix: Replay-Aware Capability Gate and Round-5 Race Closures - isSteeringSupported now requires BOTH halves of the SDK contract: injection (HOOK_INJECTED_MESSAGES_CAPABLE) AND replay (ContentTypes.STEER, shipped in the same SDK commit as the formatAgentMessages steer branch). An SDK that can inject but not replay 501s the steer route — no release window can create steer parts that would leak into provider-facing assistant content. - The local formatter mirrors the SDK's anchor reset: a post-steer tool_call mints a fresh AIMessage instead of attaching to the pre-steer anchor (invalid provider ordering). - Queued-chip send-now and the NO_ACTIVE_RUN fallback pass explicit (possibly empty) overrideFiles so an idle send can't vacuum composer files staged for a different draft. - Redis createJob deletes the stale steer list BEFORE the replacement hash is written as running — a steer 202-accepted against the new job can never be wiped by the reset. - Resumed-turn finalization mirrors the normal path's terminal drain: createdAt-guarded close-and-drain, leftovers ride the resumed final event as pendingSteers instead of being cleared by completeJob. - buildSteerMedia restores composer order over the $in result so multi-attachment steers reach the model in the order the user saw. * ⚛️ fix: Atomic Job Replacement and Boundary-Clean Steering Module Codex round 6 (5 fixed, 1 standing deferral): - createJob resets the steer queue and writes the job hash in ONE same-slot Lua script (JOB_CREATE_LUA): a steer POST can no longer interleave between them on cluster, so a steer accepted against one run can never be drained into another. Redis-validated. - The steering media pipeline moved to packages/api (agents/steering/media.ts) with injected getFiles and a structural client interface — /api keeps zero steering logic; specs ported to the DI seam. - handleSteerRequest checks the job BEFORE the capability gate: a steer racing completion on an unsupported SDK gets 404 (send-now) instead of a 501 queue with no run-end signal left to drain it. - useQueueDrain binds to the active conversation: navigating away between the final SSE and the drain effect leaves the signal unconsumed instead of submitting A's follow-up into B; the drain fires on return. - abortJob closes and drains the steer queue BEFORE the content snapshot, so a drain-hook apply that lands pre-drain is captured inline rather than lost between the snapshot and the terminal drain. * 🚦 fix: Parked Run-End Signals, Interrupt Priority, Settled-Run Fallbacks Codex round 7 (5 fixes): - Run-end signals for a non-active conversation are PARKED per conversation instead of squatting the shared index slot: a later run finishing on the same pane can no longer overwrite them, and the parked drain fires when the user returns. - "Interrupt & send" front-inserts carry a priority flag that outranks createdAt when abort leftovers merge back chronologically — the urgent redirect drains first, not the oldest steer. - STEER_UNSUPPORTED/RUN_PAUSED/QUEUE_FULL rejections landing after the run settled mirror the NO_ACTIVE_RUN fallback and send immediately (queueing would strand the text with no run-end signal left); on the pinned SDK this is the common Enter-near-run-end path. - A failed abort (e.g. 404 when the run completed first) still signals the interrupt drain, so the queued interrupt message can't strand and the armed flag can't leak onto a later run. - Steered-image fallback alt text is localized (com_ui_attached_image). * 📌 chore: Adopt Published @librechat/agents Types Post-Bump dev's pin bump to ^3.2.62 (the release carrying injection + steer replay) landed via merge; the steering runtime now uses the SDK's real InjectedMessage/hook-output types instead of the local structural mirrors that bridged the pre-publish window. The two-half capability probe stays as the defensive gate for mismatched deployments — and the capability spec now exercises its TRUE path against the published package in CI. * 🛅 feat: Park-and-Claim Steer Recovery + Host-View Content Reads Codex round 8 (6 fixed incl. both P1s, 1 push-back): - The long-deferred no-subscriber gap is closed: every terminal drain (final, aborted-final, error, abortJob, resumed finalize) PARKS acknowledged leftovers on the job hash (unrecoveredSteers), and the status route claims them exactly once for inactive jobs — a client that closed/reloaded past the transient final event restores its steers as queued chips within the post-terminal TTL. A replacement run clears the parked copy (a live client started it). - Same-instance content reads are steer-complete: RedisJobStore now caches the HOST content array (WeakRef) via setContentParts and prefers it over the SDK graph cache, whose view never contains host-authored steer parts; the graph fallback splice-INSERTS steer chunks at their recorded host-view indices (the graph array is unshifted, so assignment would overwrite SDK parts). - Replay token accounting now counts prepended file-context text: full stamped content minus the steer body (already counted), so large steered documents hit the budget instead of bypassing pruning. - The queue drain restores an item when ask() refuses without sending (history not yet in cache after navigating back) — text is never silently dropped. - The armed interrupt flag travels WITH a parked run-end signal, so another run on the same pane can neither consume nor clear it. - parseTextParts extracts steer text (search indexing / audio). * 🎛️ refactor: Single Send Slot + In-Thread Steer Messages - Merge the during-run send affordance into the send/stop button slot: with composer text the send button replaces Stop (Enter = default action), hover reveals Steer/Queue/Interrupt rows with shortcuts; drop the separate DuringRunActionsMenu chevron - Add during-run keyboard chords: Cmd/Ctrl+Enter = non-default action, Alt+Enter = interrupt & send (plain-Enter submitters only) - Render steers as standard user messages in the thread: SteerPart (icon + author header + user text presentation) replaces the SteerBubble, and submitted steers appear immediately at the projected injection point via the PendingSteers slot on the streaming message - Keep composer rows only for recoverable states: failed steers (retry/edit/queue) and queued follow-ups * 🩹 fix: Keep the Replacement Submission Alive Across Abort Settlement The aborted run's final SSE event fires before the abort HTTP response resolves, so an armed interrupt & send drains and starts the NEXT submission while the abort POST is still in flight. The response handler's unconditional clearAllSubmissions() then reset the new submission, aborting its stream attach before the subscribe — the follow-up ran and persisted server-side but the live placeholder finalized empty (content appeared only after reload). useAbortCleanup captures the submission before the abort round-trip and both settlement paths (success and 404-catch) clear only when the captured submission is still current; a replacement stays untouched. Plain Stop behavior is unchanged. * 🧭 test: Playwright E2E for Mid-Run Steering and Queuing - Add e2e/specs/mock/steering.spec.ts: steer mid-run (202 + immediate in-thread pending part + real MCP tool boundary + words survive run end), Cmd/Ctrl+Enter queue with auto-send after clean completion, and Alt+Enter interrupt & send with the follow-up streaming into the live view - Add the E2E_STEER_TOOL_REPLY fake-model marker: slow preamble, a real remember_fact MCP tool call (PostToolBatch boundary), then a final turn - Test 1 pins the run-end degradation contract while the SDK's top-level agentId stamping bug blocks live injection; its header documents the assertions to flip once the fixed SDK is pinned * 🧷 fix: Job-Independent Steer Recovery + Expiry and Resume-Gap Parking Codex round 10: the park-and-claim recovery had lifecycle holes. - Move parked steers off the job hash onto their own bounded-TTL store key (JOB_CREATE_LUA resets it; deleteJob leaves it alone): the default completeJob path deletes the job record immediately, and the Redis read path never deserialized the old hash field — recovery previously worked only with STREAM_KEEP_COMPLETED_JOBS on the in-memory store - Carry the owner identity inside the parked payload and authorize the claim against it, so the status route recovers steers on its jobless branch too (the common reload-after-terminal case); a non-owner claim returns nothing and re-parks the payload - Park queued steers on approval expiry: snapshot the frozen queue before the requires_action→aborted CAS (whose terminal cleanup drops the steers key) and park only when the CAS wins - Mirror the terminal drain/park block in resume.js's failure path, which previously let completeJob's backstop clear 202-accepted steers - Close the Redis snapshot→subscribe resume gap: re-peek the queue after attaching and re-surface missed on_steer_applied events from the durable content view (synthesizeAppliedSteerEvents), updating resumeState.pendingSteers to the live queue * 📌 chore: Require @librechat/agents 3.2.63 + Applied-Steer E2E Contract - Bump the @librechat/agents pin to ^3.2.63 in api/ and packages/api/: it scopes the hook agentId marker to subagent child graphs, so the steering drain hook fires at top-level tool-batch boundaries and mid-run injection is active (danny-avila/agents PR 307) - Flip e2e steering test 1 from the documented degradation contract to the applied-steer contract: the optimistic in-thread part transitions to the persisted part at the tool boundary and survives inside the response after run end, with no queued follow-up turn * 🎗️ feat: Steered Messages Join the Message-Nav Ribs Steers are user messages, so they get their own clickable rib on the navigation rail, interleaved at their in-thread position inside the response that absorbed them (one DOM query in document order). SteerPart anchors itself as #steer-<id> with a steer-render marker — both the optimistic pending entry and the persisted part — and the rib carries the user role label with a preview drawn from the steer's text body, skipping the author header. * ❎ feat: Cancel a Queued Steer Before Injection + True User-Message Alignment - Add POST /chat/steer/cancel: removes ONE still-queued steer by id via an atomic list rebuild (Redis Lua preserves order and TTL), authorized against the job owner; removed:false is advisory — the cancel lost its race to the drain or the run end, never an error - Surface an × on the in-thread pending steer (server-acknowledged entries only): optimistic removal, restored if the POST fails since the server would still inject the words - Outdent SteerPart past the response's icon column so steers sit flush with top-level message rows, reading as regular user messages * 🧯 fix: Round-11 Recovery Hardening + Provider-Free Pending Slot - Reconcile the resume steer gap by steerId SETS, not queue length — a steer added in the gap (or an equal-length drain+enqueue swap) now refreshes resumeState.pendingSteers and still synthesizes the missed on_steer_applied events - Make completeJob's terminal backstop park: direct error-path callers without the controllers' close-and-park no longer silently clear 202-accepted steers (createdAt-guarded closeAndDrain + owner park before the terminal write) - Persist the steer part BEFORE media encoding in the drain hook: an abort inside the encode window can no longer lose a file-steer (the part refs come from the enqueue-sanitized item; replay re-encodes per turn unchanged) - Move the parked-claim owner check INSIDE the atomic store claim (substring gate in the Lua / in-memory equivalent): a non-owner probe can no longer transiently delete the recovery payload; the app-side parse stays authoritative - Park queued steers in BOTH stores' own requires_action expiry cleanup, which bypassed the manager-level sweep - Sweep expired parked steers from the in-memory store's periodic cleanup; restore a queued chip when send-now's submit is refused; upsert steer ACKs so an SSE reconnect reseed cannot duplicate chips - Mount the cancel mutation per steer item so the pending slot needs no QueryClient on ordinary streaming renders (fixes the CI failure in ContentParts.integration.test) - Skipped delivery-gated parking (finding 8): transport receiver counts cannot prove browser delivery, and gating the only durable copy on them trades cosmetic chip resurrection for real text loss; the window is already bounded by claim-on-read, createJob reset, and the TTL * 🩺 fix: Annotate PARKED_STEERS_TTL_MS for isolatedDeclarations tsdown's d.ts generation requires explicit types on exported consts with computed initializers; tsc --noEmit does not run that check, so the round-11 export slipped past local verification and broke Build packages (and every downstream CI job that consumes the built dist). * 🛟 fix: Round-12 Terminal-Path Recovery + Durable Steer Events - Park queued steers before the stale-running reap deletes a crashed or hung job in BOTH stores — the one terminal path with no controller finalization; requires_action expiry parking refactored onto the same snapshot/park helpers - Enqueue instead of dropping when a steer fallback send is refused: both the NO_ACTIVE_RUN branch and the settled-run rejection branch now observe sendNow's false return - Recover on the SSE reconnect-404 terminal path: convert local pending steers to queued, claim parked steers via /chat/status, and write a non-completed run-end signal so interrupt flags release without auto-sending an unknown outcome - Fall back to a positive parked-recovery TTL when completedTtl is 0 (SET EX 0 is invalid and silently killed recovery) - Make on_steer_applied durable before publish: emitChunk gains a durable option that awaits the chunk-log append (best-effort) ahead of the transport publish; the default delta path stays fire-and-forget * 🔐 fix: Round-13 Steer Authorization + Trusted File Refs - Resolve client-supplied steer file refs against the DB owner-scoped at enqueue and queue only DB-derived shapes (same filter as the injection fetch, shared via refs.ts); any unresolved id fails loud with 400 — spoofed type/filepath metadata can no longer be persisted into assistant content or rendered in chat/share views - Enforce agent authorization on /chat/steer against the ORIGINATING run's job identity: the chat path's role gate (AGENTS:USE, with the same non-agents-endpoint skip) plus the per-agent ACL check with the capability bypass — revoked access mid-run can no longer inject; cancel stays ownership-only (nothing model-bound) - Mark steered uploads used after a successful enqueue (owner-scoped, best-effort) so the upload-window TTL cannot reap a file the persisted steer part references - Consume the parked recovery copy after live delivery: converting final/abort/error pendingSteers fires one owner-gated claim-on-read, so dismissed chips can no longer resurrect on a later reload * 🎙️ fix: Round-14 Composer-Context Fidelity + TTS and Queue-State Gaps - Keep steer text out of generic assistant text extraction: parseTextParts excludes STEER parts by default with an includeSteer opt-in for the full-record surfaces (Meili indexing, aborted-response persistence) — TTS callers no longer speak the user's own mid-run words - Mark queued uploads used at enqueue time via a minimal owner-scoped POST /files/usage (fail-closed without a user; upload limiters do not apply to a metadata touch), fired once wherever composer files enter the queued state — the upload-window TTL can no longer reap a file waiting out a long run or approval pause - Carry quote chips and manual skill picks on queued items: captured and consumed from the composer at queue/interrupt time exactly like files, threaded through the drain and send-now overrides, and restored by the queued row's Edit message - Key an early-aborted FIRST turn's run-end signal to NEW_CONVO (resolveRunEndTarget) so queued follow-ups stay visible on the restored new-chat composer instead of parking under an optimistic stream id the user never sees again * 🧿 fix: Round-15 Gap Coverage + Consolidated Sweep (Share Leak, Abort Ids, Chip Hygiene) - Run the resume steer-gap check for every still-active job: an empty snapshot no longer skips the re-peek, and synthesis now keys on the FRESH content view so an applied-in-gap steer that was never snapshotted still re-surfaces (over-emission is benign — applied-id dedupe, index-stable parts) - Thread queued context through steer degradation: sendQueuedNow passes the item's quotes/skills into submitSteer, and every fallback (requeue or settled send) restores them instead of dropping to text+files - Stop shared links from leaking steer attachment refs: the share snapshot now walks content — files-excluded shares strip steer-part files entirely; files-included shares sanitize and share-route them like top-level files (copy-on-write, non-steer content by reference) - Seed pending-steer chips unconditionally on load/return so a steer applied while away cannot linger as a stale chip beside its part - Use the abort response's resolved job id: chips/drain-signal land where the user actually is (NEW_CONVO for a new-held first turn, consistent with resolveRunEndTarget) while the parked-copy claim hits the resolved id instead of a no-op /chat/status/new - Open steered documents like normal message files (FilePreviewDialog) - Cap the applied-steer id set on the live path via a shared helper; kept surviving run end deliberately (late-ACK race depends on it) and fixed the atom comment that claimed otherwise * 💡 fix: Un-light Steer Ribs When Their Node Is Replaced Two stacked gaps kept a steer rib lit after scrolling away: the pending→applied swap replaces the DOM node under the same id, which produces no IntersectionObserver exit and — because the entry list dedupes on (id, preview) — no entries change either, so the observer kept watching a detached node; and the rail's mutation filter only reacted to .message-render nodes, so steer-node swaps and removals never triggered a refresh at all. - reconcileObservedElements re-points the observer at replaced nodes from the mutation-driven refresh regardless of entries identity, dropping stale visibility until the fresh node reports (the observer fires its initial intersection immediately, so a truly visible part re-lights within a frame) - The mutation filter now recognizes steer-render nodes alongside message rows * 🪪 fix: Round-16 Recovery Owner Fields + Context Stickiness + Share Labels - Park resumed-run leftovers with the manager facade's metadata owner fields: a bare job.userId is undefined on that shape, which made every parked payload from a resumed HITL run unclaimable - Keep a queued item's quotes/skills sticky through a successful steer ACK: the pending chip carries them (client-only), reseeds preserve them across reconnects, and every terminal conversion — local or server-list, merged by steerId — restores them onto the queued item - Convert resumeState.pendingSteers on the inactive status branch (deduped against unrecoveredSteers) so steers observed in the expired-pause-before-sweeper window convert instead of vanishing until a later reload - Label shared steer parts share-safely via the existing ShareContext: a viewer's own name no longer appears on the sharer's steered messages * ✂️ fix: Carry Steer Context Through the Failed-Chip Edit Action Retry and convert-to-queue already preserve a failed steer's carried quotes/skills; Edit message dropped them on the way back to the composer. It now restores them through the same context path. |
||
|
|
11caf5f21c
|
📦 chore: Update @librechat/agents to v3.2.62 (#14235)
- Bump @librechat/agents from 3.2.61 to 3.2.62 - Update @langchain/core from 1.2.1 to 1.2.2 - Upgrade openai package from 6.45.0 to 6.46.0 - Adjust @langchain/openai version to 1.5.5 in nested dependencies - Add zod package version 4.4.3 for improved validation |
||
|
|
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 |
||
|
|
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> |
||
|
|
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. |
||
|
|
bb7d99d56c
|
🫷 feat: Exclude File Authoring Tools From Eager Execution (#14051)
* feat: exclude create_file/edit_file from eager execution Side-effecting host file-authoring tools should not be speculatively eager-executed: a write can land before the turn commits, and the eager path's incrementally-streamed args can diverge from the final tool call, tripping the SDK's 'changed after eager execution' guard so the model is told the write failed and loops (observed with create_file writing a large file to /mnt/data). Pass excludeToolNames so these tools run on the normal ToolNode path with the final args. Requires @librechat/agents with eager-exclusion support; older versions ignore the field. * chore: Bump `@librechat/agents` to v3.2.56 * refactor: reorder imports in run.ts for clarity * fix: also exclude execute_code/bash_tool from eager execution The eager 'changed after eager execution' corruption isn't specific to file authoring — any tool with a large free-form streamed arg is exposed. Observed live: a bash_tool heredoc (a full Python script in `command`) tripped the guard and the write never landed. execute_code (`code`) and bash_tool (`command`) carry large args and run code (side effects), so exclude them from eager alongside create_file/edit_file. * feat: wire codeSessionToolNames so create_file/edit_file share the code sandbox Activates the agents#283 capability: pass create_file/edit_file as codeSessionToolNames so their exec session/files fold into the shared code session and a file they write is visible to later execute_code/bash_tool calls (and the existing session is injected into their requests). No-op until @librechat/agents ships codeSessionToolNames (agents#283). * test: guard code-tool eager/session wiring in createRun Asserts createRun passes excludeToolNames (create_file/edit_file/execute_code/ bash_tool) and codeSessionToolNames (create_file/edit_file) to Run.create — the wiring the create_file->bash_tool sandbox-sharing chain depends on, which was silently missing before. Guards against a future edit dropping it. Mirrors the run-summarization test harness (mocks Run.create). The full create_file->bash_tool chain runs through the real code sandbox and can't run in the mock CI harness; the SDK mechanism is covered by @librechat/agents unit tests, and this guards the LibreChat wiring. * style: fix prettier formatting in run-codeTools test * chore: Bump `@librechat/agents` to v3.2.57 |