Skip to content

๐Ÿ—‚๏ธ feat: Add Agent File Authoring Tools - #13435

Merged
danny-avila merged 22 commits into
devfrom
danny-avila/skill-file-authoring-tools
Jun 4, 2026
Merged

danny-avila merged 22 commits into
devfrom
danny-avila/skill-file-authoring-tools

Conversation

@danny-avila

Copy link
Copy Markdown
Collaborator

Summary

I added model-invocable create_file and edit_file support for LibreChat agents, covering both skill files and code-execution sandbox files.

  • Registered create_file and edit_file alongside read_file for code-exec-only agents, and upgraded their descriptions when skills are also in scope.
  • Routed skills/... authoring through skill creation, skill updates, ACL checks, bundled file storage, unified diffs, and edit-strategy reporting.
  • Added sandbox file authoring through codeapi-backed read/write callbacks, including overwrite protection, targeted edit support, session artifact forwarding, and attachment persistence for generated files.
  • Added a compatibility shim so host-side file authoring tools receive and store code-session context through the current @librechat/agents runtime.
  • Covered registration, initialization, skill authoring, sandbox authoring, sandbox write transport, and artifact callback behavior with focused tests.

Change Type

  • New feature (non-breaking change which adds functionality)

Testing

  • git diff --check
  • node --check api/server/services/Files/Code/process.js
  • node --check api/server/services/Endpoints/agents/skillDeps.js
  • node --check api/server/controllers/agents/callbacks.js
  • node --check api/server/services/ToolService.js
  • Targeted Jest was attempted but could not run because this worktree has no installed dependencies: jest is not found under packages/api, and cross-env is not found under api.

Test Configuration:

  • Local checkout: /Users/danny/.codex/worktrees/0f71/LibreChat
  • Dependency state: node_modules missing in this worktree

Checklist

  • My code adheres to this project's style guidelines
  • I have performed a self-review of my own code
  • I have commented in any complex areas of my code
  • I have written tests demonstrating that my changes are effective or that my feature works

Copilot AI review requested due to automatic review settings May 31, 2026 23:01

Copy link
Copy Markdown
Collaborator Author

@codex review

2 similar comments

Copy link
Copy Markdown
Collaborator Author

@codex review

Copy link
Copy Markdown
Collaborator Author

@codex review

@github-actions

Copy link
Copy Markdown
Contributor

GitNexus: ๐Ÿš€ deployed

The LibreChat-pr-13435 index is now live on the MCP server.
Deploy run

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

๐Ÿ’ก Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bcea08f68c

โ„น๏ธ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with ๐Ÿ‘.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

...(author.tenantId ? { tenantId: author.tenantId } : {}),
});
try {
await options.grantSkillOwner({ req, skillId: result.skill._id });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep newly created skills resolvable in the same run

When create_file creates skills/foo/SKILL.md, the current run's mergedConfigurable.accessibleSkillIds was computed before this skill existed, and resolveSkillForAuthoring later resolves bundled files only through that stale ID list. As a result, a model that creates a new skill and then tries to add skills/foo/references/... or scripts/... before the next request will get โ€œSkill not found or not accessibleโ€ even though ownership was just granted here; update the in-run accessible/primed state or otherwise pass the new skill through for subsequent authoring calls.

Useful? React with ๐Ÿ‘ย / ๐Ÿ‘Ž.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 144a2cc and the earlier follow-up commits. Newly created skills are now remembered in the in-run accessible/primed/active state so later same-batch bundled-file authoring resolves the just-created skill.

Comment thread packages/api/src/agents/initialize.ts Outdated
);
}

if (effectiveCodeEnvAvailable || hasSkillAccess) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Register skill authoring for first-time creators

This gates create_file/edit_file registration on hasSkillAccess, which is derived from non-empty VIEW-accessible skill IDs, not from the SKILLS CREATE permission checked later by canCreateSkill. In a fresh workspace or for a user who can create skills but cannot yet view any, a skills-enabled agent without code execution will never expose create_file, so the user cannot create their first skill through this feature despite passing the create-permission check if the handler were reachable.

Useful? React with ๐Ÿ‘ย / ๐Ÿ‘Ž.

Comment thread packages/api/src/agents/handlers.ts Outdated
return errorResult(tc, `Skill "${parsed.skillName}" not found or not accessible.`);
}

const current = await loadSkillFileTextForAuthoring({

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Allow overwriting large bundled skill files

For create_file with overwrite: true on an existing bundled skill file, this preloads the old file through loadSkillFileTextForAuthoring, which rejects any stored file over the 512KB cache limit before saveSkillFileContent is reached. That makes full rewrites of existing 513KBโ€“10MB reference/script files impossible even though create_file accepts new content up to MAX_AUTHORING_BYTES; the overwrite path should not require reading the whole old file just to replace it or build a diff.

Useful? React with ๐Ÿ‘ย / ๐Ÿ‘Ž.

Comment thread packages/api/src/agents/handlers.ts Outdated
Comment on lines +2485 to +2486
tc.name === CREATE_FILE_TOOL_NAME ||
tc.name === EDIT_FILE_TOOL_NAME

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Serialize file authoring calls per path

By adding create_file/edit_file to the generic Promise.all tool batch, two tool calls in the same model turn that target the same skill file can both read the old contents and then save whole-file replacements; whichever finishes last silently drops the other edit. Since the tools already accept an edits array for batching, route same-path authoring calls serially or reject concurrent same-path operations to avoid lost updates.

Useful? React with ๐Ÿ‘ย / ๐Ÿ‘Ž.

Copy link
Copy Markdown
Collaborator Author

@codex review

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@github-actions

Copy link
Copy Markdown
Contributor

GitNexus: ๐Ÿš€ deployed

The LibreChat-pr-13435 index is now live on the MCP server.
Deploy run

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

๐Ÿ’ก Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cbf865350e

โ„น๏ธ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with ๐Ÿ‘.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/api/src/agents/initialize.ts Outdated
);
}

if (effectiveCodeEnvAvailable || hasSkillAccess) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Register skill authoring for first-skill creation

When the skills capability is enabled but the user has CREATE permission and no VIEWable skills yet, accessibleSkillIds is empty, hasSkillAccess is false, and code execution may also be disabled, so this branch never registers create_file. The execution path below can create a new skills/{name}/SKILL.md after canCreateSkill, but the model is never offered the tool, which blocks bootstrapping a user's first skill from chat.

Useful? React with ๐Ÿ‘ย / ๐Ÿ‘Ž.

Comment on lines +1519 to +1523
update: {
body: content,
description: parsedUpdate.description,
...(parsedUpdate.alwaysApply !== undefined ? { alwaysApply: parsedUpdate.alwaysApply } : {}),
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Pass structured frontmatter when updating SKILL.md

When edit_file rewrites an existing SKILL.md containing runtime frontmatter like disable-model-invocation, user-invocable, or allowed-tools, this update only forwards body, description, and alwaysApply. updateSkill derives those runtime columns only from update.frontmatter, so the saved file can show new frontmatter while catalog visibility/tool-invocation behavior continues using the old values.

Useful? React with ๐Ÿ‘ย / ๐Ÿ‘Ž.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 144a2cc. create_file/edit_file now parse and pass structured SKILL.md frontmatter through createSkill/updateSkill, including runtime fields like disable-model-invocation, user-invocable, allowed-tools, and always-apply; handlers.spec covers the edit path.

if (skill && !overwrite) {
return errorResult(tc, 'File already exists. Pass overwrite: true to replace.');
}
return await writeSkillMd({

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Gate skill creation when only code authoring is enabled

When create_file is registered solely because code execution is available and skills are not in scope, a tool call whose path starts with skills/ still reaches writeSkillMd; for a missing skill this creates a persistent skill after only the role-level CREATE check, even if the current agent/run did not enable skills. This makes code-only authoring capable of mutating the skill store instead of treating the path as a sandbox file or rejecting it.

Useful? React with ๐Ÿ‘ย / ๐Ÿ‘Ž.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 144a2cc. skills/... authoring now requires the per-run skillAuthoringAvailable gate; code-exec-only agents keep sandbox create/edit support but reject skill-store writes. Added a regression test for the code-only skills/ path.

Comment on lines +213 to +217
Paths starting with "skills/" target the skill file system:
- skills/{skillName}/SKILL.md - the skill's main instruction file
- skills/{skillName}/references/{file} - supporting reference files
- skills/{skillName}/scripts/{file} - helper scripts
- skills/{skillName}/templates/{file} - output templates

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Align authored skill paths with read_file

These paths are returned and advertised as the skill-file namespace, but the existing read_file handler still parses skill files as {skillName}/{path} and treats the first segment as the skill name. After create_file returns skills/foo/SKILL.md, a natural follow-up read_file on that path looks for a skill named skills or falls back to the sandbox, so the model cannot inspect the file it just authored using the same path.

Useful? React with ๐Ÿ‘ย / ๐Ÿ‘Ž.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 144a2cc. read_file now accepts the authored skills/{skillName}/... namespace, keeps explicit skills/ paths out of sandbox fallback when skills are unavailable, and the tool description advertises the namespace.

return await getSkillByName(skillName, [new Types.ObjectId(primedIdString)], {});
}

return await getSkillByName(skillName, accessibleIds, { preferModelInvocable: true });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Block authoring hidden skills unless primed

For an active skill whose only matching document has disable-model-invocation: true, this lookup still returns that hidden skill via the preferModelInvocable fallback, and the authoring path never applies the rejection gate that skill and read_file use. A model that guesses the name can call create_file with overwrite: true or edit_file against skills/{name}/SKILL.md, modifying the hidden skill and even receiving a diff containing the old body.

Useful? React with ๐Ÿ‘ย / ๐Ÿ‘Ž.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 144a2cc. File authoring now rejects disable-model-invocation skills unless the exact skill was primed/authored in the current run, matching the read_file gate. Added tests for blocked and primed-hidden authoring.

Comment thread api/server/services/ToolService.js Outdated
Comment on lines +1390 to +1391
'create_file',
'edit_file',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid shadowing user tools named create_file

If an action/MCP/custom tool already uses create_file or edit_file, registering those names as unconditional special tools prevents loadToolsForExecution from loading the user's tool, while createToolExecuteHandler also intercepts calls with those names as file-authoring requests. In that collision case the model can see the existing tool definition but calls fail with file-authoring validation instead of invoking the configured tool.

Useful? React with ๐Ÿ‘ย / ๐Ÿ‘Ž.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 144a2cc. Host file authoring is now tracked via fileAuthoringToolNames from the registered LibreChat definitions, ToolService only treats those host definitions as special, and the execute handler falls back to loaded user tools named create_file/edit_file when host authoring is not active.

Copy link
Copy Markdown
Collaborator Author

@codex review

2 similar comments

Copy link
Copy Markdown
Collaborator Author

@codex review

Copy link
Copy Markdown
Collaborator Author

@codex review

@github-actions

Copy link
Copy Markdown
Contributor

GitNexus: ๐Ÿš€ deployed

The LibreChat-pr-13435 index is now live on the MCP server.
Deploy run

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

๐Ÿ’ก Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4e422e0c2d

โ„น๏ธ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with ๐Ÿ‘.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +386 to +392
computeAccessibleSkillIds: (handoffAgent) =>
resolveAgentScopedSkillIds({
agent: handoffAgent,
accessibleSkillIds,
skillsCapabilityEnabled,
ephemeralSkillsToggle,
}),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Thread sub-agent skill ids into execution

When a handoff agent has a different skill scope than the primary, this new initialization gives it a catalog/tool definitions based on scopedSkillIds, but the OpenAI route still stores no config.accessibleSkillIds for the handoff context and loadTools later calls enrichWithSkillConfigurable(..., primaryConfig.accessibleSkillIds, ...). As a result, sub-agent skill/read_file/create_file/edit_file calls resolve against the primary agent's skill set rather than the catalog the sub-agent was shown, so its own skill file calls can fail and primary-scoped skills can be reachable from the handoff.

Useful? React with ๐Ÿ‘ย / ๐Ÿ‘Ž.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 144a2cc. The OpenAI execution context now stores each agent context accessibleSkillIds/activeSkillNames/skillAuthoringAvailable/fileAuthoringToolNames and enriches tool execution from the current ctx instead of always using the primary config.

Comment on lines +516 to +522
computeAccessibleSkillIds: (handoffAgent) =>
resolveAgentScopedSkillIds({
agent: handoffAgent,
accessibleSkillIds,
skillsCapabilityEnabled,
ephemeralSkillsToggle,
}),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Thread sub-agent skill ids into execution

This initializes Responses handoff agents with their own scoped skill ids, but the execution context saved in agentToolContexts does not keep config.accessibleSkillIds/activeSkillNames, and both Responses loadTools closures still pass primaryConfig.accessibleSkillIds to enrichWithSkillConfigurable. For a handoff whose skill scope differs from the primary, the model sees the handoff's catalog while tool execution resolves file-authoring and skill reads against the primary's skills, causing valid handoff skill calls to fail or primary-scoped skills to be reachable from the sub-agent.

Useful? React with ๐Ÿ‘ย / ๐Ÿ‘Ž.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 144a2cc. Responses now preserves handoff-agent accessibleSkillIds/activeSkillNames and threads them through both loadTools closures, with the primary config only used as fallback.

return null;
}

return await getSkillByName(skillName, accessibleIds, { preferModelInvocable: true });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject authoring for model-disabled skills

When the only accessible skill with this name has disableModelInvocation: true, getSkillByName(..., { preferModelInvocable: true }) still falls back to that disabled document (per packages/data-schemas/src/methods/skill.ts), and the create/edit handlers never apply the explicit rejection that skill and read_file do. If the model learns the name of a manual-only skill and the user has edit permission, it can now overwrite skills/<name>/SKILL.md or bundled files through these model-invocable tools despite the skill being hidden from autonomous model invocation.

Useful? React with ๐Ÿ‘ย / ๐Ÿ‘Ž.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 144a2cc. create_file/edit_file now reject disable-model-invocation skills unless the exact skill is primed/authored in the current run. Added handler tests for blocked hidden authoring and allowed primed-hidden authoring.

Comment on lines +1608 to +1612
update: {
body: content,
description: parsedUpdate.description,
...(parsedUpdate.alwaysApply !== undefined ? { alwaysApply: parsedUpdate.alwaysApply } : {}),
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Pass SKILL.md frontmatter into skill writes

Tool-authored SKILL.md saves only pass body, description, and alwaysApply, but updateSkill/createSkill derive persisted fields like disableModelInvocation, userInvocable, and allowedTools only from the structured frontmatter argument. When create_file or edit_file adds or removes those frontmatter keys, the visible SKILL.md changes while the indexed columns used by catalog filtering and tool gating stay stale, so a skill that declares disable-model-invocation: true can still be model-invoked (or an old restriction can remain after removal).

Useful? React with ๐Ÿ‘ย / ๐Ÿ‘Ž.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 144a2cc. Tool-authored SKILL.md create/update now parses the YAML frontmatter with js-yaml and passes the structured frontmatter bag to createSkill/updateSkill so derived runtime columns stay in sync.

@danny-avila

Copy link
Copy Markdown
Collaborator Author

@codex review

@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

GitNexus: ๐Ÿš€ deployed

The LibreChat-pr-13435 index is now live on the MCP server.
Deploy run

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

๐Ÿ’ก Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 144a2cc120

โ„น๏ธ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with ๐Ÿ‘.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +681 to +683
codeEnvAvailable: config.codeEnvAvailable,
skillPrimedIdsByName: buildSkillPrimedIdsByName(
config.manualSkillPrimes,
config.alwaysApplySkillPrimes,
),
skillPrimedIdsByName:
buildSkillPrimedIdsByName(config.manualSkillPrimes, config.alwaysApplySkillPrimes) ?? {},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Pass authoring context to explicit subagents

For subagents loaded through this loadAgentById path (subagentAgentConfigs, rather than discovery), the initializeAgent call above now receives skillAuthoringAvailable, but the resulting agentToolContexts entry still omits both skillAuthoringAvailable and fileAuthoringToolNames. When that subagent later calls create_file/edit_file, loadTools enriches with ctx.skillAuthoringAvailable === true and ctx.fileAuthoringToolNames; both are missing, so the host file-authoring handler is not activated and the registered special tool resolves as Tool create_file not found (or skill authoring is incorrectly rejected). Copy the same two fields here as in the primary/discovery context entries.

Useful? React with ๐Ÿ‘ย / ๐Ÿ‘Ž.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d7b0a51. Explicit subagent contexts in initialize.js now carry skillAuthoringAvailable and fileAuthoringToolNames along with the existing skill scope fields.

}
throw error;
}
rememberAuthoredSkill([mergedConfigurable, sourceConfigurable], result.skill);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve newly authored skills across tool rounds

This records a newly created skill only in the current handler's transient configurables. On the next ON_TOOL_EXECUTE round, loadTools re-enriches from the per-agent agentToolContexts and mergedConfigurable = { ...configurable, ...toolConfigurable } lets that stale tool configurable overwrite the runtime accessibleSkillIds/skillPrimedIdsByName/activeSkillNames that were mutated here. As a result, a model can successfully create skills/foo/SKILL.md but then fail to read_file/create_file under that skill after it observes the tool result and issues a follow-up tool call; persist the authored skill into the context used by loadTools, or merge without clobbering authored entries.

Useful? React with ๐Ÿ‘ย / ๐Ÿ‘Ž.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d7b0a51. Tool execution now merges runtime skill state instead of letting freshly loaded configurables clobber authored accessibleSkillIds/skillPrimedIdsByName/activeSkillNames, with a regression covering create-then-read in a later tool round.

AgentConstants.BASH_TOOL,
AgentConstants.SKILL_TOOL,
AgentConstants.READ_FILE,
...fileAuthoringToolNames,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep file authoring executable under PTC

When programmatic tool calling is active, adding create_file/edit_file to specialToolNames removes them from allToolNamesToLoad, so they are never placed in configurable.ptcToolMap. However createToolExecuteHandler still forwards registry definitions other than PTC/search to the PTC tool, so code-exec agents that rely on programmatic_tool_calling can be shown create_file/edit_file definitions with no executable map entry, causing those calls to fail inside PTC. Either include host authoring tools in the PTC map or filter their definitions out of the PTC toolDefs.

Useful? React with ๐Ÿ‘ย / ๐Ÿ‘Ž.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d7b0a51. PTC toolDefs now filter out host file-authoring definitions when those names are handled by the host, so PTC is not shown create_file/edit_file entries that are absent from ptcToolMap. Added coverage in the PTC config test.

@danny-avila

Copy link
Copy Markdown
Collaborator Author

@codex review

1 similar comment
@danny-avila

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

๐Ÿ’ก Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c9b3785717

โ„น๏ธ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with ๐Ÿ‘.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread api/server/controllers/agents/openai.js Outdated
tool_resources: config.tool_resources,
actionsEnabled: config.actionsEnabled,
accessibleSkillIds: config.accessibleSkillIds,
activeSkillNames: config.activeSkillNames,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve sub-agent primed skill IDs

When a handoff agent has an always-apply/manual skill that is not in the primary agent's primed set (for example a hidden disable-model-invocation skill or one outside the catalog cap), adding activeSkillNames here makes handleReadFileCall take the active-name shortcut, but the context never stores that sub-agent's buildSkillPrimedIdsByName(config.manualSkillPrimes, config.alwaysApplySkillPrimes) and loadTools still passes the primary map. In that scenario the skill is treated as unprimed, so read_file/authoring calls can be misrouted or denied despite the sub-agent already having the skill body in context.

Useful? React with ๐Ÿ‘ย / ๐Ÿ‘Ž.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 6e1806f. OpenAI sub-agent tool contexts now store the sub-agent primed skill ID map and pass that map into enrichWithSkillConfigurable during tool execution, so hidden/always-apply skills primed for the handoff agent remain readable/authorable. Added an OpenAI controller regression covering a sub-agent-specific primed map.

tool_resources: config.tool_resources,
actionsEnabled: config.actionsEnabled,
accessibleSkillIds: config.accessibleSkillIds,
activeSkillNames: config.activeSkillNames,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve sub-agent primed skill IDs

This Responses sub-agent context has the same priming gap: activeSkillNames is stored, but the sub-agent's own primed ID map is not, while the tool loader later uses the primary skillPrimedIdsByName. For handoff agents with always-apply/manual skills not primed by the primary, handleReadFileCall sees the name as unprimed and can reject hidden primed skills or route capped primed skills away from the skill file system.

Useful? React with ๐Ÿ‘ย / ๐Ÿ‘Ž.

@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

GitNexus: ๐Ÿš€ deployed

The LibreChat-pr-13435 index is now live on the MCP server.
Deploy run

@danny-avila

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Another round soon, please!

โ„น๏ธ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with ๐Ÿ‘.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

GitNexus: ๐Ÿš€ deployed

The LibreChat-pr-13435 index is now live on the MCP server.
Deploy run

@danny-avila
danny-avila marked this pull request as ready for review June 1, 2026 22:51
@danny-avila
danny-avila changed the base branch from main to dev June 1, 2026 22:51
@danny-avila

Copy link
Copy Markdown
Collaborator Author

@codex review

1 similar comment
@danny-avila

Copy link
Copy Markdown
Collaborator Author

@codex review

@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

GitNexus: ๐Ÿš€ deployed

The LibreChat-pr-13435 index is now live on the MCP server.
Deploy run

@danny-avila

Copy link
Copy Markdown
Collaborator Author

@codex review

@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

GitNexus: ๐Ÿš€ deployed

The LibreChat-pr-13435 index is now live on the MCP server.
Deploy run

@danny-avila

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Breezy!

โ„น๏ธ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with ๐Ÿ‘.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

GitNexus: ๐Ÿš€ deployed

The LibreChat-pr-13435 index is now live on the MCP server.
Deploy run

@danny-avila

Copy link
Copy Markdown
Collaborator Author

@codex review

@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

GitNexus: ๐Ÿš€ deployed

The LibreChat-pr-13435 index is now live on the MCP server.
Deploy run

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

๐Ÿ’ก Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 47b324f6e9

โ„น๏ธ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with ๐Ÿ‘.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/api/src/agents/handlers.ts Outdated
const parsed = parseFrontmatter(content);
const structured = parseStructuredSkillFrontmatter(content);
return {
description: parsed.description,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve block-scalar skill descriptions

When a tool-authored SKILL.md uses valid YAML block/folded syntax for description (for example description: |- followed by indented lines), parseStructuredSkillFrontmatter already has the real string, but this line persists parseFrontmatter(content).description instead. That narrow parser only reads same-line scalars, so the catalog description becomes the literal marker such as |-/>-, which makes the skill's searchable/triggering description wrong after create or edit. Prefer the structured frontmatter description when it is a string, falling back to the scalar parser only for simple cases.

Useful? React with ๐Ÿ‘ย / ๐Ÿ‘Ž.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 76b28b4. SKILL.md create/update now prefers the structured YAML frontmatter description when available, so block/folded descriptions persist as the actual multiline string instead of the scalar marker. Covered by handlers.spec create and edit regressions for block-scalar descriptions.

const fileName = filePath.split('/').pop() || filePath;
const fileLang = useMemo(() => langFromPath(filePath), [filePath]);
const outputIsDiff = hasDiff(output);
const preview = isCreate ? authoredContent || output : output || editArgsPreview;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Prefer completed create_file output over full args

After a create_file call completes, this still renders authoredContent before the concise tool output. Since the backend accepts up to 10 MB of content, a successful creation of a large file will keep syntax-highlighting and mounting the entire request payload in the chat transcript even though the tool returned only a summary/diff, which can stall the UI whenever large generated files are created. Use the full content only while the call is in progress, or cap/truncate it once output is available.

Useful? React with ๐Ÿ‘ย / ๐Ÿ‘Ž.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 76b28b4. Completed create_file calls now render the concise tool output before the request content, with the full authored content reserved for in-progress calls that do not have output yet. Covered by FileAuthoringCall.test.tsx.

if (!req || !options.getAuthorSkillByName) {
return null;
}
const skill = await options.getAuthorSkillByName({ req, name: skillName });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep author recovery within the agent scope

In a persisted agent that has skills_enabled on but an allowlist that excludes one of the user's other skills, an explicit skills/{excluded}/... path misses accessibleSkillIds and then this fallback loads the same-author skill anyway. Because the follow-up edit path only checks the user's resource ACL, the model can read or modify skills the agent configuration intentionally did not enable; this recovery path should be limited to skills created/primed in the current run or otherwise re-check the agent-scoped IDs.

Useful? React with ๐Ÿ‘ย / ๐Ÿ‘Ž.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 76b28b4. Same-author recovery is now limited to skills already known to the current run via priming or the active skill catalog, and read_file only uses that recovery after a scoped lookup miss. Added regressions for excluded-scope recovery, stale catalog recovery, and hidden-skill recovery not becoming primed.

Comment on lines +922 to +928
if (effectiveCodeEnvAvailable || skillAuthoringAvailable) {
const fileAuthoringResult = registerFileAuthoringTools({
toolRegistry,
toolDefinitions,
includeSkillFileInstructions: skillAuthoringAvailable,
});
toolDefinitions = fileAuthoringResult.toolDefinitions;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Expose read_file for create-only skill runs

When skillAuthoringAvailable is true only because the user can create skills and the agent has no scoped skills yet, this block registers create_file/edit_file but no skill-aware read_file (that is only added via code execution or injectSkillCatalog). In that first-authoring scenario the model can create a skill, but cannot inspect the created or duplicate same-author SKILL.md that the authoring handlers explicitly support; register read_file whenever skill authoring is enabled, even without an initial catalog.

Useful? React with ๐Ÿ‘ย / ๐Ÿ‘Ž.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 76b28b4. initializeAgent now registers skill-aware read_file whenever skill authoring is available, including first-skill/create-only runs without code execution. Updated initializer coverage to assert read_file/create_file/edit_file are all exposed in that path.

@danny-avila

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

๐Ÿ’ก Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 76b28b4e76

โ„น๏ธ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with ๐Ÿ‘.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +113 to +116
return (
scopedSkillIds.length > 0 ||
(skillCreateAllowed === true &&
isAgentSkillsEnabledForRun({ agent, skillsCapabilityEnabled, ephemeralSkillsToggle }))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Gate authoring tools on write permission

When a user only has VIEW access to a shared skill, scopedSkillIds is still non-empty because the IDs are loaded with requiredPermissions: PermissionBits.VIEW, so this branch marks skillAuthoringAvailable true and registers create_file/edit_file. Every actual write for that user will then fail later in ensureCanCreateSkill/ensureCanEditSkill, leaving the model with tools that cannot succeed in read-only skill contexts. Consider separating read-file availability from authoring availability, or requiring create permission/an EDIT-capable skill before exposing the authoring tools.

Useful? React with ๐Ÿ‘ย / ๐Ÿ‘Ž.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 53befd6. Skill file authoring now requires skill CREATE permission or an EDIT-scoped skill for the current agent; VIEW-scoped skills still drive catalog/read_file access. The shared canAuthorSkillFiles helper is centralized in skillDeps and the classic/OpenAI/Responses paths now compute VIEW and EDIT scopes separately. Covered by package initializer coverage plus initialize.js/OpenAI/Responses focused tests.

@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

GitNexus: ๐Ÿš€ deployed

The LibreChat-pr-13435 index is now live on the MCP server.
Deploy run

@danny-avila

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Can't wait for the next one!

โ„น๏ธ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with ๐Ÿ‘.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

GitNexus: ๐Ÿš€ deployed

The LibreChat-pr-13435 index is now live on the MCP server.
Deploy run

@danny-avila
danny-avila merged commit 1da789b into dev Jun 4, 2026
18 checks passed
@danny-avila
danny-avila deleted the danny-avila/skill-file-authoring-tools branch June 4, 2026 03:58
fuuuzzy pushed a commit to fuuuzzy/LibreChat that referenced this pull request Jun 4, 2026
* feat: add agent file authoring tools

* style: format file authoring changes

* style: satisfy file authoring prettier

* test: fix file authoring initialization expectations

* fix: complete skill file authoring flow

* fix: pass skill authoring state on edit

* test: mock missing bundled skill file

* fix: harden agent file authoring gates

* fix: preserve file authoring runtime context

* test: fix authoring context mock typing

* fix: preserve subagent skill primes

* test: avoid array at in handler spec

* refactor: deepen skill authoring runtime wiring

* fix: address codex authoring review findings

* test: fix authoring collision fixture type

* test: add skill file authoring mock e2e

* fix: Improve skill file authoring recovery

* fix: Show file authoring args while running

* fix: Clarify skill rename authoring errors

* fix: Keep code-only file authoring schemas sandbox scoped

* fix: Address skill authoring review findings

* fix: Gate skill authoring on write access
ThomasVuNguyen pushed a commit to ThomasVuNguyen/LibreChat that referenced this pull request Jul 15, 2026
* feat: add agent file authoring tools

* style: format file authoring changes

* style: satisfy file authoring prettier

* test: fix file authoring initialization expectations

* fix: complete skill file authoring flow

* fix: pass skill authoring state on edit

* test: mock missing bundled skill file

* fix: harden agent file authoring gates

* fix: preserve file authoring runtime context

* test: fix authoring context mock typing

* fix: preserve subagent skill primes

* test: avoid array at in handler spec

* refactor: deepen skill authoring runtime wiring

* fix: address codex authoring review findings

* test: fix authoring collision fixture type

* test: add skill file authoring mock e2e

* fix: Improve skill file authoring recovery

* fix: Show file authoring args while running

* fix: Clarify skill rename authoring errors

* fix: Keep code-only file authoring schemas sandbox scoped

* fix: Address skill authoring review findings

* fix: Gate skill authoring on write access
omarchouikha-goreply added a commit to go-reply-de/go-genai-studio that referenced this pull request Jul 30, 2026
* ๐Ÿ›‚ fix: Gate RUM Proxy Route on the RUM_ENABLED Flag (#13475)

* โšก refactor: Change Minifier from 'terser' to 'oxc' in Vite Config (#13476)

* ๐Ÿ—ƒ๏ธ feat: Retain Agent Files During All-Data Retention (#13477)

* feat: add agent file retention exemption

* refactor: centralize agent file retention policy

* ๐Ÿฉป refactor: Replace Opaque OAuth Errors with Structured Failure Diagnostics (#13471)

* Improve OAuth failure logging

* Improve OAuth failure logging

* test: type oauth failure request helper

* refactor: move OpenID callback helper to api package

* ๐ŸŽญ feat: Add Credential-Free Playwright Smoke Suite with a Local Mock LLM (#13472)

* ๐Ÿงช feat: add e2e playwright tests

* ๐Ÿงช feat: Add Playwright Recording Harness

* test: fix mock playwright config

* test: harden mock e2e environment

* test: preserve mock dotenv secrets

* test: harden mock isolation setup

* ci: cache mock e2e builds

* test: harden e2e cache and recorder checks

* test: preserve data-provider exports in oauth route test

* test: isolate mock auth logout state

* test: allow isolated logout smoke setup

* test: prepare logout smoke auth via api

* test: isolate oauth route module mock

---------

Co-authored-by: Danny Avila <danny@librechat.ai>

* โšก feat: Immediate Conversation Title Generation (#13395)

* โšก feat: Immediate Conversation Title Generation

Generate conversation titles as soon as the request is made (in parallel
with the response, from the user's first message) as the new default,
fixing the #13318 race where a transient /gen_title 404 left new chats
stuck on "New Chat".

- Add per-endpoint `titleTiming` ('immediate' | 'final') to baseEndpointSchema;
  `endpoints.all` acts as the global default, unset = immediate. Resolve via
  a new `resolveTitleTiming` helper (`all` takes precedence).
- Fire title generation in parallel with `sendMessage`; `titleConvo` waits
  (bounded, abortable) for the agent run and titles from the user input only.
  Persist after the conversation row exists; defer `disposeClient` until the
  title settles.
- Expose `titleGenerationTiming` via startup config; `useTitleGeneration`
  fetches eagerly in immediate mode with a bounded 404 retry and never treats
  a transient 404 as final. Skip title queueing for temporary conversations.
- Supersedes #13329 while incorporating its bounded 404-retry.

* ๐Ÿฉน fix: Address Copilot review findings on title timing

- Guard against an undefined conversationId in addTitle (skip + warn) so the
  gen_title cache key can't collide as `userId-undefined` and saveConvo is
  never called without a conversationId.
- Gate the title `useQueries` on `enabled` so no /gen_title request fires while
  unauthenticated (e.g. after logout) even if the module queue holds IDs.
- Drop the stale `conversationId` param from the titleConvo JSDoc.
- Add a regression test for the undefined-conversationId guard.

* ๐Ÿงต fix: Harden immediate-title edge cases from codex review

- Cancel in-flight immediate title generation when the request aborts: thread
  job.abortController.signal through addTitle so pressing Stop on a new chat
  neither consumes the title model nor surfaces a title for a cancelled turn.
- Preserve a locally-applied title when the final SSE event's conversation
  carries no title yet (built before the title was saved), so long immediate-mode
  responses no longer revert the chat to "New Chat" until reload.
- Guarantee one full post-completion gen_title fetch cycle before giving up, so a
  `final`-mode title (generated only after the stream ends) is still fetched under
  a global `immediate` default instead of being stranded.
- Add regression tests for the abort propagation and the undefined-conversationId guard.

* ๐Ÿ” fix: Correct title abort, post-completion refetch, and replacement ordering

Follow-up to codex review of the immediate-title fixes:

- Use a dedicated title AbortController instead of `job.abortController`. The
  latter is also aborted by `completeJob` on *successful* completion, which
  cancelled any title slower than a short response. The title is now cancelled
  only on a real user Stop or when the stream is replaced; a completed-then-
  aborted title is discarded (no save, cache cleared) rather than persisted.
- Reset (not remove) the post-completion title query: `resetQueries` refetches
  the mounted observer with a fresh retry budget, whereas `removeQueries` left it
  stuck in its error state, so the promised post-completion cycle never ran.
- Run the job-replacement check before resolving `convoReady`, and on a replaced
  stream cancel/discard the stale title so a discarded prompt can't persist a title.

* ๐Ÿงท fix: Tighten title abort ordering and endpoint-level timing resolution

Follow-up to codex review:

- Abort the title controller before resolving `convoReady` on a stopped turn, so
  the title task can't resume and persist before the later abort.
- Cancel the title and unblock its waits on ANY send failure (not just user
  aborts): a preflight/quota failure before the run exists otherwise hangs
  `_waitForRun`, deferring client disposal until the 45s title timeout.
- Resolve `titleTiming` for custom endpoints via `getCustomEndpointConfig`
  (their config lives under `endpoints.custom[]`, not `endpoints[endpoint]`).
- Derive the startup `titleGenerationTiming` via `resolveTitleTiming` for the
  agents endpoint so an endpoint-level `final` (without `endpoints.all`) is honored
  client-side instead of defaulting to immediate and burning eager gen_title polls.

* ๐Ÿชข fix: Per-agent title timing and safer abort/replacement handling

Follow-up to codex review:

- Resolve `titleTiming` from the agent's actual endpoint after initialization, so a
  per-endpoint `final` override on a custom/provider endpoint backing an (ephemeral)
  agent is honored instead of always using the `agents` endpoint's value.
- Don't preserve a locally-fetched title on a stopped (unfinished) turn: the server
  cancels and discards that title, so keeping it client-side would diverge from
  server state and leave the stopped chat titled until reload.
- On abort/replacement, only delete the cached title if it still holds THIS task's
  value โ€” a replacement stream shares the `userId-conversationId` key and may have
  already cached its own valid title that must not be removed.

* ๐Ÿชž fix: Mirror AgentClient title-config resolution for titleTiming

Per maintainer guidance, keep titleTiming resolution identical to how
`AgentClient#titleConvo` already resolves the endpoint config โ€” `endpoints.all`
is the intended global override and the agent's actual provider endpoint is used:

- Resolve via `endpoints.all ?? endpoints[endpoint] ?? getProviderConfig(endpoint)
  .customEndpointConfig` (was using `getCustomEndpointConfig` directly). Going
  through `getProviderConfig` picks up its case-insensitive fallback for normalized
  provider names (e.g. `openrouter` โ†’ `OpenRouter`), so a custom endpoint's
  `titleTiming` is honored like its other title settings.
- Add `titleTiming` to the Azure endpoint schema `.pick()` so
  `endpoints.azureOpenAI.titleTiming` is no longer silently stripped by Zod.

Note: per-endpoint title settings being skipped when `endpoints.all` is present is
the existing, intended global-override behavior โ€” not changed here.

* ๐Ÿงช test: Cover useTitleGeneration effect logic (integration)

Adds a deterministic white-box integration test that drives the real hook's
React effects with a controllable react-query surface, locking down the
stateful decisions that previously had no coverage:

- immediate mode fetches a queued conversation while its stream is still active
- final mode gates until the stream completes, then becomes eligible
- success applies the fetched title to the conversation caches
- a 404 while active defers (removeQueries) instead of giving up
- a 404 after completion forces a fresh fetch via resetQueries (post-completion remount)

* feat: Stream immediate title events

* style: Format title SSE handler

* test: Preserve data-provider exports in OAuth mock

* test: Isolate OAuth route API mock

* test: Keep OAuth callback factory capture

* fix: Replay streamed title events on resume

* fix: Honor agents title timing precedence

* style: Format title timing fixes

* ๐Ÿงญ ci: Use System Chrome for Mock E2E (#13481)

* ๐ŸŒ i18n: Update translation.json with latest translations (#13482)

* ๐Ÿท๏ธ fix: Prevent Bedrock Cache Tokens from Inflating Completion Count (#13468)

* ๐Ÿ› fix: prevent Bedrock cache tokens from inflating completion count

* style: fix prettier formatting

* ๐Ÿ›ฐ๏ธ feat: Add Auth Fallback Observability (#13488)

* feat: add auth fallback observability

* refactor: move auth log helpers to api package

* test: harden auth log context handling

* fix: keep auth logs low cardinality

* fix: render auth log context in debug messages

* fix: lower plain jwt auth failures to debug

* ๐Ÿ”— 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>

* ๐Ÿ—‚๏ธ feat: Add Private Chat Projects (#13467)

* feat: Add private chat projects

* fix: Format project files

* fix: Address project review findings

* fix: Resolve project review follow-ups

* fix: Handle project stats and cache edge cases

* style: align projects UI with sidebar patterns

* fix: resolve projects UI lint issues

* style: Align project menus and composer

* fix: Avoid project placeholder shadowing

* fix: Handle project search and stale ids

* fix: Polish project sidebar behavior

* fix: Preserve new chat stream after creation

* fix: Stabilize project sidebar sections

* fix: Smooth project sidebar organization

* fix: stabilize project chat entry

* fix: keep project workspace outside chat context

* fix: show default model on project workspace

* fix: fallback project workspace model label

* fix: preserve project scope during draft hydration

* fix: include route project in new chat submission

* fix: persist project id in agent chat saves

* fix: refine project sidebar and creation UX

* fix: export chat project method types

* fix: polish project landing context

* fix: refine project navigation affordances

* feat: rework projects UX โ€” coexisting sidebar sections + URL-driven scope

Sidebar
- Replace the chronological/by-project mode toggle with coexisting
  Projects + Chats sections (both always visible)
- Remove ProjectConversations (927 lines), the org-mode Header, and types
- Add ProjectsSection: collapsible project rows that unfurl chats inline
  (full-size rows), with per-project new chat and an open/rename/delete menu
- Lift the marketplace/favorites shortcuts above the Projects section

Chat scope
- Derive a new chat's project strictly from the URL ?projectId, so the
  global New Chat no longer stays stuck in a project after a project chat

Surfaces
- Chat landing: subtle, clickable project chip instead of the floating badge
- Project workspace: modest header, composer-style entry, chats list
- All-projects grid: Claude-style cards with pluralized chat counts

* chore: prune unused i18n keys; fix project chat-count pluralization

* fix: project new-chat keeps model spec; sidebar header + row polish

- newConversation: ignore a chatProjectId-only template when deciding to
  apply the default model spec, so starting a chat in a project no longer
  strips the conversation `spec`
- useSelectMention: the Model Selector and @ command now retain the active
  project across endpoint/spec/preset switches; other new-chat paths still
  clear it
- Chats header now matches the Projects header (inline chevron + a new-chat
  icon button) and starts a non-project chat
- Project rows: use the new-chat icon for the per-project add button, render
  at text-sm to match the chat list, and align the row actions + hover color
  with conversation rows

* fix: read project scope from router params; align sidebar header icons

- useSelectMention now reads the active project from React Router's search
  params instead of window.location, which can drift out of sync because
  new-chat params are written to the URL via raw history.pushState; the
  Model Selector and @ command now reliably keep the project on switch
- Move the Chats section header out of the virtualized list so it renders
  in the same context as the Projects header and isn't shifted by the
  list scrollbar
- Inset header action icons (pr-2) so Projects/Chats header icons line up
  with the project-row and conversation-row trailing actions
- Extract getRouteChatProjectId into utils for the submit path

* fix: preserve chatProjectId through the new-chat template reduction

The param-endpoint guard in newConversation reduced a new chat's template to
{ endpoint } only, dropping the chatProjectId injected by the Model Selector /
@ switch โ€” so switching models cleared the project scope. Keep chatProjectId
in the reduced template.

* style: align chat-history panel top padding; improve projects page contrast

- Add pt-2 to the chat-history panel so its top spacing matches the other
  side panels (agent builder, skills, files, etc.)
- Projects grid + workspace now use the darkest surface for the page
  (surface-primary) with cards, inputs, and the composer one step lighter
  (surface-secondary) and tertiary on hover, so cards read as elevated
  rather than darker than the background

* feat: interactive project landing chip + gallery icon for all-projects

- All-projects sidebar button uses the gallery-vertical-end icon
- The project landing chip is now interactive: click it to switch projects
  via a searchable combobox (ControlCombobox), or the trailing ร— to drop the
  project scope. Both update the draft conversation and the ?projectId search
  param in place, so the typed message and selected model are preserved

* test: fix Conversations unit test for refactored sidebar; add projects e2e

- Update Conversations.test.tsx mocks for the inline Chats header
  (useNewConvo, useQueryClient, conversation atom, NewChatIcon, TooltipAnchor),
  drop the removed chatsHeaderControls prop, and remove the mock for the
  deleted ../Header module โ€” fixes the failing frontend Jest job
- Add e2e/specs/mock/projects.spec.ts covering project creation, the
  project-scoped new-chat landing + interactive chip (switch/remove), and
  listing projects on /projects
- Give the landing chip combobox a stable selectId for reliable targeting

* fix: refresh project stats after project-chat activity; stabilize e2e

- useEventHandlers: when a project chat is created/updated, invalidate the
  live [projects] query (gated on chatProjectId) instead of the now-unused
  projectConversations key, so the sidebar + all-projects stats refresh
  after a streamed reply (addresses a Codex finding)
- projects e2e: assert the reliable project-landing behavior (chip, scoped
  composer, accepted send) rather than the /c/:id transition, which the
  mock LLM harness doesn't complete

* test: verify a project chat saves and is filed under its project (e2e)

- Switch to a mock endpoint before sending so the message streams without a
  real API key (the default model failed with "No key found", so no chat was
  saved and the page never left /c/new); this also asserts the project chip
  survives the model switch
- Restore the reply + /c/:id transition assertions and add a check that the
  chat is listed under the expanded project in the sidebar
- Add data-testid="project-chats-<id>" to the inline project chat list

* fix: address Codex review findings (project scope edge cases)

- useSelectMention: fall back to the conversation's chatProjectId when the
  URL has no projectId, so switching model/spec inside an existing project
  chat (/c/:id) keeps the project assignment
- Conversations: include chatProjectId in the MemoizedConvo comparator so a
  sidebar row's project menu doesn't stay stale after a reassignment
- useDeleteProjectMutation: clear the active conversation's chatProjectId
  when its project is deleted (mirrors the assignment mutation); drop the
  now-dead projectConversations invalidation
- useQueryParams: carry the project into the new conversation when applying
  URL settings, so /c/new?projectId=...&<settings> stays scoped

* fix: project stats pagination + archived-chat edge cases (data-schemas)

- listChatProjects: include the null lastConversationAt bucket in the desc
  cursor so empty projects paginate (a $lt:<date> predicate excluded nulls,
  hiding chat-less projects from "Load more")
- saveConvo: recompute project stats instead of the incremental fast path
  when the saved conversation is itself archived/temporary/expired, so a
  project's lastConversationAt/Id no longer points at a hidden chat

* test: cover chat-less project pagination across the datedโ†’null boundary

* fix: validate project ownership in bulkSaveConvos

Bulk paths (import/duplicate/fork) persisted whatever chatProjectId the
payload carried; an id that does not belong to the user created an orphan
assignment hidden from both the project and the unassigned sidebar. Validate
ownership like saveConvo and strip un-owned project ids before persisting,
refreshing stats only for owned projects.

* fix(projects): preserve chatProjectId on continuation, basename-safe delete redirect, project-detail invalidation

* fix(projects): navigate project workspace chats via useNavigateToConvo to avoid stale conversation state

* fix(projects): include projectConversations cache when resolving deleted chat's project for detail invalidation

* fix(projects): refresh both projects when a save or bulk write moves a chat between them

* style(projects): use Folders icon for the sidebar Projects header

* fix(projects): require id on ProjectUser so ProjectRequest extends Express Request cleanly

* style(projects): taller project chip with hover-revealed remove button, upward combobox; sort en translations

* style(projects): show endpoint/agent icon for project workspace chat rows

* ๐ŸŽš๏ธ feat: Add Focus Management and Drag-to-Scrub to MessageNav (#13497)

* ๐Ÿงญ feat: Add MessageNav Focus Management and Drag-to-Scroll

Resolves #13491: move keyboard focus into the conversation when a message indicator is selected, and add a Shift+Alt+M shortcut to jump focus back to the nav. Also adds drag-to-scrub interaction across the rib column.

* ๐Ÿ–ฑ๏ธ style: Use grab cursor for MessageNav drag affordance

* ๐Ÿ› fix: Harden MessageNav drag against stale pointer state and dead clicks

Addresses Codex/Copilot review on #13497:
- Ignore pre-drag pointermoves when the primary button is not held, preventing a stale press (released outside the column) from starting a spurious scrub or calling setPointerCapture on an inactive pointer.
- Clear the post-drag click-suppression flag after the synthetic-click window so a later activation (including keyboard) is never swallowed.
- Match the advertised Shift+Alt+M shortcut by accepting the layout-aware key in addition to the physical code.

* ๐ŸŽฏ fix: Track MessageNav drag globally so it survives leaving the column

Round-2 Codex review on #13497: the 4px threshold was applied before any pointer capture, so a drag that left the narrow rib column before crossing it silently failed to scrub.

Replace per-element capture with document-level pointermove/up/cancel listeners attached on pointerdown:
- Drag tracking continues regardless of pointer position (fixes diagonal/touch drags off the ribs).
- pointerup is always received, so no stale drag state and no setPointerCapture on an inactive pointer (removes the NotFoundError path entirely).
- Native click is preserved, so the keyboard/click selection a11y path is unchanged.
- Listeners are torn down on pointerup/cancel and on unmount.

* ๐Ÿงน fix: Reset drag state on pointer replace and gate MessageNav shortcut

Round-3 Codex review on #13497:
- When a second pointerdown replaces an in-progress drag, run the cleanup with the real drag state so draggingRef is cleared and the rib column resumes auto-centering (was hardcoded to finish(false)).
- Only preventDefault on Shift+Alt+M when the nav is actually rendered and has a focus target, so the shortcut no longer swallows browser/AT shortcuts when the nav is absent (<3 messages). focusNav now reports whether it moved focus.

* โœจ fix: Make MessageNav drag span the whole thread and harden teardown

Round-4 Codex review on #13497:
- Map the drag pointer proportionally across the full entries range instead of the visible rib rects, so long conversations whose mini-nav overflows are fully scrubbable in one drag. This is also wobble-immune, so the column auto-centering no longer needs to be frozen mid-drag (removed the freeze and draggingRef).
- focusNav now reports success only if focus actually landed, so Shift+Alt+M does not preventDefault when the nav is mounted-but-hidden (hidden md:flex on small viewports).
- End the drag if the primary button is released mid-move or the window loses focus, covering pointers released outside the document where pointerup/cancel never arrive.

* ๐Ÿชค fix: Reload Messages When Reopening a Chat from a Non-Chat Route (#13501)

navigateToConvo now removes the target conversation's cached messages before
fetching, so a freshly-mounted ChatView refetches them. clearMessagesCache
leaves a left conversation cached as [], and the messages query's
refetchOnMount: false treats that empty array as valid โ€” so returning to the
conversation from a route where ChatRoute was unmounted (e.g. /projects) left
the chat stuck on an empty cache with no /api/messages request.

* ๐Ÿ—‚๏ธ feat: Add Agent File Authoring Tools (#13435)

* feat: add agent file authoring tools

* style: format file authoring changes

* style: satisfy file authoring prettier

* test: fix file authoring initialization expectations

* fix: complete skill file authoring flow

* fix: pass skill authoring state on edit

* test: mock missing bundled skill file

* fix: harden agent file authoring gates

* fix: preserve file authoring runtime context

* test: fix authoring context mock typing

* fix: preserve subagent skill primes

* test: avoid array at in handler spec

* refactor: deepen skill authoring runtime wiring

* fix: address codex authoring review findings

* test: fix authoring collision fixture type

* test: add skill file authoring mock e2e

* fix: Improve skill file authoring recovery

* fix: Show file authoring args while running

* fix: Clarify skill rename authoring errors

* fix: Keep code-only file authoring schemas sandbox scoped

* fix: Address skill authoring review findings

* fix: Gate skill authoring on write access

* ๐Ÿšฆ fix: Gate Chat Starts During Readiness (#13502)

* fix: guard chat starts during server readiness

* style: format readiness retry condition

* fix: clarify chat start retry diagnostics

* fix: cancel stale chat start retries

* style: use const for retry timeout

* ๐ŸŒ i18n: Update translation.json with latest translations (#13505)

* ๐ŸŽญ test: Run Mock E2E Suite Through `createRun` With In-Process Fake Model (#13508)

* ๐ŸŽญ test: Run Mock E2E Suite Through createRun With In-Process Fake Model

Replace the standalone HTTP mock LLM server with an in-process fake model
injected into the real createRun -> Run.create pipeline via
run.Graph.overrideTestModel, so the mock suite exercises the agents
integration end-to-end without a live provider or a separate server.

- Bump @librechat/agents to 3.2.2 for the FakeChatModel/createFakeStreamingLLM exports
- Add an env-gated applyTestRunHook seam in packages/api createRun (no /api changes)
- Add e2e/setup/fake-model.js to drive default replies + the skill-authoring tool-call flow
- Drop the mock-llm webServer from playwright.config.mock.ts and set LIBRECHAT_TEST_RUN_HOOK

* ๐Ÿงน test: Retire Standalone Mock LLM Server From E2E Recorder

Migrate the `--profile=mock` recorder onto the same in-process fake model
as the Playwright mock suite, then delete the now-unused HTTP mock server
so the fake-LLM logic lives in a single place.

- Point record.js mock profile at the fake model via LIBRECHAT_TEST_RUN_HOOK
- Remove the mock-llm-server spawn/wait and MOCK_LLM_PORT plumbing from record.js
- Delete e2e/setup/mock-llm-server.js (e2e/setup/fake-model.js is now the only source)
- Update e2e/README.md to describe the in-process fake LLM

* ๐Ÿท๏ธ ci: Rename Playwright Mock E2E Check to Playwright E2E Tests

* ๐Ÿงท fix: Bind Agent File Context to Current Turn (#13506)

* fix: Bind agent file context to current turn

* fix: Avoid duplicating agent file context

* fix: Export agent file context prepender

* test: Use exported file context prepender

* fix: Keep file context transient for memory and counts

* ๐Ÿ’ผ fix: Harden Shared-Link Message Sanitization with an Allowlist (#13510)

* fix: harden shared-link message sanitization with an allowlist

Public shared links built their message payload via `{ ...message }` and
`{ ...attachment }` spreads, which exposed internal fields that are never
needed by the shared view:

- message: endpoint, conversationSignature, clientId, plugin(s), metadata
- attachments: filepath, storageKey, metadata, and other internal keys

Replace the passthrough with an allowlist so only render-relevant fields
(sender, text, content, token/feedback/error flags, and sanitized
attachments) are surfaced. Assistant model ids remain anonymized; other
model names are omitted rather than disclosed.

Also add `tenantId?: string` to ISharedLink, matching the field already
read by the shared-link access middleware for multi-tenant deployments.

* fix: preserve shared render data; sanitize by denylist (review feedback)

Address Codex/Copilot review on the shared-link sanitization:

- The tight attachment allowlist dropped tool-call render data. Switch to a
  denylist of storage/identity-internal fields (filepath, storageKey, user,
  tenantId, source, metadata, โ€ฆ) so toolCallId, tool payloads (web_search /
  file_search / etc.), and dimensions are preserved while internals are stripped.
- Preserve user-uploaded message.files (previously dropped entirely) via the
  same denylist sanitizer, so shared links keep uploaded images/documents.
- Introduce a dedicated SharedMessage / SharedFile type and use it for
  SharedMessagesResult.messages instead of casting the allowlisted object to
  IMessage, so omitted fields are caught at compile time.

Extends the regression test to assert toolCallId, web_search payload, and
files survive while filepath/storageKey/user/tenantId/metadata are removed.

* fix: keep render URLs + skill badges, drop private feedback (review round 2)

Address Codex round-2 findings on shared-link sanitization:

- filepath is the URL the share renderer loads (Files.tsx uses
  file.preview ?? file.filepath; image attachments render only when filepath
  is set). Drop it from the denylist so shared images/downloads still render;
  storageKey (the raw object key) stays stripped.
- Preserve manualSkills / alwaysAppliedSkills so SkillPills still render for
  skill-assisted turns (non-sensitive UI metadata).
- Remove feedback from the shared projection โ€” it is the owner's private
  rating/notes, is never rendered in the share view, and must not be exposed
  to anyone holding the share URL.

Regression test updated to assert filepath/skills survive and feedback is
omitted.

* fix: anonymize file ids + preserve message iconURL (review round 3)

- Persisted message.files records can carry the original conversationId/messageId.
  Attachments were already rewritten to the anonymized ids; apply the same
  rewrite to files so shared user-uploaded files don't expose the real ids.
- Preserve message.iconURL (read by Share/MessageIcon.tsx) so shared assistant/
  custom-endpoint turns keep their custom avatar instead of falling back to the
  generic icon.

Regression test asserts the shared file's conversationId is the anonymized id
and that iconURL survives.

* ๐Ÿ–ผ๏ธ fix: Upgrade Framer Motion for Vite 8 Compatibility (#13512)

LibreChat recently updated Vite (see 7dba640c9).

The older version of framer-motion we're using is incompatible with
this newer version of Vite; if you try to use it, you get the error
"e is not a function."

(One easy way to reproduce: try to enable 2FA on your account.)

Updating to the latest framer-motion fixes this issue.

* ๐Ÿงช test: Add E2E Regression For 2FA framer-motion Crash (#13513)

Add a Playwright mock e2e spec that opens Settings -> Account -> Enable 2FA
and asserts the framer-motion dialog renders. Reproduces the Vite /
framer-motion incompatibility from issue #13511: on the current build the
dialog crashes the client with "e is not a function" and never renders, so
this spec fails until the framer-motion bump in #13512 is merged.

* ๐Ÿชง chore: Generalize Project Name Placeholder (#13514)

* ๐Ÿ“ฆ chore: npm audit fix (#13515)

- Upgraded @langchain/langgraph from 1.3.2 to 1.3.4
- Upgraded @langchain/langgraph-checkpoint from 1.0.2 to 1.0.4
- Upgraded @langchain/langgraph-sdk from 1.9.4 to 1.9.15
- Updated uuid from 10.0.0 to 14.0.0 across multiple packages
- Upgraded @langchain/protocol from 0.0.15 to 0.0.16
- Upgraded @remix-run/router from 1.23.2 to 1.23.3
- Upgraded hono from 4.12.18 to 4.12.23
- Upgraded react-router from 6.30.3 to 6.30.4

* ๐Ÿ“œ feat: Improve Skill Authoring Guidance (#13517)

* feat: Improve skill authoring guidance

* test: Guard tool description lengths

* fix: Align skill template guidance

* fix: Satisfy advisory limit test lint

* fix: Transform LangGraph ESM in Jest

* ๐Ÿชก fix: Handle Missing Skill File Upsert Metadata (#13520)

* ๐Ÿ‘ป fix: Clear Project-Scoped Landing When the Selected Project Is Deleted (#13525)

* fix(projects): clear landing scope when the selected project is deleted

When a project-scoped new-chat landing (/c/new?projectId=...) was open and the
project got deleted, the chip kept showing the dead project and sends targeted it
(saving unscoped with a visual glitch).

- ChatRoute: only trust the scope when the project query succeeds (isSuccess), so
  React Query's retained-on-error data can't keep a deleted project's chip alive;
  strip ?projectId once the query settles to not-found so the landing reverts to a
  normal unscoped chat.
- useDeleteProjectMutation: invalidate the project-detail query instead of removing
  it, so active observers refetch and settle into an error state (removing left them
  stuck loading under refetchOnMount: false).
- e2e: regression test for delete-while-scoped.

Fixes a follow-up issue to the projects feature (#13467).

* fix(projects): only drop scope on definitive not-found; clear inactive deleted detail

Address Codex review on #13525:
- ChatRoute: gate scope removal on a 404 (isNotFoundError) or a success that
  resolves to a different/empty project, so a transient (non-404) failure under
  retry:false no longer unscopes a valid project; keep the chip through transient
  errors via retained data.
- useDeleteProjectMutation: also removeQueries({ type: 'inactive' }) so a deleted
  project's inactive cached detail is dropped and a later visit refetches into a
  not-found state instead of rendering stale cache within cacheTime.

* ๐Ÿชน feat: Collapse Empty Projects Section in the Sidebar by Default (#13531)

The Projects section defaulted to expanded, taking sidebar space for users with no
projects. Now derive the default: collapsed when there are no projects and the user
has never toggled the section; expanded once they have a project or explicitly
expand it. Any explicit toggle (new projectsSectionToggled flag) โ€” or a collapse set
before this default existed โ€” is respected.

* ๐Ÿงญ feat: Scope Model Spec Skills (#13522)

* feat: scope model spec skills

* style: format skill catalog limit

* fix: serialize model spec skill resolution

* test: satisfy model spec load config typing

* fix: apply model spec skills to added conversations

* fix: support alwaysApply frontmatter alias

* fix: address model spec skills review

* ๐Ÿ—‚๏ธ feat: Add Deployment Skill Directory (#13523)

* feat: Add deployment skill directory

* chore: Address deployment skill review feedback

* fix: Include deployment skill file metadata

* test: Add deployment skills e2e smoke test

* ๐Ÿงญ fix: Restore Empty Skill Allowlist Catalog (#13526)

* ๐Ÿชฆ fix: Add Durable MCP Config Tombstones (#13534)

* fix: add durable MCP config tombstones

* fix: preserve scoped config tombstones

* fix: clean up config tombstone lint

* fix: handle empty model spec skill allowlist

* fix: preserve inactive config tombstones

* ๐Ÿ” fix: Handle Multiple Concurrent MCP OAuth Login Prompts (#13200)

* fix: handle multiple MCP OAuth prompts

* fix: address MCP OAuth review feedback

* fix: address MCP OAuth prompt lifecycle review

* fix: narrow OAuth prompt slot cleanup

* fix: format OAuth prompt test

---------

Co-authored-by: Danny Avila <danny@librechat.ai>

* ๐Ÿ” fix: Reuse MCP OAuth Authorization URL (#13532)

* fix: reuse MCP OAuth authorization URL

* fix: validate MCP OAuth initiate flow ID

* ๐Ÿชช fix: Filter ACL Principal Details (#13524)

* fix: filter ACL principal details

* test: type ACL permission pipeline assertions

* test: add ACL permissions e2e coverage

* ๐Ÿ–ผ๏ธ fix: Support Known Endpoint Group Icons (#13542)

* fix: Support known endpoint group icons

* fix: Resolve known endpoint group icon edge cases

* ๐Ÿ“Š fix: Contain Markdown Table Overflow (#13543)

* fix: Contain Markdown table overflow

* fix: Improve Markdown table scrollbar

* ๐Ÿ“Ž fix: Preserve Provider Document Uploads (#13550)

* fix: Preserve provider document uploads

* test: Add provider upload e2e coverage

* ๐Ÿ”ง chore: Update ESLint config, Import Sorting script, Test Sharding, Bump `@librechat/agents` (#13552)

* ๐Ÿ”ง chore: Update ESLint config, add import sorting script, Test Sharding, Bump `@librechat/agents`

* Change 'no-nested-ternary' rule from 'warn' to 'error' in ESLint config
* Add new scripts for sorting imports in the project
* Update lint-staged configuration to include import sorting
* Modify GitHub Actions workflows to support sharding for unit tests

* chore: remove nested ternary expressions

* refactor: Extract scale multiplier logic into a separate function in CircleRender component
* refactor: Simplify auto-refill rendering logic in Balance component for better readability
* refactor: Improve width style handling in DataTable components for clarity and maintainability

* chore: remove CircleRender component

* delete: Remove CircleRender component as it is no longer needed in the project

* chore: Bump @librechat/agents to version 3.2.31 and update Node.js engine requirement

* Update @librechat/agents dependency from 3.2.2 to 3.2.31 in package-lock.json, api/package.json, and packages/api/package.json
* Change Node.js engine requirement from >=20.0.0 to >=24.0.0 in @librechat/agents

* chore: Add import sorting check to ESLint CI workflow

* Implement a new job in the GitHub Actions workflow to verify import ordering on changed files.
* The job checks for changes in specific file types and reports any import order drift, providing instructions for local fixes.

* ๐Ÿ› ๏ธ fix: Enable Gemini Mixed Tool Config (#13538)

* fix: enable Gemini mixed tool config

* fix: apply Gemini mixed tool flag after skills

* style: match initialize formatting

* style: wrap final tool check

* fix: Respect Vertex auth mode

* style: Sort Agent Initialize Imports

* fix: Tighten Gemini Mixed Tool Gate

* ๐Ÿช feat: Surface Agent Marketplace in Model Selector (#13553)

* feat: surface marketplace in model selector

* chore: sort marketplace selector imports

* fix: localize marketplace selector search

* ๐ŸŒฑ feat: Support Soft Default Model Spec (#13554)

* feat: add soft default model spec

* chore: sort ChatRoute imports

* ๐Ÿšฆ fix: Guard Auth Continuation with Dedicated Limiter (#13555)

* fix: refine auth continuation handling

* test: align auth route mock setup

* fix: separate auth continuation throttling

* test: format auth route mock

* fix: preserve continuation limiter context

* fix: hydrate continuation user before bans

* ๐Ÿท๏ธ fix: Categorize Auth Tokens by Flow Type (#13556)

* fix: Scope auth token lifecycle

* fix: Preserve legacy auth token lookup

* fix: Scope verification token cleanup

* ๐Ÿ“Ž fix: Scope Attachment Usage to Request Owner (#13557)

* fix: harden attachment usage handling

* fix: sort file method imports

* fix: clarify file usage scope

* ๐Ÿ›‚ fix: Normalize Verification Flow Error Responses (#13558)

* fix: normalize verification flow responses

* fix: keep verification responses consistent

* ๐Ÿ”€ fix: Reconcile Agent Action Credential Merges (#13559)

* fix: Refine Agent Action Updates

* fix: Format Action Update Helper

* fix: Refine Agent Action Update Handling

* fix: Move Agent Action Update Planning

* fix: Sort Action Update Imports

* chore: Reorder imports in actions.js for clarity

* โณ feat: Make OpenID Token Reuse Window Configurable (#13546)

* feat: make OpenID token reuse window configurable via OPENID_REUSE_MAX_SESSION_AGE_MS

The OpenID session-token reuse window in AuthController was a hardcoded 15-minute
constant, forcing /api/auth/refresh to perform a real refreshTokenGrant against the
IdP every 15 minutes even when the current access token is still valid. IdPs that
rotate and revoke the previous access token on refresh then invalidate a token that
is still in use by downstream consumers of the reused OpenID token (e.g. MCP servers
that receive {{LIBRECHAT_OPENID_TOKEN}} and introspect the bearer), producing
~15-minute 401 cycles regardless of the access token's actual lifetime.

Read the window from process.env.OPENID_REUSE_MAX_SESSION_AGE_MS via the existing
math() helper, so it accepts an arithmetic expression like SESSION_EXPIRY (e.g.
60 * 60 * 24 * 1000), defaulting to the existing 15 minutes so behavior is unchanged
unless explicitly configured. The existing 30s-before-expiry guard still forces a
refresh before genuine expiry, so a larger window remains safe.

* fix: extend OpenID reuse session lifetime

---------

Co-authored-by: Danny Avila <danny@librechat.ai>

* ๐Ÿƒ refactor: Agent Avatar Conversation Icons and Streaming Indicators (#13563)

* ๐Ÿ‘ท ci: Type-check the Client Workspace (#13560)

The `client/` workspace was never type-checked: the existing typecheck
job only covered `packages/` and `api/`, and Vite/esbuild transpiles
without type-checking, so type errors shipped through every CI gate.

- Add a `typecheck` job to frontend-review.yml running `tsc --noEmit`
  over `client/` (zero tolerance), reusing the data-provider +
  client-package build artifacts. Triggers on `client/**`,
  `packages/client/**`, `packages/data-provider/**`.
- Fix all 168 pre-existing client type errors this surfaced (source +
  tests), including genuine latent bugs:
  - `getFileConfig()` was typed as merged `FileConfig`, but the server
    returns the raw config that `mergeFileConfig()` consumes (`TFileConfig`).
  - SidePanel/Agents `Retrieval`/`ImageVision` were bound to `AgentForm`
    but use the assistants `Capabilities` enum โ†’ `AssistantForm`.
  - `useSearchResultsByTurn` read a `sources` field its type lacked.
  - Removed orphaned dead code: `Artifacts/Mermaid.tsx` (imported a
    never-installed dep) and dead barrel re-exports (`./Plugins`, `./MCPAuth`).
- Narrow `client/tsconfig.json` to the client app (drop `../e2e` and
  `../config/translations`, which reference backend/tooling modules) so
  the gate's scope matches its trigger.

No `any`/`@ts-ignore`/`as unknown as`. Localized newly-surfaced strings.

* ๐Ÿงผ fix: Prevent Shared Link Caching and Strengthen Log Redaction (#13561)

* fix: tighten share caching and log redaction

* fix: sort changed imports

* fix: redact splat log arguments

* fix: avoid mutating log metadata during redaction

* fix: redact error and api_key log values

* fix: preserve error log context during redaction

* fix: cover remaining log redaction paths

* fix: bound log redaction work

* fix: align redaction scan cap with log config

* ๐Ÿ“ฆ chore: Update Turbo to v2.9.16 (#13564)

* ๐Ÿฉน chore: Double-Assert Partial Test Fixture in EndpointIcon (#13567)

The `endpointsConfig` fixture in `EndpointIcon.test.tsx` casts an object whose
values are `{}` to `TEndpointsConfig` (`Record<EModelEndpoint | string, TConfig | null | undefined>`).
`TConfig.order` is required, so `{}` doesn't overlap `TConfig` and the direct
assertion is a TS2352 error under a fresh `tsc --noEmit` over the client
workspace (the type-check job added in #13560), when `librechat-data-provider`
is built from source (the test was added in #13563):

    Conversion of type '{ agents: {}; google: {}; }' to type 'TEndpointsConfig'
    may be a mistake because neither type sufficiently overlaps with the other.

Give the fixture entries the required `order` field so they're valid `TConfig`
values. This keeps the plain `as TEndpointsConfig` assertion type-checking the
fixture shape, rather than blanking it out with `as unknown as`.

* ๐Ÿฉน fix: Bump GitNexus to 1.6.5 and Fail-Soft the PR Index Job (#13569)

* ๐Ÿฉน fix: Bump GitNexus to 1.6.5 and Fail-Soft the PR Index Job

The GitNexus Index workflow began failing on most PRs with
"Analysis failed: Maximum call stack size exceeded". Root cause is in
the pinned gitnexus@1.5.3 CLI: pipeline.js does
`deferredWorkerCalls.push(...chunkWorkerData.calls)`, and once a chunk
yields more extracted calls than V8's argument-count limit (~125k on
this repo) the spread-push throws a RangeError. It is deterministic on
repo size, not flaky โ€” LibreChat simply grew past the threshold, so it
fails "more often" as more branches cross it. Stack-size flags don't
help; it's an arg-count limit, not stack depth.

gitnexus@1.6.5 refactored that code path (the .calls spread-pushes are
gone) and indexes this repo cleanly. Bump the indexer, the deploy image
tag/build-arg, and the Dockerfile default in lockstep (an index written
by 1.6.5 must be served by a 1.6.5 server), and move the co-pinned
@ladybugdb/core to 0.16.1 to match.

Also make the index job fail-soft on pull_request events so a future
tool-internal crash degrades gracefully instead of red-X'ing PRs. Push,
dispatch, and /gitnexus command runs still fail loudly, keeping the
deploy-gating and completion-comment logic correct.

* ๐Ÿณ fix: Unbreak the GitNexus Deploy Image for 1.6.5

Addresses two issues in the deploy image surfaced after the 1.6.5 bump:

- The image build's lbug-adapter patch grepped
  dist/mcp/core/lbug-adapter.js for "LOAD EXTENSION fts", but in 1.6.5
  that file is a shim re-export and the FTS load moved to
  dist/core/lbug/lbug-adapter.js. The grep would fail the build on the
  next image rebuild. The patch is also obsolete: 1.6.5 loads the vector
  extension itself via loadVectorExtension. Removed the patch step.

- The image installed only gitnexus, letting @ladybugdb/core resolve
  freely via gitnexus's ^0.16.1 range while the index workflow pins
  0.16.1 exactly. Pin the native DB in the image too (nested under
  gitnexus so install-extensions.js keeps resolving it), restoring the
  intended indexer/server lockstep.

* ๐Ÿท๏ธ fix: Preserve Generated Conversation Title on Stop (#13568)

Immediate title generation discarded an already-generated title when the
user stopped the turn, both in the backend (skipped saveConvo) and the
frontend (rolled back the streamed title), leaving the chat as "Untitled"
in the interim and "New Chat" after refresh.

Split the title abort into two signals: `signal` still cancels an in-flight
title model call on Stop, while a new `discardSignal` discards an
already-generated title only when the stream is superseded by a newer run
or the turn fails. A plain user Stop now persists and keeps the title.
The frontend no longer rolls back a real, already-applied title on an
aborted final event.

* ๐ŸŒฒ test: Add E2E Coverage for Message Tree Streaming (#13570)

* add e2e message tree stream coverage

* fix e2e message tree review findings

* expand message tree e2e recovery coverage

* fix stream-start failure recovery coverage

* ๐Ÿ›๏ธ refactor: Prioritize Deployment Skills over Persisted Duplicates (#13575)

* fix: prefer deployment skills on name collision

* chore: sort deployment skill imports

* fix: dedupe deployment collision warnings

* fix: return logger from warning spy

* fix: preserve skill collision pagination

* fix: honor db page boundary for skill merges

* ๐Ÿ“ป fix: Replay MCP OAuth Prompts for Coalesced Connections (#13565)

* fix: Replay MCP OAuth URL for Joined Connections

* chore: Sort MCP OAuth Imports

* test: Restore MCP OAuth Registry Spies

* fix: Replay pending MCP OAuth prompts

* fix: Replay MCP OAuth on Stream Resume

* fix: Preserve MCP OAuth Replay Context

* chore: Format MCP OAuth Replay Context

* test: Expect MCP OAuth Replay Expiry

* fix: Render pending MCP OAuth prompts

* chore: Clean MCP OAuth Replay Type Narrowing

* fix: Stabilize new MCP OAuth chats

* fix: Re-emit cached MCP OAuth prompts

* fix: Replay pending OAuth for selected MCP tools

* fix: Avoid stalling pending MCP OAuth replay

* test: Clean MCP OAuth review findings

* test: Restore MCP OAuth registry spy

* fix: Resolve OAuth Typecheck Regressions

* fix: Harden MCP OAuth replay edge cases

* test: Cover MCP OAuth joined prompt expiry

* test: Mark joined OAuth replay fixture

* test: Use OAuth fixture for joined replay expiry

* fix: Anchor resumed MCP OAuth prompts

* fix: Seed resumable turn metadata before MCP init

* test: Format resume metadata regression

* fix: Prioritize resumable stream routes

* fix: Preserve MCP OAuth resume message tree

* test: Fix MCP OAuth Resume Test Types

* fix: Replay MCP OAuth Regenerate Prompts

* fix: Skip OAuth-only Abort Persistence

* fix: Stabilize OAuth Resume Replay

* fix: Target Non-Tail Regenerate Responses

* fix: Scope Regenerate Step Updates

* fix: Clean Up OAuth Abort State

* fix: Preserve Regenerate Branch Siblings

* fix: Preserve OAuth Resume Branch State

* fix: Preserve OAuth Branch Resume State

* chore: Sort OAuth Resume Imports

* fix: Address OAuth Resume Review Findings

* test: Fix Abort Fixture Typing

* ๐Ÿ“Š feat: Surface Message Feedback as Langfuse Scores (#13544)

* feat: surface message feedback (thumbs up/down) as Langfuse scores

When Langfuse tracing is enabled, the message feedback endpoint now posts a
boolean `user-feedback` score (1/0 + tag/comment) to Langfuse for the
assistant message's trace; clearing feedback deletes the score. Fire-and-
forget, so the feedback UX never blocks on Langfuse.

Linking is lookup-free: the run opts into deterministic Langfuse trace ids
(`langfuse.deterministicTraceId`, passed to the agents Run), so the trace id
is sha256(messageId)[:32]. The feedback route recomputes the same id and
scores by it.

- api/server/services/Langfuse.js: POST/DELETE /api/public/scores (env-gated)
- api/server/utils/langfuseTrace.js: traceIdForMessage(messageId)
- api/server/routes/messages.js: fire feedback score after the Mongo write
- packages/api: pass langfuse.deterministicTraceId to the run
- bump @librechat/agents to ^3.2.21 (adds LangfuseConfig.deterministicTraceId)

Closes #13537

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: match Langfuse trace environment for feedback scores

@librechat/agents passes no environment to its Langfuse tracer, so
@langfuse/otel falls back to LANGFUSE_TRACING_ENVIRONMENT and otherwise to
Langfuse's "default". The score helper instead fell back to NODE_ENV, so a
deployment with only NODE_ENV=production filed scores under "production" while
the trace stayed on "default" โ€” the score never landed on the trace.

Use LANGFUSE_TRACING_ENVIRONMENT only, and omit `environment` when unset so
Langfuse defaults both score and trace to "default".

Addresses Codex review on #13544.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: don't require LANGFUSE_BASE_URL to post feedback scores

The agent tracer emits traces with just the public/secret keys (defaulting to
Langfuse Cloud, or via the legacy LANGFUSE_BASEURL alias), but the score helper
disabled itself unless LANGFUSE_BASE_URL was set โ€” so an otherwise-traced
deployment silently posted no scores. Resolve the base URL the same way the
tracer does (LANGFUSE_BASE_URL -> LANGFUSE_BASEURL -> Cloud) and gate enablement
on the credentials only.

Addresses Codex review on #13544.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: only post feedback scores for agent-endpoint messages

The feedback route is shared by all message types, but deterministic Langfuse
trace IDs are only enabled for agent runs. Rating a message from a non-agent
endpoint (with Langfuse configured) posted a user-feedback score for
sha256(messageId) that no trace will ever match, leaving orphan scores.

Gate scoring on isAgentsEndpoint(message.endpoint); `updateMessage` now returns
`endpoint` so the route can check it.

Addresses Codex review on #13544.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: gate feedback scoring by !isAssistantsEndpoint, not isAgentsEndpoint

The previous gate used isAgentsEndpoint, which only matches the literal
`agents` endpoint. But provider endpoints (anthropic, openai, custom, โ€ฆ) run
through the agents runtime as ephemeral agents and DO emit deterministic
AgentRun traces, so isAgentsEndpoint('anthropic') === false suppressed scoring
for the common case. Only the OpenAI/Azure Assistants endpoints use a separate
runtime with no agent trace, so gate on !isAssistantsEndpoint instead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* style: sort message method imports

* fix: honor Langfuse tracing gates for feedback scores

* refactor: move Langfuse feedback logic to api package

* fix: support Langfuse host for feedback scores

* test: type Langfuse feedback fetch mock

* chore: compact Langfuse feedback comment

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Danny Avila <danny@librechat.ai>

* ๐ŸงŠ perf: Memoize Completed Markdown Blocks During Streaming (#13576)

Render assistant markdown as independently memoized top-level blocks instead of a
single ReactMarkdown that re-parses and re-highlights the entire message on every
streamed token. Once a block's source slice is stable it skips re-parse/re-render;
only the final, still-growing block re-parses.

- splitMarkdown: split a message into top-level blocks via mdast-util-from-markdown
  (+ gfm/directive/math extensions) using node source offsets; also report per-block
  executable-code and artifact index counts.
- MarkdownBlocks: render each block memoized on its raw slice, each wrapped in its
  own CodeBlock/Artifact providers seeded with prefix-summed base indices, so the
  document-order indices used to match code-execution results stay stable under
  memoization (verified by OLD-vs-NEW parity tests across direct + streamed renders).
- CodeBlockContext/ArtifactContext: add optional baseIndex (default 0, fully
  backward compatible) so per-block providers continue the running index.
- markdownConfig: extract the shared remark/rehype plugins + components map.
- deps: declare mdast-util-from-markdown, mdast-util-gfm/math/directive and the
  micromark gfm/math/directive extensions as direct client dependencies (previously
  resolved transitively via react-markdown).
- Tests: splitter unit tests; index parity + DOM equivalence vs the whole-message
  renderer; rendering smoke tests.
- Bench (MarkdownBlocks.bench.tsx, outside __tests__ so the default jest run skips
  it): ~88% fewer code-block renders and ~2.3x faster cumulative render across a
  simulated stream.

* ๐Ÿ” fix: Resolve Env Variables in MCP OAuth URL Fields (#13573)

* fix: resolve env variables in MCP OAuth URL fields before validation

Apply the extractEnvVariable transform to authorization_url, token_url,
redirect_uri, and revocation_endpoint in OAuthOptionsBaseSchema. Without
this, ${ENV_VAR} syntax in these fields caused a Zod URL validation error
at startup before any env substitution could happen.

The same .transform().pipe() pattern is already used on all transport url
fields (SSE, WebSocket, StreamableHTTP) and ProxyUrlSchema.

Closes #13572

* fix: block env var expansion in user OAuth URL fields

Override redirect_uri and revocation_endpoint in UserOAuthOptionsSchema
with userOAuthEndpointUrlSchema, matching the existing overrides for
authorization_url and token_url. Without this, user-submitted configs
could inherit the extractEnvVariable transform added to the base schema
and resolve env vars like ${OPENAI_API_KEY} in those fields.

Add envVarPattern rejection to userOAuthEndpointUrlSchema so that
valid-URL-shaped payloads containing ${VAR} patterns are also blocked,
not just bare non-URL strings. Move envVarPattern declaration above the
schema to make it available at module evaluation time.

Add regression tests for all four OAuth URL fields on the user path,
using structurally valid URLs with embedded ${VAR} patterns to confirm
it is the env var guard โ€” not URL shape โ€” that rejects them.

* โšก perf: Migrate `data-schemas` Build to tsdown with isolatedDeclarations (#13578)

* โšก perf: Migrate data-schemas Build to tsdown with isolatedDeclarations

Replace Rollup with tsdown (rolldown + oxc) for @librechat/data-schemas. With the source made isolatedDeclarations-clean, oxc emits .d.ts without tsc, dropping the package build from ~5.8s to ~0.8s (~7x).

- Annotate exported model/method factories for isolatedDeclarations (TypeScript's fixMissingTypeAnnotationOnExports codefix plus hand-authored interfaces); type the ~44 mongoose `any`s and add an explicit PromptMethods interface (previously its declaration was silently dropped by the Rollup build).
- Repoint package.json exports/main/module/types to tsdown output; drop rollup config.
- Config lives in tsdown.config.mjs (native ESM) so CI without a TS-config loader can build it; bundle `dotenv` so the package stays self-contained for its env-loading side effect.
- Fix a latent token `metadata` mismatch the accurate types surfaced: widen TokenCreate/UpdateData inputs to accept plain objects, flatten OAuthMetadata at the api boundary.
- Update mongoMeili/aclEntry specs to the precise model types; drop redundant terser minification from data-provider's library build.

All data-schemas tests pass; api builds clean against the new output.

* ๐Ÿ”ง chore: Hash tsdown.config.mjs in data-schemas CI build-cache keys

The data-schemas build switched from rollup to tsdown, but the build-data-schemas / build-api cache keys in backend-review, config-review, and playwright-mock still hashed the (now-deleted) rollup.config.js. Hash tsdown.config.mjs instead so a config-only change invalidates the cached dist/api builds. (Found by Codex review.)

* ๐Ÿ”ง chore: Replace deprecated tsdown `external` with `deps.neverBundle`

tsdown 0.22 deprecated the top-level `external` option in favor of `deps.neverBundle`. Migrate the data-schemas config and set `deps.onlyBundle: false` to silence the (intentional) dotenv bundling hint. Build output and externalization are unchanged โ€” dotenv bundled, all peers external.

* โฌ†๏ธ chore: Bump TypeScript to 5.9.3 (+ typescript-eslint 8.60.1) (#13584)

Bumps typescript 5.3.3 -> 5.9.3 across all workspaces. typescript-eslint must move 8.24.0 -> 8.60.1 too: 8.24's typescript peer was capped at <5.8.0; 8.60.1 widens it to <6.1.0.

Two errors surfaced by the newer compiler are fixed:
- api/src/rum/proxy.ts: TS 5.9 made `Buffer` generic (`Buffer<ArrayBufferLike>`), which no longer structurally matches `BodyInit`; cast the fetch body (Node's fetch accepts a Buffer at runtime).
- client usePresetIndexOptions.ts: drop a dead `|| {}` on an object spread (always truthy โ€” flagged by the new TS2872 check).

All four package typecheck jobs + the client app typecheck pass under 5.9.3; builds (tsdown + rollup) and the rum proxy tests are unaffected.

* ๐ŸŒฟ fix: Anchor Post-Auth MCP Stream to Submission Message Tree (#13582)

* fix: Preserve MCP OAuth post-auth message tree

* fix: Hydrate MCP OAuth replay after created event

* fix: Satisfy OAuth replay typecheck

* ๐Ÿšง fix: Add Per-User Throttle to 2FA Continuation Attempts (#13583)

* fix: refine auth continuation throttling

* chore: import order

* ๐Ÿ“Œ fix: Preserve Project Scope Through Enforced Model Specs (#13586)

* ๐Ÿ”ง chore: Enforce isolatedDeclarations in data-schemas tsconfig (#13593)

Enable `isolatedDeclarations` (and `declaration`) in
packages/data-schemas/tsconfig.json so any exported declaration missing
an explicit type annotation is flagged directly in editors and the CI
typecheck, instead of only surfacing during the tsdown/oxc dts emit at
build time.

The package is already fully annotated, so this is a zero-error,
enforcement-only change that keeps data-schemas eligible for the fast
oxc-based declaration emit going forward.

* โšก๏ธ refactor: Migrate `@librechat/api` build to `tsdown` (#13595)

* โšก๏ธ refactor: Migrate @librechat/api build to tsdown

Replace Rollup with tsdown (rolldown + oxc isolated-declarations) for the
@librechat/api package build, mirroring the merged data-schemas migration.

- Add tsdown.config.mjs (cjs output, oxc dts, externalize all bare deps,
  bundle first-party `~/` + relative imports)
- Annotate exports for isolatedDeclarations (codefix-driven). Collapse the
  tokens.ts model->token maps to Record<string, Record<string, number>> and
  switch validation.ts's runtime `files` field from z.any() to z.unknown()
  so no explicit `any` is introduced
- Repoint package.json main/types/exports to tsdown's .cjs/.d.cts output
- Add src/telemetry.ts entry shim so the two index.ts entries don't collide
  in oxc's flat dts output (stable dist/telemetry.{cjs,d.cts})
- Delete rollup.config.js

Build time ~36s -> ~0.5s. No runtime behavior change: 5712 unit tests pass,
both entries load via require(), legacy /api consumes them unchanged.

* ๐Ÿ‘ท ci: Hash packages/api/tsdown.config.mjs in build-api cache keys

The build-api cache keys hashed `packages/api/server-rollup.config.js`,
which never existed (api used `rollup.config.js`, now removed) โ€” a copy-paste
artifact from the data-provider key that matched no file. Replace it with the
new `packages/api/tsdown.config.mjs` so edits to the build config (entry,
format, externals) bust the api build cache, matching the data-schemas key.

* โšก refactor: Migrate `data-provider` Build to `tsdown` (split tsc dts) (#13597)

Replace the Rollup + `rollup-plugin-typescript2` build with a split
pipeline: tsdown (rolldown) bundles the JS in ~0.2s, and plain `tsc`
emits the declarations to `dist/types` (~2s). Full cold build drops from
~9.2s to ~2.5s (~3.6x) with zero source changes.

Unlike data-schemas, the fast oxc/isolated-declarations dts path isn't
viable here: the package's 78 exported zod schemas produce 374
`isolatedDeclarations` errors (TS9013/TS9038) and a `z.ZodType<T>`
annotation would break the 76 downstream `.extend`/`.shape`/`.pick`
usages. Plain `tsc` keeps the rich zod types intact, and since dts was
never the bottleneck (rollup-plugin-typescript2 was), the win stands.

- dts stays unbundled in `dist/types/` โ€” identical to the prior output,
  so the existing deep `dist/types` imports and the exports `types`
  paths are unchanged.
- ESM output renamed `index.es.js` -> `index.mjs` (via the exports map;
  no consumer hardcodes the old path). cjs/types paths unchanged.
- `./react-query` now emits a real cjs build + types โ€” the exports map
  already promised them, but Rollup only ever built the esm file.
- Kept `rollup` + the plugins used by `server-rollup.config.js`
  (the `rollup:api` server-bundle smoke test in backend-review.yml);
  removed only the deps used solely by the deleted `rollup.config.js`.
- Repointed CI build-cache keys from `rollup.config.js` to
  `tsdown.config.mjs`.

* โšก refactor: Migrate `@librechat/client` build to `tsdown` (#13596)

* โšก refactor: Migrate @librechat/client build from Rollup to tsdown

Mirrors the data-schemas migration. Replaces Rollup (rpt2 + postcss) with
tsdown (rolldown + oxc); the package build drops from tens of seconds to ~0.3s.

- Emit isolated-declaration .d.ts via oxc (dts.oxc) and enforce
  isolatedDeclarations in tsconfig for editor DX (source made clean: explicit
  export type annotations added across src, no `any`).
- Extract component CSS to dist/style.css so the CJS output stays valid
  CommonJS (the prior postcss runtime-injection produced an ESM import in the
  CJS bundle that breaks jest/require). Imported once in the client app entry;
  Vite bundles it for the app.
- Repoint package.json to dual .mjs/.cjs + .d.mts/.d.cts and add ./style.css
  and ./package.json exports.
- Update CI build-cache keys to hash tsdown.config.mjs; remove rollup.config.js.

* ๐Ÿ”ง chore: address Codex review on client tsdown migration

- Add tsdown.config.mjs to turbo.json build `inputs` so changes to the new
  bundler config invalidate the Turbo cache (the shared inputs only listed the
  rollup configs). Also covers the already-migrated data-schemas.
- Name the memoized default export (ControlComboboxMemo) instead of the
  codefix-generated `_default_1`, for clearer stack traces / grepping.

* ๐Ÿงฉ feat: Enable Model Spec Subagents (#13598)

* ๐Ÿ“ฆ chore: Declare runtime deps externalized by tsdown in `@librechat/api` (#13600)

The tsdown migration (#13595) externalizes all third-party imports
(Rollup inlined them), so several modules the api source imports must be
present at runtime. Six were not, causing production (`npm ci --omit=dev`)
to crash on boot with `Cannot find module 'get-stream'` (then the next).

Fixed following the package's existing convention โ€” packages/api declares
runtime libs as `peerDependencies`, and the `/api` app provides them as
real `dependencies` (how express/mongoose/sharp already resolve):

- `api/package.json` (the prod app, the provider): add the 3 that were
  missing โ€” `get-stream`, `jszip`, `mongodb`. (`dedent`/`lodash`/`nanoid`
  were already provided by /api.)
- `packages/api/package.json`: add all 6 to `peerDependencies` (the
  contract) and to `devDependencies` (workspace build/tests), matching
  the existing `mammoth`/`pdfjs-dist`/`sanitize-html` dev+peer pattern.
  `jszip`/`mongodb` move out of dev-only (were pruned in production).

Pinned to CJS-compatible majors (get-stream@6, nanoid@3). Verified the
built bundle has zero undeclared externals and the 3 newly-provided deps
are production (non-dev) in the lockfile, so they survive `--omit=dev`.

* ๐Ÿ“‹ refactor: Attach Message Context to Langfuse Feedback Scores (#13604)

* ๐Ÿชž fix: Preserve Model Spec Icons Across Stream Resume and Abort (#13603)

* ๐Ÿ‘ท ci: Add API runtime smoke (boot the production image) to docker-smoke (#13605)

* ๐Ÿ‘ท ci: Add API runtime smoke (boot the production image) to docker-smoke

The docker-smoke workflow only built the `client-package-build` stage and
never booted the runtime, so it couldn't catch the class โ€ฆ
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants