👤 feat: Agent Avatar Removal and Decouple upload/reset from Agent Updates (#10527)

*  feat: Enhance agent avatar management with upload and reset functionality

*  feat: Refactor AvatarMenu to use DropdownPopup for improved UI and functionality

*  feat: Improve avatar upload handling in AgentPanel to suppress misleading "no changes" toast

*  feat: Refactor toast message handling and payload composition in AgentPanel for improved clarity and functionality

*  feat: Enhance agent avatar functionality with upload, reset, and validation improvements

*  feat: Refactor agent avatar upload handling and enhance related components for improved functionality and user experience

* feat(agents): tighten ACL, harden GETs/search, and sanitize action metadata
stop persisting refreshed S3 URLs on GET; compute per-response only
enforce ACL EDIT on revert route; remove legacy admin/author/collab checks
sanitize action metadata before persisting during duplication (api_key, oauth_client_id, oauth_client_secret)
escape user search input, cap length (100), and use Set for public flag mapping
add explicit req.file guard in avatar upload; fix empty catch lint; remove unused imports

* feat: Remove outdated avatar-related translation keys

* feat: Improve error logging for avatar updates and streamline file input handling

* feat(agents): implement caching for S3 avatar refresh in agent list responses

* fix: replace unconventional 'void e' with explicit comment to clarify intentionally ignored error

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* feat(agents): enhance avatar handling and improve search functionality

* fix: clarify intentionally ignored error in agent list handler

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Marco Beretta 2025-11-17 23:04:01 +01:00 committed by GitHub
parent c0cb48256e
commit 8907bd5d7c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 931 additions and 398 deletions

View file

@ -47,6 +47,7 @@ jest.mock('~/server/services/PermissionService', () => ({
findPubliclyAccessibleResources: jest.fn().mockResolvedValue([]),
grantPermission: jest.fn(),
hasPublicPermission: jest.fn().mockResolvedValue(false),
checkPermission: jest.fn().mockResolvedValue(true),
}));
jest.mock('~/models', () => ({
@ -573,6 +574,68 @@ describe('Agent Controllers - Mass Assignment Protection', () => {
expect(updatedAgent.version).toBe(agentInDb.versions.length);
});
test('should allow resetting avatar when value is explicitly null', async () => {
await Agent.updateOne(
{ id: existingAgentId },
{
avatar: {
filepath: 'https://example.com/avatar.png',
source: 's3',
},
},
);
mockReq.user.id = existingAgentAuthorId.toString();
mockReq.params.id = existingAgentId;
mockReq.body = {
avatar: null,
};
await updateAgentHandler(mockReq, mockRes);
const updatedAgent = mockRes.json.mock.calls[0][0];
expect(updatedAgent.avatar).toBeNull();
const agentInDb = await Agent.findOne({ id: existingAgentId });
expect(agentInDb.avatar).toBeNull();
});
test('should ignore avatar field when value is undefined', async () => {
const originalAvatar = {
filepath: 'https://example.com/original.png',
source: 's3',
};
await Agent.updateOne({ id: existingAgentId }, { avatar: originalAvatar });
mockReq.user.id = existingAgentAuthorId.toString();
mockReq.params.id = existingAgentId;
mockReq.body = {
avatar: undefined,
};
await updateAgentHandler(mockReq, mockRes);
const agentInDb = await Agent.findOne({ id: existingAgentId });
expect(agentInDb.avatar.filepath).toBe(originalAvatar.filepath);
expect(agentInDb.avatar.source).toBe(originalAvatar.source);
});
test('should not bump version when no mutable fields change', async () => {
const existingAgent = await Agent.findOne({ id: existingAgentId });
const originalVersionCount = existingAgent.versions.length;
mockReq.user.id = existingAgentAuthorId.toString();
mockReq.params.id = existingAgentId;
mockReq.body = {
avatar: undefined,
};
await updateAgentHandler(mockReq, mockRes);
const agentInDb = await Agent.findOne({ id: existingAgentId });
expect(agentInDb.versions.length).toBe(originalVersionCount);
});
test('should handle validation errors properly', async () => {
mockReq.user.id = existingAgentAuthorId.toString();
mockReq.params.id = existingAgentId;