@lobehub/lobehub 2.0.0-next.306 → 2.0.0-next.307

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/.vscode/settings.json +18 -3
  2. package/CHANGELOG.md +28 -0
  3. package/changelog/v1.json +9 -0
  4. package/package.json +2 -2
  5. package/packages/builtin-agents/src/agents/group-supervisor/index.ts +1 -7
  6. package/packages/builtin-tool-group-agent-builder/src/ExecutionRuntime/index.ts +29 -0
  7. package/packages/builtin-tool-group-agent-builder/src/executor.ts +18 -0
  8. package/packages/builtin-tool-group-agent-builder/src/manifest.ts +17 -0
  9. package/packages/builtin-tool-group-agent-builder/src/types.ts +10 -0
  10. package/packages/builtin-tool-group-management/src/executor.test.ts +0 -12
  11. package/packages/builtin-tool-group-management/src/executor.ts +8 -47
  12. package/packages/builtin-tool-group-management/src/manifest.ts +0 -17
  13. package/packages/builtin-tool-group-management/src/systemRole.ts +1 -8
  14. package/packages/builtin-tool-group-management/src/types.ts +0 -10
  15. package/packages/builtin-tool-local-system/src/ExecutionRuntime/index.ts +70 -31
  16. package/packages/builtin-tool-local-system/src/executor/index.ts +94 -60
  17. package/packages/database/src/repositories/agentGroup/index.ts +23 -0
  18. package/packages/prompts/src/prompts/fileSystem/formatCommandOutput.test.ts +61 -0
  19. package/packages/prompts/src/prompts/fileSystem/formatCommandOutput.ts +21 -0
  20. package/packages/prompts/src/prompts/fileSystem/formatCommandResult.test.ts +87 -0
  21. package/packages/prompts/src/prompts/fileSystem/formatCommandResult.ts +35 -0
  22. package/packages/prompts/src/prompts/fileSystem/formatEditResult.test.ts +57 -0
  23. package/packages/prompts/src/prompts/fileSystem/formatEditResult.ts +17 -0
  24. package/packages/prompts/src/prompts/fileSystem/formatFileContent.test.ts +59 -0
  25. package/packages/prompts/src/prompts/fileSystem/formatFileContent.ts +14 -0
  26. package/packages/prompts/src/prompts/fileSystem/formatFileList.test.ts +62 -0
  27. package/packages/prompts/src/prompts/fileSystem/formatFileList.ts +13 -0
  28. package/packages/prompts/src/prompts/fileSystem/formatFileSearchResults.test.ts +34 -0
  29. package/packages/prompts/src/prompts/fileSystem/formatFileSearchResults.ts +12 -0
  30. package/packages/prompts/src/prompts/fileSystem/formatGlobResults.test.ts +64 -0
  31. package/packages/prompts/src/prompts/fileSystem/formatGlobResults.ts +23 -0
  32. package/packages/prompts/src/prompts/fileSystem/formatGrepResults.test.ts +85 -0
  33. package/packages/prompts/src/prompts/fileSystem/formatGrepResults.ts +24 -0
  34. package/packages/prompts/src/prompts/fileSystem/formatKillResult.test.ts +30 -0
  35. package/packages/prompts/src/prompts/fileSystem/formatKillResult.ts +9 -0
  36. package/packages/prompts/src/prompts/fileSystem/formatMoveResults.test.ts +37 -0
  37. package/packages/prompts/src/prompts/fileSystem/formatMoveResults.ts +20 -0
  38. package/packages/prompts/src/prompts/fileSystem/formatMultipleFiles.test.ts +54 -0
  39. package/packages/prompts/src/prompts/fileSystem/formatMultipleFiles.ts +9 -0
  40. package/packages/prompts/src/prompts/fileSystem/formatRenameResult.test.ts +35 -0
  41. package/packages/prompts/src/prompts/fileSystem/formatRenameResult.ts +17 -0
  42. package/packages/prompts/src/prompts/fileSystem/formatWriteResult.test.ts +30 -0
  43. package/packages/prompts/src/prompts/fileSystem/formatWriteResult.ts +11 -0
  44. package/packages/prompts/src/prompts/fileSystem/index.ts +13 -0
  45. package/packages/prompts/src/prompts/index.ts +1 -0
  46. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Topic/Actions.tsx +4 -3
  47. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Topic/useDropdownMenu.tsx +12 -2
  48. package/src/app/[variants]/(main)/community/(detail)/group_agent/features/Sidebar/ActionButton/AddGroupAgent.tsx +69 -17
  49. package/src/app/[variants]/(main)/group/_layout/Sidebar/Topic/Actions.tsx +4 -3
  50. package/src/app/[variants]/(main)/group/_layout/Sidebar/Topic/useDropdownMenu.tsx +12 -2
  51. package/src/features/ChatInput/ActionBar/Upload/ServerMode.tsx +13 -3
  52. package/src/features/ChatInput/ActionBar/components/ActionDropdown.tsx +26 -3
  53. package/src/features/ResourceManager/components/Header/AddButton.tsx +20 -3
  54. package/src/server/routers/lambda/__tests__/agentGroup.test.ts +1 -0
  55. package/src/server/routers/lambda/agentGroup.ts +22 -0
  56. package/src/services/chat/index.ts +1 -0
  57. package/src/services/chat/mecha/agentConfigResolver.test.ts +62 -45
  58. package/src/services/chat/mecha/agentConfigResolver.ts +29 -27
  59. package/src/services/chatGroup/index.ts +14 -0
  60. package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +6 -2
@@ -21,9 +21,16 @@ const hotArea = css`
21
21
  }
22
22
  `;
23
23
 
24
- export const useTopicActionsDropdownMenu = (): MenuProps['items'] => {
24
+ interface UseTopicActionsDropdownMenuOptions {
25
+ onUploadClose?: () => void;
26
+ }
27
+
28
+ export const useTopicActionsDropdownMenu = (
29
+ options: UseTopicActionsDropdownMenuOptions = {},
30
+ ): MenuProps['items'] => {
25
31
  const { t } = useTranslation(['topic', 'common']);
26
32
  const { modal } = App.useApp();
33
+ const { onUploadClose } = options;
27
34
 
28
35
  const [removeUnstarredTopic, removeAllTopic, importTopic] = useChatStore((s) => [
29
36
  s.removeUnstarredTopic,
@@ -33,6 +40,7 @@ export const useTopicActionsDropdownMenu = (): MenuProps['items'] => {
33
40
 
34
41
  const handleImport = useCallback(
35
42
  async (file: File) => {
43
+ onUploadClose?.();
36
44
  try {
37
45
  const text = await file.text();
38
46
  // Validate JSON format
@@ -46,7 +54,7 @@ export const useTopicActionsDropdownMenu = (): MenuProps['items'] => {
46
54
  }
47
55
  return false; // Prevent default upload behavior
48
56
  },
49
- [importTopic, modal, t],
57
+ [importTopic, modal, onUploadClose, t],
50
58
  );
51
59
 
52
60
  const [topicDisplayMode, updatePreference] = useUserStore((s) => [
@@ -101,6 +109,7 @@ export const useTopicActionsDropdownMenu = (): MenuProps['items'] => {
101
109
  <div className={cx(hotArea)}>{t('actions.import')}</div>
102
110
  </Upload>
103
111
  ),
112
+ ...(onUploadClose ? { closeOnClick: false } : null),
104
113
  },
105
114
  {
106
115
  type: 'divider' as const,
@@ -143,6 +152,7 @@ export const useTopicActionsDropdownMenu = (): MenuProps['items'] => {
143
152
  updatePreference,
144
153
  updateSystemStatus,
145
154
  handleImport,
155
+ onUploadClose,
146
156
  removeUnstarredTopic,
147
157
  removeAllTopic,
148
158
  t,
@@ -87,6 +87,42 @@ const AddGroupAgent = memo<{ mobile?: boolean }>(() => {
87
87
  return;
88
88
  }
89
89
 
90
+ // Find supervisor from memberAgents
91
+ const supervisorMember = memberAgents.find((member: any) => {
92
+ const agent = member.agent || member;
93
+ const role = member.role || agent.role;
94
+ return role === 'supervisor';
95
+ });
96
+
97
+ // Prepare supervisor config
98
+ let supervisorConfig;
99
+ if (supervisorMember) {
100
+ // Type assertion needed because actual API data structure differs from type definition
101
+ const member = supervisorMember as any;
102
+ const agent = member.agent || member;
103
+ const currentVersion = member.currentVersion || member;
104
+ const rawConfig = {
105
+ avatar: currentVersion.avatar,
106
+ backgroundColor: currentVersion.backgroundColor,
107
+ description: currentVersion.description,
108
+ model: currentVersion.config?.model || currentVersion.model,
109
+ params: currentVersion.config?.params || currentVersion.params,
110
+ provider: currentVersion.config?.provider || currentVersion.provider,
111
+ systemRole:
112
+ currentVersion.config?.systemRole ||
113
+ currentVersion.config?.systemPrompt ||
114
+ currentVersion.systemRole ||
115
+ currentVersion.content,
116
+ tags: currentVersion.tags,
117
+ title: currentVersion.name || agent.name || 'Supervisor',
118
+ };
119
+ // Filter out null/undefined values
120
+ supervisorConfig = Object.fromEntries(
121
+ // eslint-disable-next-line eqeqeq, @typescript-eslint/no-unused-vars
122
+ Object.entries(rawConfig).filter(([_, v]) => v != null),
123
+ );
124
+ }
125
+
90
126
  // Prepare group config
91
127
  const groupConfig = {
92
128
  config: {
@@ -95,30 +131,46 @@ const AddGroupAgent = memo<{ mobile?: boolean }>(() => {
95
131
  openingQuestions: config.openingQuestions,
96
132
  revealDM: config.revealDM,
97
133
  },
98
- content: config.systemRole,
134
+ // Group content is the supervisor's systemRole (for backward compatibility)
135
+ content: supervisorConfig?.systemRole || config.systemRole,
99
136
  ...meta,
100
137
  };
101
138
 
102
139
  // Prepare member agents from market data
103
- const members = memberAgents.map((member: any) => {
104
- const agent = member.agent || member;
105
- const currentVersion = member.currentVersion || member;
106
- return {
107
- avatar: currentVersion.avatar,
108
- backgroundColor: currentVersion.backgroundColor,
109
- description: currentVersion.description,
110
- model: currentVersion.model,
111
- plugins: currentVersion.plugins,
112
- provider: currentVersion.provider,
113
- systemRole: currentVersion.systemRole || currentVersion.content,
114
- tags: currentVersion.tags,
115
- title: currentVersion.name || agent.name,
116
- };
117
- });
140
+ // Filter out supervisor role as it will be created separately using supervisorConfig
141
+ const members = memberAgents
142
+ .filter((member: any) => {
143
+ const agent = member.agent || member;
144
+ const role = member.role || agent.role;
145
+ return role !== 'supervisor';
146
+ })
147
+ .map((member: any) => {
148
+ const agent = member.agent || member;
149
+ const currentVersion = member.currentVersion || member;
150
+ return {
151
+ avatar: currentVersion.avatar,
152
+ backgroundColor: currentVersion.backgroundColor,
153
+ description: currentVersion.description,
154
+ model: currentVersion.config?.model || currentVersion.model,
155
+ plugins: currentVersion.plugins,
156
+ provider: currentVersion.config?.provider || currentVersion.provider,
157
+ systemRole:
158
+ currentVersion.config?.systemRole ||
159
+ currentVersion.config?.systemPrompt ||
160
+ currentVersion.systemRole ||
161
+ currentVersion.content,
162
+ tags: currentVersion.tags,
163
+ title: currentVersion.name || agent.name,
164
+ };
165
+ });
118
166
 
119
167
  try {
120
168
  // Create group with all members in one request
121
- const result = await chatGroupService.createGroupWithMembers(groupConfig, members);
169
+ const result = await chatGroupService.createGroupWithMembers(
170
+ groupConfig,
171
+ members,
172
+ supervisorConfig,
173
+ );
122
174
 
123
175
  // Refresh group list
124
176
  await loadGroups();
@@ -1,14 +1,15 @@
1
1
  import { ActionIcon, DropdownMenu } from '@lobehub/ui';
2
2
  import { MoreHorizontal } from 'lucide-react';
3
- import { memo } from 'react';
3
+ import { memo, useState } from 'react';
4
4
 
5
5
  import { useTopicActionsDropdownMenu } from './useDropdownMenu';
6
6
 
7
7
  const Actions = memo(() => {
8
- const menuItems = useTopicActionsDropdownMenu();
8
+ const [open, setOpen] = useState(false);
9
+ const menuItems = useTopicActionsDropdownMenu({ onUploadClose: () => setOpen(false) });
9
10
 
10
11
  return (
11
- <DropdownMenu items={menuItems}>
12
+ <DropdownMenu items={menuItems} onOpenChange={setOpen} open={open}>
12
13
  <ActionIcon icon={MoreHorizontal} size={'small'} />
13
14
  </DropdownMenu>
14
15
  );
@@ -21,9 +21,16 @@ const hotArea = css`
21
21
  }
22
22
  `;
23
23
 
24
- export const useTopicActionsDropdownMenu = (): MenuProps['items'] => {
24
+ interface UseTopicActionsDropdownMenuOptions {
25
+ onUploadClose?: () => void;
26
+ }
27
+
28
+ export const useTopicActionsDropdownMenu = (
29
+ options: UseTopicActionsDropdownMenuOptions = {},
30
+ ): MenuProps['items'] => {
25
31
  const { t } = useTranslation(['topic', 'common']);
26
32
  const { modal } = App.useApp();
33
+ const { onUploadClose } = options;
27
34
 
28
35
  const [removeUnstarredTopic, removeAllTopic, importTopic] = useChatStore((s) => [
29
36
  s.removeUnstarredTopic,
@@ -33,6 +40,7 @@ export const useTopicActionsDropdownMenu = (): MenuProps['items'] => {
33
40
 
34
41
  const handleImport = useCallback(
35
42
  async (file: File) => {
43
+ onUploadClose?.();
36
44
  try {
37
45
  const text = await file.text();
38
46
  // Validate JSON format
@@ -46,7 +54,7 @@ export const useTopicActionsDropdownMenu = (): MenuProps['items'] => {
46
54
  }
47
55
  return false; // Prevent default upload behavior
48
56
  },
49
- [importTopic, modal, t],
57
+ [importTopic, modal, onUploadClose, t],
50
58
  );
51
59
 
52
60
  const [topicDisplayMode, updatePreference] = useUserStore((s) => [
@@ -101,6 +109,7 @@ export const useTopicActionsDropdownMenu = (): MenuProps['items'] => {
101
109
  <div className={cx(hotArea)}>{t('actions.import')}</div>
102
110
  </Upload>
103
111
  ),
112
+ ...(onUploadClose ? { closeOnClick: false } : null),
104
113
  },
105
114
  {
106
115
  type: 'divider' as const,
@@ -143,6 +152,7 @@ export const useTopicActionsDropdownMenu = (): MenuProps['items'] => {
143
152
  updatePreference,
144
153
  updateSystemStatus,
145
154
  handleImport,
155
+ onUploadClose,
146
156
  removeUnstarredTopic,
147
157
  removeAllTopic,
148
158
  t,
@@ -1,5 +1,5 @@
1
1
  import { validateVideoFileSize } from '@lobechat/utils/client';
2
- import { Icon, type ItemType, type MenuProps, Tooltip } from '@lobehub/ui';
2
+ import { Icon, type ItemType, Tooltip } from '@lobehub/ui';
3
3
  import { Upload } from 'antd';
4
4
  import { css, cx } from 'antd-style';
5
5
  import isEqual from 'fast-deep-equal';
@@ -21,6 +21,7 @@ import { preferenceSelectors } from '@/store/user/selectors';
21
21
 
22
22
  import { useAgentId } from '../../hooks/useAgentId';
23
23
  import Action from '../components/Action';
24
+ import type { ActionDropdownMenuItems } from '../components/ActionDropdown';
24
25
  import CheckboxItem from '../components/CheckboxWithLoading';
25
26
 
26
27
  const hotArea = css`
@@ -48,6 +49,7 @@ const FileUpload = memo(() => {
48
49
  s.updateGuideState,
49
50
  ]);
50
51
  const [modalOpen, setModalOpen] = useState(false);
52
+ const [dropdownOpen, setDropdownOpen] = useState(false);
51
53
  const [updating, setUpdating] = useState(false);
52
54
 
53
55
  const files = useAgentStore((s) => agentByIdSelectors.getAgentFilesById(agentId)(s), isEqual);
@@ -61,8 +63,9 @@ const FileUpload = memo(() => {
61
63
  s.toggleKnowledgeBase,
62
64
  ]);
63
65
 
64
- const uploadItems: MenuProps['items'] = [
66
+ const uploadItems: ActionDropdownMenuItems = [
65
67
  {
68
+ closeOnClick: false,
66
69
  disabled: !canUploadImage,
67
70
  icon: ImageUp,
68
71
  key: 'upload-image',
@@ -70,6 +73,7 @@ const FileUpload = memo(() => {
70
73
  <Upload
71
74
  accept={'image/*'}
72
75
  beforeUpload={async (file) => {
76
+ setDropdownOpen(false);
73
77
  await upload([file]);
74
78
 
75
79
  return false;
@@ -86,6 +90,7 @@ const FileUpload = memo(() => {
86
90
  ),
87
91
  },
88
92
  {
93
+ closeOnClick: false,
89
94
  icon: FileUp,
90
95
  key: 'upload-file',
91
96
  label: (
@@ -105,6 +110,7 @@ const FileUpload = memo(() => {
105
110
  return false;
106
111
  }
107
112
 
113
+ setDropdownOpen(false);
108
114
  await upload([file]);
109
115
 
110
116
  return false;
@@ -117,6 +123,7 @@ const FileUpload = memo(() => {
117
123
  ),
118
124
  },
119
125
  {
126
+ closeOnClick: false,
120
127
  icon: FolderUp,
121
128
  key: 'upload-folder',
122
129
  label: (
@@ -136,6 +143,7 @@ const FileUpload = memo(() => {
136
143
  return false;
137
144
  }
138
145
 
146
+ setDropdownOpen(false);
139
147
  await upload([file]);
140
148
 
141
149
  return false;
@@ -214,7 +222,7 @@ const FileUpload = memo(() => {
214
222
  },
215
223
  );
216
224
 
217
- const items: MenuProps['items'] = [
225
+ const items: ActionDropdownMenuItems = [
218
226
  ...uploadItems,
219
227
  ...(knowledgeItems.length > 0 ? knowledgeItems : []),
220
228
  ];
@@ -229,6 +237,8 @@ const FileUpload = memo(() => {
229
237
  }}
230
238
  icon={Paperclip}
231
239
  loading={updating}
240
+ onOpenChange={setDropdownOpen}
241
+ open={dropdownOpen}
232
242
  showTooltip={false}
233
243
  title={t('upload.action.tooltip')}
234
244
  trigger={'both'}
@@ -8,6 +8,7 @@ import {
8
8
  type DropdownMenuProps,
9
9
  DropdownMenuRoot,
10
10
  DropdownMenuTrigger,
11
+ type MenuItemType,
11
12
  type MenuProps,
12
13
  type PopoverTrigger,
13
14
  renderDropdownMenuItems,
@@ -16,6 +17,7 @@ import { createStaticStyles, cx } from 'antd-style';
16
17
  import {
17
18
  type CSSProperties,
18
19
  type ReactNode,
20
+ isValidElement,
19
21
  memo,
20
22
  useCallback,
21
23
  useEffect,
@@ -34,8 +36,15 @@ const styles = createStaticStyles(({ css }) => ({
34
36
  `,
35
37
  }));
36
38
 
37
- type ActionDropdownMenu = Omit<Pick<MenuProps, 'className' | 'onClick' | 'style'>, 'items'> & {
38
- items: MenuProps['items'] | (() => MenuProps['items']);
39
+ export type ActionDropdownMenuItem = MenuItemType;
40
+
41
+ export type ActionDropdownMenuItems = MenuProps<ActionDropdownMenuItem>['items'];
42
+
43
+ type ActionDropdownMenu = Omit<
44
+ Pick<MenuProps<ActionDropdownMenuItem>, 'className' | 'onClick' | 'style'>,
45
+ 'items'
46
+ > & {
47
+ items: ActionDropdownMenuItems | (() => ActionDropdownMenuItems);
39
48
  };
40
49
 
41
50
  export interface ActionDropdownProps extends Omit<DropdownMenuProps, 'items'> {
@@ -116,7 +125,7 @@ const ActionDropdown = memo<ActionDropdownProps>(
116
125
  }, [openOnHover, triggerProps]);
117
126
 
118
127
  const decorateMenuItems = useCallback(
119
- (items: MenuProps['items']): MenuProps['items'] => {
128
+ (items: ActionDropdownMenuItems): ActionDropdownMenuItems => {
120
129
  if (!items) return items;
121
130
 
122
131
  return items.map((item) => {
@@ -136,10 +145,24 @@ const ActionDropdown = memo<ActionDropdownProps>(
136
145
  };
137
146
  }
138
147
  const itemOnClick = 'onClick' in item ? item.onClick : undefined;
148
+ const closeOnClick = 'closeOnClick' in item ? item.closeOnClick : undefined;
149
+ const keepOpenOnClick = closeOnClick === false;
150
+ const itemLabel = 'label' in item ? item.label : undefined;
151
+ const shouldKeepOpen = isValidElement(itemLabel);
152
+
153
+ const resolvedCloseOnClick = closeOnClick ?? (shouldKeepOpen ? false : undefined);
139
154
 
140
155
  return {
141
156
  ...item,
157
+ ...(resolvedCloseOnClick !== undefined ? { closeOnClick: resolvedCloseOnClick } : null),
142
158
  onClick: (info) => {
159
+ if (keepOpenOnClick) {
160
+ info.domEvent.stopPropagation();
161
+ menu.onClick?.(info);
162
+ itemOnClick?.(info);
163
+ return;
164
+ }
165
+
143
166
  info.domEvent.preventDefault();
144
167
  menu.onClick?.(info);
145
168
  itemOnClick?.(info);
@@ -5,7 +5,7 @@ import { Notion } from '@lobehub/icons';
5
5
  import { Button, DropdownMenu, Icon, type MenuProps } from '@lobehub/ui';
6
6
  import { Upload } from 'antd';
7
7
  import { FilePenLine, FileUp, FolderIcon, FolderUp, Link, Plus } from 'lucide-react';
8
- import { useCallback, useMemo } from 'react';
8
+ import { type ChangeEvent, useCallback, useMemo, useState } from 'react';
9
9
  import { useTranslation } from 'react-i18next';
10
10
 
11
11
  import { useResourceManagerStore } from '@/app/[variants]/(main)/resource/features/store';
@@ -23,6 +23,7 @@ const AddButton = () => {
23
23
  const pushDockFileList = useFileStore((s) => s.pushDockFileList);
24
24
  const uploadFolderWithStructure = useFileStore((s) => s.uploadFolderWithStructure);
25
25
  const createResourceAndSync = useFileStore((s) => s.createResourceAndSync);
26
+ const [menuOpen, setMenuOpen] = useState(false);
26
27
 
27
28
  // TODO: Migrate Notion import to use createResource
28
29
  // Keep old functions temporarily for components not yet migrated
@@ -121,6 +122,13 @@ const AddButton = () => {
121
122
  t,
122
123
  uploadFolderWithStructure,
123
124
  });
125
+ const handleFolderUploadWithClose = useCallback(
126
+ (event: ChangeEvent<HTMLInputElement>) => {
127
+ setMenuOpen(false);
128
+ return handleFolderUpload(event);
129
+ },
130
+ [handleFolderUpload],
131
+ );
124
132
 
125
133
  const items = useMemo<MenuProps['items']>(
126
134
  () => [
@@ -144,11 +152,13 @@ const AddButton = () => {
144
152
  type: 'divider',
145
153
  },
146
154
  {
155
+ closeOnClick: false,
147
156
  icon: <Icon icon={FileUp} />,
148
157
  key: 'upload-file',
149
158
  label: (
150
159
  <Upload
151
160
  beforeUpload={async (file) => {
161
+ setMenuOpen(false);
152
162
  await pushDockFileList([file], libraryId, currentFolderId ?? undefined);
153
163
 
154
164
  return false;
@@ -161,6 +171,7 @@ const AddButton = () => {
161
171
  ),
162
172
  },
163
173
  {
174
+ closeOnClick: false,
164
175
  icon: <Icon icon={FolderUp} />,
165
176
  key: 'upload-folder',
166
177
  label: <label htmlFor="folder-upload-input">{t('header.actions.uploadFolder')}</label>,
@@ -211,7 +222,13 @@ const AddButton = () => {
211
222
 
212
223
  return (
213
224
  <>
214
- <DropdownMenu items={items} placement="bottomRight" trigger="both">
225
+ <DropdownMenu
226
+ items={items}
227
+ onOpenChange={setMenuOpen}
228
+ open={menuOpen}
229
+ placement="bottomRight"
230
+ trigger="both"
231
+ >
215
232
  <Button data-no-highlight icon={Plus} type="primary">
216
233
  {t('addLibrary')}
217
234
  </Button>
@@ -233,7 +250,7 @@ const AddButton = () => {
233
250
  <input
234
251
  id="folder-upload-input"
235
252
  multiple
236
- onChange={handleFolderUpload}
253
+ onChange={handleFolderUploadWithClose}
237
254
  style={{ display: 'none' }}
238
255
  type="file"
239
256
  // @ts-expect-error - webkitdirectory is not in the React types
@@ -171,6 +171,7 @@ describe('agentGroupRouter', () => {
171
171
  config: { ...DEFAULT_CHAT_GROUP_CHAT_CONFIG, allowDM: true },
172
172
  },
173
173
  ['agent-1', 'agent-2'],
174
+ undefined,
174
175
  );
175
176
  expect(result).toEqual({
176
177
  agentIds: ['agent-1', 'agent-2'],
@@ -129,6 +129,19 @@ export const agentGroupRouter = router({
129
129
  })
130
130
  .partial(),
131
131
  ),
132
+ supervisorConfig: z
133
+ .object({
134
+ avatar: z.string().nullish(),
135
+ backgroundColor: z.string().nullish(),
136
+ description: z.string().nullish(),
137
+ model: z.string().nullish(),
138
+ params: z.any().nullish(),
139
+ provider: z.string().nullish(),
140
+ systemRole: z.string().nullish(),
141
+ tags: z.array(z.string()).nullish(),
142
+ title: z.string().nullish(),
143
+ })
144
+ .optional(),
132
145
  }),
133
146
  )
134
147
  .mutation(async ({ input, ctx }) => {
@@ -144,6 +157,14 @@ export const agentGroupRouter = router({
144
157
  const memberAgentIds = createdAgents.map((agent) => agent.id);
145
158
 
146
159
  // 2. Create group with supervisor and member agents
160
+ // Filter out null/undefined values from supervisorConfig
161
+ const supervisorConfig = input.supervisorConfig
162
+ ? Object.fromEntries(
163
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, eqeqeq
164
+ Object.entries(input.supervisorConfig).filter(([_, v]) => v != null),
165
+ )
166
+ : undefined;
167
+
147
168
  const { group, supervisorAgentId } = await ctx.agentGroupRepo.createGroupWithSupervisor(
148
169
  {
149
170
  ...input.groupConfig,
@@ -152,6 +173,7 @@ export const agentGroupRouter = router({
152
173
  ),
153
174
  },
154
175
  memberAgentIds,
176
+ supervisorConfig as any,
155
177
  );
156
178
 
157
179
  return { agentIds: memberAgentIds, groupId: group.id, supervisorAgentId };
@@ -138,6 +138,7 @@ class ChatService {
138
138
  plugins: pluginIds,
139
139
  } = resolveAgentConfig({
140
140
  agentId: targetAgentId,
141
+ groupId, // Pass groupId for supervisor detection
141
142
  model: payload.model,
142
143
  plugins: enabledPlugins,
143
144
  provider: payload.provider,