Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
4b49f10
🐛 fix: Add Directory Permissions in Dockerfile.multi
inodb Oct 21, 2025
903d00a
✨ feat: Enable HTML Support in Agent Descriptions (#8291)
inodb Oct 21, 2025
beb686a
Disable PWA service worker using selfDestroying mode
inodb Mar 3, 2026
a68aaba
✨ feat: Add product feedback modal for downvote workflow
Feb 25, 2026
8287243
🔧 refactor: Replace proxy with direct GitHub API call for feedback is…
Feb 25, 2026
021a3ca
🐛 fix: Persist thumbs-down feedback when product feedback modal is su…
Feb 25, 2026
4bb15d0
🔧 refactor: Store full feedback payload as JSON string in existing te…
Feb 26, 2026
82cb1c9
🔧 refactor: Replace GitHub issue creation with MongoDB persistence an…
Mar 6, 2026
f7f9412
🎨 style: Yellow feedback buttons and remove edit/fork buttons
inodb Mar 6, 2026
53d3999
🐛 fix: Extract message text from content array for feedback context
inodb Mar 6, 2026
8b27934
UI improvements v6: feedback modals, agent buttons, CI workflow
inodb Mar 9, 2026
fea3773
Build arm64 only in CI to halve build time
inodb Mar 9, 2026
99e69b3
Fix agent button selector: use handleSelectSpec from context
inodb Mar 9, 2026
f9c81f5
Hide agent buttons after conversation starts, use short names
inodb Mar 9, 2026
cb418ae
Switch CI to native arm64 runner, drop QEMU
inodb Mar 9, 2026
0c8f405
Add agent_id and agent_name to trace metadata
inodb Mar 9, 2026
698b86a
Send user feedback to Langfuse as scores
inodb Mar 9, 2026
0da2710
Use BOOLEAN dataType for Langfuse feedback scores
inodb Mar 9, 2026
b34a337
Fix thumbs-down feedback flow: don't save until modal is submitted
inodb Mar 9, 2026
e1f4f4a
Fix 400 error on feedback submit: remove required validation for titl…
inodb Mar 9, 2026
7235820
Fix: allow undoing thumbs-down feedback by clicking active thumb again
inodb Mar 9, 2026
ec14112
Show feedback summary on click with Delete/Update options
inodb Mar 10, 2026
75af388
Fix ESLint and prettier issues in feedback summary dialog
inodb Mar 11, 2026
13eff68
Fix feedback summary showing empty for thumbs-down
inodb Mar 11, 2026
0a26c04
fix: Persist thumbs up/down feedback state in React Query cache
Mar 13, 2026
692d00b
fix: Allow feedback persistence without a tag selection
Mar 16, 2026
8614a24
Merge pull request #5 from ForisKuang/fix/feedback-persistence
inodb Mar 17, 2026
39fe61f
Skip summary dialog, open submission modals directly with pre-filled …
inodb Mar 12, 2026
d4fe816
Langfuse JSON parsing + toMinimalFeedback fallback
inodb Mar 17, 2026
c4e0f89
Show success toast after thumbs-up feedback submission
inodb Mar 17, 2026
e43bc5b
Use plain text for feedback success toast messages
inodb Mar 18, 2026
b044fe8
Fix agent conversation starters not returned by list endpoint
inodb Mar 19, 2026
f6309d5
Style conversation starters as horizontal pills
inodb Mar 19, 2026
213e55a
Add agent selector and prioritize greeting for agent intro (#6)
ForisKuang Mar 20, 2026
9d0c1db
Add hover tooltips and "Switch Agent" header to agent selector
Mar 25, 2026
f2b3a76
Add "Switch Agent" button to landing page and side-by-side layout wit…
Mar 27, 2026
c42776e
Merge pull request #7 from ForisKuang/feature/agent-selector-tooltip-…
inodb Mar 30, 2026
4f413b3
Activate dormant context summarization for agents endpoint
Mar 31, 2026
45e1683
Fix Anthropic compatibility and config resolution for context summari…
Apr 1, 2026
6f3e7ed
🔍 feat: Pass user ID and session ID to Langfuse for tracing
Apr 8, 2026
c098020
🔍 feat: Use user email as Langfuse userId for easier debugging
Apr 8, 2026
e25dc16
Merge pull request #9 from ForisKuang/feature/agent-context-summariza…
inodb Apr 13, 2026
4364118
Merge remote-tracking branch 'origin/v0.8.3-rc1-custom-v7' into feat/…
inodb Apr 13, 2026
f6cf5ad
Merge pull request #11 from ForisKuang/feat/langfuse-user-id-tracking
inodb Apr 13, 2026
27ae9bb
🩹 fix: Cycle Switch Agent button through all specs and preserve admin…
ForisKuang May 6, 2026
0ed7112
Bump @librechat/agents from 3.1.50 to 3.1.68
inodb May 12, 2026
7263e91
Add unified cBioAgent UI config controls
ForisKuang Jun 2, 2026
5a5e740
Merge cbioagent-unified-ui-config (#14) into v0.8.3-rc1-custom-v9 — d…
inodb Jun 5, 2026
93c7a12
Show cBioAgent examples by default
ForisKuang Jun 8, 2026
c451de0
Merge pull request #15 from ForisKuang/cbioagent-capability-cards-def…
inodb Jun 9, 2026
327396e
feat: add dd-trace for Datadog LLM Observability (#16)
dippindots Jun 10, 2026
c7866ff
fix comma
inodb Jun 10, 2026
7a328c1
chore: update package-lock.json for dd-trace
dippindots Jun 10, 2026
af54732
chore: update package-lock.json for dd-trace
dippindots Jun 10, 2026
8414dad
chore: update package-lock.json for dd-trace
dippindots Jun 10, 2026
27e2a07
Merge pull request #18 from dippindots/fix/dd-trace-lockfile
dippindots Jun 10, 2026
5929d6c
Resolve any Lucide icon by name for conversation-starter categories (…
inodb Jun 17, 2026
c45fa40
Fix Lucide bundle TDZ blanking chat.cbioportal.org (#21)
inodb Jun 17, 2026
f18aa76
Alias Map/Navigation lucide imports to avoid shadowing JS globals (#22)
inodb Jun 17, 2026
37744d9
Lazy-load category icons via lucide-react's dynamicIconImports (#23)
inodb Jun 17, 2026
657379d
Revert PR #23 (lazy lucide) — Vite chunk reshuffle broke mermaid (#24)
inodb Jun 17, 2026
4b323d0
Make cBioAgent landing page scrollable (#19)
ForisKuang Jun 17, 2026
e0dffed
Brand the SPA shell as cBioChat with the cbioportal icon (#25)
inodb Jun 22, 2026
47cae98
Adding toggle thinking reasoning mode visibility
ForisKuang Jun 23, 2026
29b8cbf
chore: ignore dd-trace in depcheck — loaded via NODE_OPTIONS at runtime
ForisKuang Jun 23, 2026
d74d201
fix: prettier formatting in reasoning toggle components
ForisKuang Jun 23, 2026
250ebf8
fix: remove dead code and fix AgentHandoff visibility in reasoning to…
ForisKuang Jul 23, 2026
a4088c3
Merge pull request #28 from cBioPortal/feat/reasoning-actions-toggle
ForisKuang Jul 23, 2026
304bc5c
fix: ensure user id propagates to MCP request headers from session-de…
ForisKuang Jul 21, 2026
b25161c
Merge pull request #29 from ForisKuang/fix/mcp-user-id-header-propaga…
ForisKuang Jul 23, 2026
7263dab
fix(mcp): resolve {{LIBRECHAT_USER_ID}} from _id on the MCP tool-call…
inodb Aug 4, 2026
92ad6ef
fix(mcp): source user id from the agent client when req.user lacks id…
inodb Aug 5, 2026
9f0a442
fix(mcp): resolve {{LIBRECHAT_USER_ID}} in loadTools' MCP tool-loadin…
ForisKuang Aug 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .depcheckrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ignores:
- "dd-trace"
23 changes: 23 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,22 @@ CONSOLE_JSON=false
DEBUG_LOGGING=true
DEBUG_CONSOLE=false

#==================#
# Langfuse Tracing #
#==================#

# Enables Langfuse tracing for agent endpoints via OpenTelemetry.
# All three keys below are required to enable tracing.
# When enabled, traces include user ID and session (thread) tracking.

# LANGFUSE_SECRET_KEY=sk-lf-...
# LANGFUSE_PUBLIC_KEY=pk-lf-...
# LANGFUSE_BASE_URL=https://cloud.langfuse.com

# Optional: Sets the tracing environment label in Langfuse.
# Defaults to NODE_ENV, then 'development' if NODE_ENV is not set.
# LANGFUSE_TRACING_ENVIRONMENT=production

#=============#
# Permissions #
#=============#
Expand Down Expand Up @@ -844,3 +860,10 @@ OPENWEATHER_API_KEY=
# Skip code challenge method validation (e.g., for AWS Cognito that supports S256 but doesn't advertise it)
# When set to true, forces S256 code challenge even if not advertised in .well-known/openid-configuration
# MCP_SKIP_CODE_CHALLENGE_CHECK=false

#======================================#
# Product Feedback #
#======================================#

# Enable the product feedback feature (thumbs-down stores feedback in MongoDB for review)
# PRODUCT_FEEDBACK_ENABLED=true
53 changes: 53 additions & 0 deletions .github/workflows/cbio-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: Build and Push cBioPortal LibreChat Image

on:
push:
branches:
- '**'
workflow_dispatch:
inputs:
tag:
description: 'Image tag (e.g. v0.8.3-rc1-custom-v6). Defaults to branch name.'
type: string
required: false

jobs:
build:
runs-on: ubuntu-24.04-arm
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set image tag
id: tag
run: |
if [ -n "${{ github.event.inputs.tag }}" ]; then
echo "tag=${{ github.event.inputs.tag }}" >> "$GITHUB_OUTPUT"
else
# Use branch name, replacing / with -
BRANCH="${GITHUB_REF_NAME//\//-}"
echo "tag=${BRANCH}" >> "$GITHUB_OUTPUT"
fi

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}

- name: Prepare environment
run: cp .env.example .env

- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
file: Dockerfile
push: true
tags: cbioportal/librechat:${{ steps.tag.outputs.tag }}
platforms: linux/arm64
cache-from: type=gha
cache-to: type=gha,mode=max
3 changes: 3 additions & 0 deletions .github/workflows/unused-packages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,9 @@ jobs:
UNUSED=$(depcheck --json | jq -r '.dependencies | join("\n")' || echo "")
# Exclude dependencies used in scripts, code, and workspace packages
UNUSED=$(comm -23 <(echo "$UNUSED" | sort) <(cat root_used_deps.txt root_used_code.txt root_workspace_deps.txt | sort) || echo "")
# dd-trace is loaded via NODE_OPTIONS=--require dd-trace/init at
# runtime — depcheck can't see that.
UNUSED=$(echo "$UNUSED" | grep -v "^dd-trace$" || echo "")
echo "ROOT_UNUSED<<EOF" >> $GITHUB_ENV
echo "$UNUSED" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
Expand Down
4 changes: 4 additions & 0 deletions Dockerfile.multi
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ COPY --from=data-provider-build /app/packages/data-provider/dist ./packages/data
COPY --from=data-schemas-build /app/packages/data-schemas/dist ./packages/data-schemas/dist
COPY --from=api-package-build /app/packages/api/dist ./packages/api/dist
COPY --from=client-build /app/client/dist ./client/dist
# Create runtime directories with correct permissions
RUN mkdir -p /app/api/logs /app/uploads /app/client/public/images && \
chown -R node:node /app
USER node
WORKDIR /app/api
EXPOSE 3080
ENV HOST=0.0.0.0
Expand Down
30 changes: 25 additions & 5 deletions api/app/clients/BaseClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ class BaseClient {
}

async handleTokenCountMap(tokenCountMap) {
if (this.clientName === EModelEndpoint.agents) {
if (this.clientName === EModelEndpoint.agents && !this.shouldSummarize) {
return;
}
if (this.currentMessages.length === 0) {
Expand Down Expand Up @@ -506,7 +506,7 @@ class BaseClient {
shouldSummarize &&
diff === 1 &&
firstMessage?.summary &&
this.previous_summary.messageId === firstMessage.messageId;
this.previous_summary?.messageId === firstMessage.messageId;

if (diff > 0) {
payload = formattedMessages.slice(diff);
Expand Down Expand Up @@ -537,16 +537,36 @@ class BaseClient {
}

if (usePrevSummary) {
summaryMessage = { role: 'system', content: firstMessage.summary };
const summaryRole = this.clientName === EModelEndpoint.agents ? 'user' : 'system';
const summaryContent = this.clientName === EModelEndpoint.agents
? `[Previous conversation summary]\n${firstMessage.summary}`
: firstMessage.summary;
summaryMessage = { role: summaryRole, content: summaryContent };
summaryTokenCount = firstMessage.summaryTokenCount;
payload.unshift(summaryMessage);
if (this.clientName === EModelEndpoint.agents) {
payload.unshift(
summaryMessage,
{ role: 'assistant', content: 'Understood, I have the context from our previous conversation.' },
);
} else {
payload.unshift(summaryMessage);
}
remainingContextTokens -= summaryTokenCount;
} else if (shouldSummarize && messagesToRefine.length > 0) {
({ summaryMessage, summaryTokenCount } = await this.summarizeMessages({
messagesToRefine,
remainingContextTokens,
}));
summaryMessage && payload.unshift(summaryMessage);
if (summaryMessage) {
if (this.clientName === EModelEndpoint.agents) {
payload.unshift(
summaryMessage,
{ role: 'assistant', content: 'Understood, I have the context from our previous conversation.' },
);
} else {
payload.unshift(summaryMessage);
}
}
remainingContextTokens -= summaryTokenCount;
}

Expand Down
2 changes: 1 addition & 1 deletion api/app/clients/tools/util/handleTools.js
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,7 @@ const loadTools = async ({
/** MCP server tools are initialized sequentially by server */
let index = -1;
const failedMCPServers = new Set();
const safeUser = createSafeUser(options.req?.user);
const safeUser = createSafeUser(options.req?.user, user);
for (const [serverName, toolConfigs] of Object.entries(requestedMCPTools)) {
index++;
/** @type {LCAvailableTools} */
Expand Down
64 changes: 64 additions & 0 deletions api/app/clients/tools/util/handleTools.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,25 @@ jest.mock('~/server/services/Config', () => ({
},
},
}),
getMCPServerTools: jest.fn(),
}));

jest.mock('~/config', () => ({
...jest.requireActual('~/config'),
getMCPServersRegistry: jest.fn(),
}));

jest.mock('~/server/services/MCP', () => ({
...jest.requireActual('~/server/services/MCP'),
createMCPTools: jest.fn(),
}));

const { Calculator } = require('@librechat/agents');
const { Constants } = require('librechat-data-provider');

const { User } = require('~/db/models');
const { getMCPServersRegistry } = require('~/config');
const { createMCPTools } = require('~/server/services/MCP');
const PluginService = require('~/server/services/PluginService');
const { validateTools, loadTools, loadToolWithAuth } = require('./handleTools');
const { StructuredSD, availableTools, DALLE3 } = require('../');
Expand Down Expand Up @@ -283,4 +297,54 @@ describe('Tool Handlers', () => {
delete process.env.SD_WEBUI_URL;
});
});

describe('loadTools MCP user id propagation', () => {
// Regression guard: MCP tool-call requests shipped the literal "{{LIBRECHAT_USER_ID}}"
// placeholder in x-user-id headers because `loadTools` built the MCP `user` object from
// `options.req?.user` alone. When `req.user` is a plain object deserialized from the
// passport session (no `id` virtual, no `_id`), that object resolves to no usable id even
// though `loadTools` is already given the caller's resolved id via its own `user` param
// (see ToolService.js: `loadTools({ user: req.user.id, ... })`). Assert that id makes it
// into the object handed to `createMCPTools`.
const mcpServerName = 'test-mcp-server';
const mcpAllToolName = `${Constants.mcp_all}${Constants.mcp_delimiter}${mcpServerName}`;

beforeEach(() => {
getMCPServersRegistry.mockReturnValue({
getServerConfig: jest.fn().mockResolvedValue({ startup: true }),
});
createMCPTools.mockResolvedValue([]);
});

it('falls back to the loadTools `user` param when req.user has neither id nor _id', async () => {
const userId = fakeUser._id.toString();
const sessionUser = { email: 'fakeuser@example.com', provider: 'local' };

await loadTools({
user: userId,
tools: [mcpAllToolName],
options: { req: { user: sessionUser } },
});

expect(createMCPTools).toHaveBeenCalledWith(
expect.objectContaining({
user: expect.objectContaining({ id: userId }),
}),
);
});

it('prefers a resolvable req.user id over the loadTools `user` param', async () => {
await loadTools({
user: fakeUser._id.toString(),
tools: [mcpAllToolName],
options: { req: { user: { id: 'explicit-req-user-id' } } },
});

expect(createMCPTools).toHaveBeenCalledWith(
expect.objectContaining({
user: expect.objectContaining({ id: 'explicit-req-user-id' }),
}),
);
});
});
});
1 change: 1 addition & 0 deletions api/models/Agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -717,6 +717,7 @@ const getListAgentsByAccess = async ({
category: 1,
support_contact: 1,
is_promoted: 1,
conversation_starters: 1,
}).sort({ updatedAt: -1, _id: 1 });

// Only apply limit if pagination is requested
Expand Down
Loading
Loading