Commit graph

9 commits

Author SHA1 Message Date
Danny Avila
84fa6aa820
🧹 feat: Eager HITL Checkpoint Cleanup (Expiry + Deletion) & Full-Wiring E2E (#14123)
* feat: eager HITL checkpoint cleanup on expiry + deletion, full-wiring e2e

Follow-up to the lazy checkpointer (#14024): two paths still left a paused
run's durable checkpoint to the 24h Mongo TTL, and no test exercised the
whole HITL seam with real components.

1. Approval expiry: GenerationJobManager.setApprovalExpiredHandler(fn) — a
   non-destructive host hook fired after expireApproval's CAS succeeds
   (periodic sweeper AND stale-submit path), safe on startups that run
   constructor defaults. Both startups (index.js configureGenerationStreams,
   experimental.js) register a handler that prunes the checkpoint, resolving
   config lazily per expiry (streamId === conversationId === thread_id).

2. Conversation deletion: deleteConvos now returns the deleted
   conversationIds; the three deletion paths (DELETE /convos, DELETE
   /convos/all, account deletion) prune them via the new bulk
   deleteAgentCheckpoints (one $in deleteMany per collection). The delete
   routes gain configMiddleware for the checkpointer config.

3. Full-wiring e2e (hitlCheckpoint.e2e.spec.js): real SDK Run driven by
   FakeChatModel calling a gated tool, real PreToolUse/humanInTheLoop wiring,
   real LazyMongoSaver over mongodb-memory-server, real GenerationJobManager,
   real /resume controller via supertest. Asserts: clean turn persists
   nothing; error turn persists nothing; pause -> HTTP approve -> gated tool
   executes exactly once -> finalize prunes the checkpoint; expiry prunes the
   abandoned pause eagerly.

Tests: 3 handler unit tests (pendingAction.spec), 2 bulk-prune integration
tests (checkpointer.integration.spec), convos route + deleteUser specs
updated, 4 e2e scenarios. 207 tests green across changed areas.

* fix: tenant-scoped expiry prune, store-won expiry relay, resilient deleteConvos ids

Codex round 1 on #14123 — all three valid:

1. The approval-expired handler now receives the expired JOB so both startups
   resolve config in the paused job's tenant/user scope (getAppConfig({userId,
   tenantId})) — a tenant checkpointer override no longer sends the prune to
   the base config's collections. expireApproval fetches the job best-effort.

2. Multi-replica: when RedisJobStore.cleanupRequiresActionIndex wins the
   expiry CAS on another replica, this replica's sweeper relay branch now runs
   the approval-expired cleanup too (prune is idempotent) — store-driven
   expiry no longer bypasses the hook.

3. deleteConvos: post-delete cleanup (deleteMessages, project stats refresh)
   is now best-effort — the conversations are already gone, so throwing hid
   the deletion and dropped the conversationIds the checkpoint prune needs,
   unrecoverable on retry. Updated the existing tag-decrement-on-failure test
   to the new contract (ids still returned).

Tests: handler-receives-job, store-won relay path, ids-survive-cleanup-failure.
134 tests green across changed suites.

* fix: relay cleanup independent of cached errorEvent; enter tenant ALS context

Codex round 2 on #14123:

1. The sweeper's relay branch gated BOTH the terminal-error emit and the new
   checkpoint cleanup on !runtime.errorEvent — but a reconnect seeds errorEvent
   from the aborted job (runtime-state creation), which then suppressed the
   cleanup entirely. The emit stays gated; the idempotent cleanup now runs
   independent of the cached error, once per runtime lifetime
   (approvalCleanupRan flag — the aborted job is swept repeatedly).

2. Passing userId/tenantId to getAppConfig only keys the config cache; the
   Config query is ALS-scoped by the tenant-isolation plugin. Both startup
   handlers now ENTER the paused job's tenant context via tenantStorage.run
   before resolving config + pruning, so a tenant checkpointer override is
   honored in strict and non-strict modes.

Tests: relay-cleanup-with-cached-error (reconnect simulation), repeated sweeps
run the cleanup once. 27+4 green.

* fix: dedup expiry cleanup across winner and relay paths

Codex round 3 (P3): expireApproval ran the handler without marking the
runtime's approvalCleanupRan flag, so the next sweep's relay branch (the
aborted job outlives expiry for the completed-job TTL) ran the cleanup a
second time. The dedup now lives inside runApprovalExpiredHandler — the
single choke point both paths call — set-before-run, once per runtime
lifetime. Test: local expiry followed by a sweep fires the handler once.
2026-07-05 11:29:30 -04:00
Dan Lew
743f57f63e
🔖 feat: Add Pinned Conversations (#13492)
* feat: add `convo.pinned`

We want to be able to pin convos (so users can easily find them), thus we
added a new field to the DB schema: `pinned`.

We also had to add an API method for pinning a convo. It's got thorough tests.
It's structured just like how /api/convos/archive works, only for pinning.

* feat: add 'pinned' section to conversation list

If there are any pinned conversations, they will appear above the normal
"chats" list, with a pinned icon next to them.

* feat: added pin/unpin to convo options

ConvoOptions now has a pin/unpin button which lets you change the
pin status of any given conversation.

* fix: adjust ellipsizing gradient on ConvoLink

Because it went across the whole ConvoLink, it would cover up any
children (i.e. icons) that appear after the title. However, the point
of the gradient is just to gradually make the title disappear, not
the icons.

This change places the gradient on the title only, so it achieves
the same ellipsizing effect without interfering with the display of
the child icons.

* Fixed import sorting
2026-06-17 20:26:55 -04:00
Atef Bellaaj
86fe79c37d
🔗 feat: Add Granular Access Control to Shared Links via ACL System (#13051)
* feat: Add granular access control to shared links via ACL system

* fix(shared-links): preserve isPublic on failed migration grants

Transient ACL failures during auto-migration permanently stranded
links — $unset ran unconditionally, removing the legacy flag that
triggers retry. Now only $unset isPublic after all grants succeed.

* fix(config): skip isPublic unset for failed ACL grants

Bulk migration unconditionally removed isPublic from all links,
even those whose ACL writes failed. Failed links then lost the
legacy marker needed for auto-migration retry. Now tracks failed
link IDs per-batch and excludes them from the $unset step.

Also adds sharedLink to AccessRole resourceType schema enum —
was missing, only worked because seedDefaultRoles uses
findOneAndUpdate which bypasses validation.

* ci(config): add jest config and PR workflow for migration tests

config/__tests__/ specs depend on api/jest.config.js module
mappings but had no dedicated runner. Adds config/jest.config.js
extending api config with absolutized paths, npm test:config
script, and a GitHub Actions workflow triggered by changes to
config/, api/models/, api/db/, or packages/ ACL code.

* fix(permissions): honor boolean sharedLinks config

SHARED_LINKS has no USE permission, so boolean config produced
an empty update payload — gate conditions only matched object
form, making `sharedLinks: false` a no-op on existing perms.

* fix(share): resolve role before creating shared link

Role lookup between create and grant left an orphaned link
without ACL entries if getRoleByName threw — retry then hit "Share already exists" with no recovery path.

* fix: Restore Public ACL Access Checks

* fix: Type Public ACL Lookup

* fix: Preserve Private Legacy Shared Links

* chore: Promote Shared Link Permission Migration

* fix: Address Shared Link Review Findings

* fix: Repair Shared Link CI Follow-Up

* fix: Narrow Shared Link Mongoose Test Mock

* fix: Address Shared Link Review Follow-Ups

* fix: Close Shared Link Review Gaps

* fix: Guard Missing Shared Link Permission Backfill

* test: Add Shared Link Mock E2E

* test: Stabilize Shared Link Mock E2E

---------

Co-authored-by: Danny Avila <danny@librechat.ai>
2026-06-03 14:17:17 -04:00
Danny Avila
8ba2bde5c1
📦 refactor: Consolidate DB models, encapsulating Mongoose usage in data-schemas (#11830)
* chore: move database model methods to /packages/data-schemas

* chore: add TypeScript ESLint rule to warn on unused variables

* refactor: model imports to streamline access

- Consolidated model imports across various files to improve code organization and reduce redundancy.
- Updated imports for models such as Assistant, Message, Conversation, and others to a unified import path.
- Adjusted middleware and service files to reflect the new import structure, ensuring functionality remains intact.
- Enhanced test files to align with the new import paths, maintaining test coverage and integrity.

* chore: migrate database models to packages/data-schemas and refactor all direct Mongoose Model usage outside of data-schemas

* test: update agent model mocks in unit tests

- Added `getAgent` mock to `client.test.js` to enhance test coverage for agent-related functionality.
- Removed redundant `getAgent` and `getAgents` mocks from `openai.spec.js` and `responses.unit.spec.js` to streamline test setup and reduce duplication.
- Ensured consistency in agent mock implementations across test files.

* fix: update types in data-schemas

* refactor: enhance type definitions in transaction and spending methods

- Updated type definitions in `checkBalance.ts` to use specific request and response types.
- Refined `spendTokens.ts` to utilize a new `SpendTxData` interface for better clarity and type safety.
- Improved transaction handling in `transaction.ts` by introducing `TransactionResult` and `TxData` interfaces, ensuring consistent data structures across methods.
- Adjusted unit tests in `transaction.spec.ts` to accommodate new type definitions and enhance robustness.

* refactor: streamline model imports and enhance code organization

- Consolidated model imports across various controllers and services to a unified import path, improving code clarity and reducing redundancy.
- Updated multiple files to reflect the new import structure, ensuring all functionalities remain intact.
- Enhanced overall code organization by removing duplicate import statements and optimizing the usage of model methods.

* feat: implement loadAddedAgent and refactor agent loading logic

- Introduced `loadAddedAgent` function to handle loading agents from added conversations, supporting multi-convo parallel execution.
- Created a new `load.ts` file to encapsulate agent loading functionalities, including `loadEphemeralAgent` and `loadAgent`.
- Updated the `index.ts` file to export the new `load` module instead of the deprecated `loadAgent`.
- Enhanced type definitions and improved error handling in the agent loading process.
- Adjusted unit tests to reflect changes in the agent loading structure and ensure comprehensive coverage.

* refactor: enhance balance handling with new update interface

- Introduced `IBalanceUpdate` interface to streamline balance update operations across the codebase.
- Updated `upsertBalanceFields` method signatures in `balance.ts`, `transaction.ts`, and related tests to utilize the new interface for improved type safety.
- Adjusted type imports in `balance.spec.ts` to include `IBalanceUpdate`, ensuring consistency in balance management functionalities.
- Enhanced overall code clarity and maintainability by refining type definitions related to balance operations.

* feat: add unit tests for loadAgent functionality and enhance agent loading logic

- Introduced comprehensive unit tests for the `loadAgent` function, covering various scenarios including null and empty agent IDs, loading of ephemeral agents, and permission checks.
- Enhanced the `initializeClient` function by moving `getConvoFiles` to the correct position in the database method exports, ensuring proper functionality.
- Improved test coverage for agent loading, including handling of non-existent agents and user permissions.

* chore: reorder memory method exports for consistency

- Moved `deleteAllUserMemories` to the correct position in the exported memory methods, ensuring a consistent and logical order of method exports in `memory.ts`.
2026-03-21 14:28:53 -04:00
Danny Avila
ca79a03135
🚦 fix: Add Rate Limiting to Conversation Duplicate Endpoint (#12218)
* fix: add rate limiting to conversation duplicate endpoint

* chore: linter

* fix: address review findings for conversation duplicate rate limiting

* refactor: streamline test mocks for conversation routes

- Consolidated mock implementations into a dedicated `convos-route-mocks.js` file to enhance maintainability and readability of test files.
- Updated tests in `convos-duplicate-ratelimit.spec.js` and `convos.spec.js` to utilize the new mock structure, improving clarity and reducing redundancy.
- Enhanced the `duplicateConversation` function to accept an optional title parameter for better flexibility in conversation duplication.

* chore: rename files
2026-03-13 23:40:44 -04:00
Danny Avila
b8c31e7314
🔱 chore: Harden API Routes Against IDOR and DoS Attacks (#11760)
* 🔧 feat: Update user key handling in keys route and add comprehensive tests

- Enhanced the PUT /api/keys route to destructure request body for better clarity and maintainability.
- Introduced a new test suite for keys route, covering key update, deletion, and retrieval functionalities, ensuring robust validation and IDOR prevention.
- Added tests to verify handling of extraneous fields and missing optional parameters in requests.

* 🔧 fix: Enhance conversation deletion route with parameter validation

- Updated the DELETE /api/convos route to handle cases where the request body is empty or the 'arg' parameter is null/undefined, returning a 400 status with an appropriate error message for DoS prevention.
- Added corresponding tests to ensure proper validation and error handling for these scenarios, enhancing the robustness of the API.

* 🔧 fix: Improve request body validation in keys and convos routes

- Updated the DELETE /api/convos and PUT /api/keys routes to validate the request body, returning a 400 status for null or invalid bodies to enhance security and prevent potential DoS attacks.
- Added corresponding tests to ensure proper error handling for these scenarios, improving the robustness of the API.
2026-02-12 18:08:24 -05:00
Danny Avila
b94388ce9d
🏺 fix: Restore Archive Functionality with Dedicated Endpoint (#11183)
The archive conversation feature was broken after the `/api/convos/update`
route was modified to only handle title updates. The frontend was sending
`{ conversationId, isArchived }` to the update endpoint, but the backend
was only extracting `title` and ignoring the `isArchived` field entirely.

This fix implements a dedicated `/api/convos/archive` endpoint to restore
the archive/unarchive functionality.

Changes:

packages/data-provider/src/api-endpoints.ts:
- Add `archiveConversation()` endpoint returning `/api/convos/archive`

packages/data-provider/src/data-service.ts:
- Update `archiveConversation()` to use dedicated archive endpoint

api/server/routes/convos.js:
- Add `POST /archive` route with validation for `conversationId` (required)
  and `isArchived` (must be boolean)

api/server/routes/__tests__/convos.spec.js:
- Add test coverage for archive endpoint (success, validation, error cases)
2026-01-02 19:41:53 -05:00
Danny Avila
bfc981d736
✍️ fix: Validation for Conversation Title Updates (#11099)
* ✍️ fix: Validation for Conversation Title Updates

* fix: Add validateConvoAccess middleware mock in tests
2025-12-25 12:59:48 -05:00
Danny Avila
ba71375982
🗑️ fix: Delete Shared Links on Conversation Deletion (#10396)
*  feat: Enhance DELETE /all endpoint to remove shared links alongside conversations and tool calls

- Added functionality to delete all shared links for a user when clearing conversations.
- Introduced comprehensive tests to ensure correct behavior and error handling for the new deletion process.

*  feat: Implement deleteConvoSharedLink method and update conversation deletion logic to remove associated shared links
2025-11-06 11:44:28 -05:00