mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-10-24 19:27:23 +00:00
🪶 feat: Enable Standalone Skill Authoring (#15480)
* feat: enable standalone skill authoring * test: align skill e2e flow with enable toggle * fix: preserve legacy skill state on version restore
This commit is contained in:
parent
dfb264811c
commit
aa7539909b
33 changed files with 683 additions and 123 deletions
|
|
@ -429,6 +429,8 @@ class AgentClient extends BaseClient {
|
|||
subagents: agent.subagents,
|
||||
memory_scope: agent.memory_scope,
|
||||
skills_enabled: agent.skills_enabled,
|
||||
skill_authoring_enabled: agent.skill_authoring_enabled,
|
||||
skills_scope: agent.skills_scope,
|
||||
skills: agent.skills,
|
||||
backgroundToolNames: agent.backgroundToolNames,
|
||||
intentToolNames: agent.intentToolNames,
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ const {
|
|||
const {
|
||||
Time,
|
||||
Tools,
|
||||
SkillsScope,
|
||||
CacheKeys,
|
||||
Constants,
|
||||
FileSources,
|
||||
|
|
@ -241,18 +242,38 @@ const blockFilteredAgentContent = async (req, res, agentData) => {
|
|||
|
||||
const sanitizeViewerSkillScope = (agent, accessibleSkillSet) => {
|
||||
const skillScopeEnabled = agent.skills_enabled === true;
|
||||
const configuredScope = agent.skills_scope;
|
||||
delete agent.skills_enabled;
|
||||
delete agent.skills_scope;
|
||||
|
||||
if (!skillScopeEnabled) {
|
||||
delete agent.skills;
|
||||
return agent;
|
||||
}
|
||||
|
||||
const configuredSkills = Array.isArray(agent.skills) ? agent.skills : [];
|
||||
if (configuredSkills.length === 0) {
|
||||
// Empty allowlist means the viewer's full accessible catalog.
|
||||
if (configuredScope === SkillsScope.none) {
|
||||
delete agent.skills;
|
||||
agent.skills_enabled = true;
|
||||
agent.skills_scope = SkillsScope.none;
|
||||
return agent;
|
||||
}
|
||||
|
||||
if (configuredScope === SkillsScope.all) {
|
||||
delete agent.skills;
|
||||
agent.skills_enabled = true;
|
||||
agent.skills_scope = SkillsScope.all;
|
||||
return agent;
|
||||
}
|
||||
|
||||
const configuredSkills = Array.isArray(agent.skills) ? agent.skills : [];
|
||||
if (configuredSkills.length === 0) {
|
||||
// Legacy empty allowlists mean the viewer's full accessible catalog;
|
||||
// explicit selected scope remains an intentionally empty catalog.
|
||||
delete agent.skills;
|
||||
agent.skills_enabled = true;
|
||||
if (configuredScope === SkillsScope.selected) {
|
||||
agent.skills_scope = SkillsScope.selected;
|
||||
}
|
||||
return agent;
|
||||
}
|
||||
|
||||
|
|
@ -262,11 +283,18 @@ const sanitizeViewerSkillScope = (agent, accessibleSkillSet) => {
|
|||
|
||||
if (visibleSkills.length === 0) {
|
||||
delete agent.skills;
|
||||
if (configuredScope === SkillsScope.selected) {
|
||||
agent.skills_enabled = true;
|
||||
agent.skills_scope = SkillsScope.selected;
|
||||
}
|
||||
return agent;
|
||||
}
|
||||
|
||||
agent.skills = visibleSkills;
|
||||
agent.skills_enabled = true;
|
||||
if (configuredScope === SkillsScope.selected) {
|
||||
agent.skills_scope = SkillsScope.selected;
|
||||
}
|
||||
return agent;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ const { v4: uuidv4 } = require('uuid');
|
|||
const { agentSchema, aclEntrySchema, fileSchema, userSchema } = require('@librechat/data-schemas');
|
||||
const {
|
||||
Tools,
|
||||
SkillsScope,
|
||||
FileSources,
|
||||
PermissionBits,
|
||||
PrincipalModel,
|
||||
|
|
@ -3210,6 +3211,34 @@ describe('Agent Controllers - Mass Assignment Protection', () => {
|
|||
expect(response.data[0].skills_enabled).toBe(true);
|
||||
});
|
||||
|
||||
test('should preserve an enabled empty catalog for VIEW list callers', async () => {
|
||||
await Agent.findByIdAndUpdate(agentA1._id, {
|
||||
skills_enabled: false,
|
||||
skill_authoring_enabled: true,
|
||||
skills_scope: SkillsScope.none,
|
||||
skills: [],
|
||||
});
|
||||
|
||||
mockReq.user.id = userB.toString();
|
||||
mockReq.query.requiredPermission = String(PermissionBits.VIEW);
|
||||
findAccessibleResources.mockImplementation(({ resourceType }) => {
|
||||
if (resourceType === ResourceType.AGENT) {
|
||||
return Promise.resolve([agentA1._id]);
|
||||
}
|
||||
return Promise.resolve([]);
|
||||
});
|
||||
findPubliclyAccessibleResources.mockResolvedValue([]);
|
||||
|
||||
await getListAgentsHandler(mockReq, mockRes);
|
||||
|
||||
const response = mockRes.json.mock.calls[0][0];
|
||||
expect(response.data).toHaveLength(1);
|
||||
expect(response.data[0].skills).toBeUndefined();
|
||||
expect(response.data[0].skills_enabled).toBeUndefined();
|
||||
expect(response.data[0].skill_authoring_enabled).toBe(true);
|
||||
expect(response.data[0].skills_scope).toBeUndefined();
|
||||
});
|
||||
|
||||
test('should return raw skill configuration for EDIT list callers', async () => {
|
||||
const visibleSkillId = new mongoose.Types.ObjectId();
|
||||
const hiddenSkillId = new mongoose.Types.ObjectId();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue