LibreChat/client
Danny Avila 94c73123ee
📋 fix: Cap Default Limit on Agent List Queries (#13382)
* 🛡️ fix: Cap Default Limit on Agent List Queries (#13363)

`GET /api/agents` accepted unbounded requests: when the client omitted
`limit`, the value flowed straight into `getListAgentsByAccess`, which
set `isPaginated = false` and issued an uncapped MongoDB query. Combined
with the unindexed `findPubliclyAccessibleResources` AclEntry scan run
on every request, this produced 10-19s response times and stalled the
connection pool on instances with 100+ agents.

- Default `limit` to 100 in the route handler so client requests without
  `?limit=` paginate by default.
- Default `limit` to 100 in `getListAgentsByAccess` itself as
  defense-in-depth. The function already caps numeric limits at 100, so
  there is no client-facing change.
- Pass `limit: null` explicitly in the actions route, which legitimately
  needs the full editable-agent set, to preserve its existing behavior.
- Add regression tests covering the default cap and the explicit
  unbounded opt-out.

* 🛡️ fix: Avoid agent-list regression for users with 100+ agents

Codex review pointed out that capping `getListAgentsByAccess` at 100
silently truncated agents past the first page for the four consumers
(`useAgentsMap`, `AgentSelect`, `ModelSelectorContext`, `useMentions`)
that read `res.data` without following `has_more`/`after`.

- Raise the function's hard cap from 100 to 1000 to match
  `MAX_AVATAR_REFRESH_AGENTS`, the realistic upper bound the
  avatar-refresh path already assumes. (Side effect: the avatar refresh
  call site was silently being capped at 100 by the old normalize step.)
- In `useListAgentsQuery`, merge `limit: 1000` into params so the four
  consumers above get the user's full accessible set in a single
  round-trip instead of needing cursor pagination.
- Route handler default stays at 100 as defense-in-depth for any other
  caller that omits `limit`.
- Add a regression test asserting an explicit `limit` above 100 now
  returns the full set instead of being clipped.

* 🪢 fix: Keep agent-list cache key stable for mutations

Codex P2 review noted that folding `limit: 1000` into the cache key
broke `allAgentViewAndEditQueryKeys` in `Agents/mutations.ts`, which
references `[QueryKeys.agents, { requiredPermission }]` directly across
eight mutation handlers. After my prior change the cached entry lived
under `[QueryKeys.agents, { limit: 1000, requiredPermission }]`, so
create/update/delete/avatar/action mutations stopped updating the list
the four consumer hooks render — and with `refetchOnMount` and focus/
reconnect refetches disabled, the UI would stay stale until something
else triggered a fetch.

Split the merged limit out of the cache key: the request to
`dataService.listAgents` still uses `requestParams` (with the default
limit applied), but the React Query cache key uses the caller's `params`
as-is. The mutation cache updates land again, and the request still
returns the user's full accessible set in one round-trip.

* 🛡️ fix: Index AclEntry and paginate agent list internally (#13363)

Completes the perf fix for #13363 properly — resolves both the
unbounded ACL scans Copilot flagged and Codex's tension between "show
all agents" and "don't bypass the server cap".

Backend:
- Add a compound index on `{ principalType, resourceType, permBits,
  resourceId }` to the AclEntry schema. This is the index missing for
  `findPublicResourceIds` and the public branch of the `$or` in
  `findAccessibleResources`, both of which previously fell back to a
  collection scan on every `GET /api/agents`. Adds an `explain`-based
  regression test asserting the public query no longer COLLSCANs.

Client:
- Rewrite `useListAgentsQuery` to follow the server's cursor
  pagination internally and concatenate every page into a single flat
  `AgentListResponse`. Consumers (`useAgentsMap`, `AgentSelect`,
  `ModelSelectorContext`, `useMentions`) get the user's complete
  accessible-agent set without any of them needing to learn about
  cursors, and each individual request uses the server's default
  page size (so the route's 100-default defense-in-depth fires for
  real). Cache key shape is unchanged, so the eight mutation handlers
  in `Agents/mutations.ts` keep matching `allAgentViewAndEditQueryKeys`
  and update the cached list as before.
- Drop the `FULL_AGENT_LIST_LIMIT = 1000` injection added in the
  previous commit — no longer needed once pagination handles the full
  set, and removing it stops bypassing the route default.

* 🧹 fix: CI fallout from C-done-properly refactor

- Collapse multi-line `fetchAllAgentPages` signature in queries.ts so
  prettier stops complaining.
- In the new public-principal index test, grant one ACL entry before
  calling `.explain()` so the collection exists (otherwise mongo returns
  `nonExistentNamespace` and there is no winning plan to inspect).
- Cast the `.explain('queryPlanner')` result to a typed shape — the
  mongoose return type doesn't expose `queryPlanner` directly and was
  failing the TypeScript check.

* 🧪 fix: Test the AclEntry public-principal index via hint, not planner choice

The previous test asserted the query planner did not pick COLLSCAN for
the public-principal lookup. That assertion fails on small collections
(under the planner's collection-size heuristic) — the index exists and
is usable, but with a single document in the test the planner correctly
chooses COLLSCAN as the cheaper plan.

Reshape the assertion:
1. Confirm the new compound index is actually declared by inspecting
   `collection.indexes()` after `syncIndexes()`.
2. Force the planner to that index via `.hint()` and assert the winning
   plan is `IXSCAN` — proves the index is real and serves this query
   shape, without depending on collection-size heuristics.

* 🧹 chore: Slim down verbose comments

The JSDoc and inline comments added across the perf fix had drifted
into multi-paragraph rationale better suited to the PR description than
the source. Collapse to single-line JSDoc that just describes what each
piece does; drop the inline comment in `actions.js` entirely — the call
is self-evident.
2026-05-28 21:37:53 -07:00
..
public 🎨 chore: Update Agent Tool with new SVG assets (#12065) 2026-03-04 09:28:19 -05:00
scripts
src 📋 fix: Cap Default Limit on Agent List Queries (#13382) 2026-05-28 21:37:53 -07:00
test 🧑‍🎨 refactor: Prompts/Sidebar styles for improved UI Consistency (#12426) 2026-04-09 00:02:31 -04:00
babel.config.cjs 🧑‍🎨 refactor: Prompts/Sidebar styles for improved UI Consistency (#12426) 2026-04-09 00:02:31 -04:00
check_updates.sh
index.html
jest.config.cjs v0.8.6-rc1 (#13094) 2026-05-12 21:40:23 -04:00
nginx.conf 📬 docs: Add Forwarded Headers to Nginx SSL Proxy Template (#12379) 2026-03-25 13:04:19 -04:00
package.json v0.8.6-rc1 (#13094) 2026-05-12 21:40:23 -04:00
postcss.config.cjs
tailwind.config.cjs style(MCP): Enhance dialog accessibility and styling consistency (#11585) 2026-02-11 22:08:40 -05:00
tsconfig.json 📦 chore: Update TypeScript Config for TS v7 (#12794) 2026-04-23 12:51:03 -04:00
vite.config.ts 📜 feat: Skills UI + Initial E2E CRUD / Sharing (#12580) 2026-04-25 04:02:00 -04:00