@lobehub/lobehub 2.0.0-next.347 → 2.0.0-next.349

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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,59 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 2.0.0-next.349](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.348...v2.0.0-next.349)
6
+
7
+ <sup>Released on **2026-01-23**</sup>
8
+
9
+ #### 🐛 Bug Fixes
10
+
11
+ - **misc**: When use market group, the group sys role was not used.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's fixed
19
+
20
+ - **misc**: When use market group, the group sys role was not used, closes [#11739](https://github.com/lobehub/lobe-chat/issues/11739) ([afc76f9](https://github.com/lobehub/lobe-chat/commit/afc76f9))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
30
+ ## [Version 2.0.0-next.348](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.347...v2.0.0-next.348)
31
+
32
+ <sup>Released on **2026-01-23**</sup>
33
+
34
+ #### 🐛 Bug Fixes
35
+
36
+ - **copilot**: History popover not refreshing when agentId changes.
37
+ - **misc**: Fixed the agent group builder tools excaution edge case crash, fixed the group topic copy not right.
38
+
39
+ <br/>
40
+
41
+ <details>
42
+ <summary><kbd>Improvements and Fixes</kbd></summary>
43
+
44
+ #### What's fixed
45
+
46
+ - **copilot**: History popover not refreshing when agentId changes, closes [#11731](https://github.com/lobehub/lobe-chat/issues/11731) ([64f39e7](https://github.com/lobehub/lobe-chat/commit/64f39e7))
47
+ - **misc**: Fixed the agent group builder tools excaution edge case crash, closes [#11735](https://github.com/lobehub/lobe-chat/issues/11735) ([5de4742](https://github.com/lobehub/lobe-chat/commit/5de4742))
48
+ - **misc**: Fixed the group topic copy not right, closes [#11730](https://github.com/lobehub/lobe-chat/issues/11730) ([282c1fb](https://github.com/lobehub/lobe-chat/commit/282c1fb))
49
+
50
+ </details>
51
+
52
+ <div align="right">
53
+
54
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
55
+
56
+ </div>
57
+
5
58
  ## [Version 2.0.0-next.347](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.346...v2.0.0-next.347)
6
59
 
7
60
  <sup>Released on **2026-01-23**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,22 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "fixes": [
5
+ "When use market group, the group sys role was not used."
6
+ ]
7
+ },
8
+ "date": "2026-01-23",
9
+ "version": "2.0.0-next.349"
10
+ },
11
+ {
12
+ "children": {
13
+ "fixes": [
14
+ "Fixed the agent group builder tools excaution edge case crash, fixed the group topic copy not right."
15
+ ]
16
+ },
17
+ "date": "2026-01-23",
18
+ "version": "2.0.0-next.348"
19
+ },
2
20
  {
3
21
  "children": {
4
22
  "fixes": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/lobehub",
3
- "version": "2.0.0-next.347",
3
+ "version": "2.0.0-next.349",
4
4
  "description": "LobeHub - an open-source,comprehensive AI Agent framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -81,7 +81,7 @@ export const BatchCreateAgentsInspector = memo<
81
81
  {displayInfo && (
82
82
  <>
83
83
  <div className={styles.avatarGroup}>
84
- {displayInfo.displayAgents.map((agent, index) => (
84
+ {displayInfo.displayAgents?.map((agent, index) => (
85
85
  <Avatar
86
86
  avatar={agent.avatar}
87
87
  key={index}
@@ -2,7 +2,7 @@ import { eq, inArray } from 'drizzle-orm';
2
2
  import { afterEach, beforeEach, describe, expect, it } from 'vitest';
3
3
 
4
4
  import { getTestDB } from '../../../core/getTestDB';
5
- import { agents, messages, sessions, topics, users } from '../../../schemas';
5
+ import { agents, messagePlugins, messages, sessions, topics, users } from '../../../schemas';
6
6
  import { LobeChatDatabase } from '../../../type';
7
7
  import { CreateTopicParams, TopicModel } from '../../topic';
8
8
 
@@ -279,6 +279,129 @@ describe('TopicModel - Create', () => {
279
279
  expect(duplicatedMessages[1].content).toBe('Assistant message');
280
280
  });
281
281
 
282
+ it('should correctly map parentId references when duplicating messages', async () => {
283
+ const topicId = 'topic-with-parent-refs';
284
+
285
+ await serverDB.transaction(async (tx) => {
286
+ await tx.insert(topics).values({ id: topicId, sessionId, userId, title: 'Original Topic' });
287
+ await tx.insert(messages).values([
288
+ { id: 'msg1', role: 'user', topicId, userId, content: 'First message', parentId: null },
289
+ {
290
+ id: 'msg2',
291
+ role: 'assistant',
292
+ topicId,
293
+ userId,
294
+ content: 'Reply to first',
295
+ parentId: 'msg1',
296
+ },
297
+ {
298
+ id: 'msg3',
299
+ role: 'tool',
300
+ topicId,
301
+ userId,
302
+ content: 'Tool response',
303
+ parentId: 'msg2',
304
+ },
305
+ {
306
+ id: 'msg4',
307
+ role: 'assistant',
308
+ topicId,
309
+ userId,
310
+ content: 'Final message',
311
+ parentId: 'msg3',
312
+ },
313
+ ]);
314
+ });
315
+
316
+ const { topic: duplicatedTopic, messages: duplicatedMessages } = await topicModel.duplicate(
317
+ topicId,
318
+ 'Duplicated Topic',
319
+ );
320
+
321
+ expect(duplicatedMessages).toHaveLength(4);
322
+
323
+ const msgMap = new Map(duplicatedMessages.map((m) => [m.content, m]));
324
+ const newMsg1 = msgMap.get('First message')!;
325
+ const newMsg2 = msgMap.get('Reply to first')!;
326
+ const newMsg3 = msgMap.get('Tool response')!;
327
+ const newMsg4 = msgMap.get('Final message')!;
328
+
329
+ expect(newMsg1.parentId).toBeNull();
330
+ expect(newMsg2.parentId).toBe(newMsg1.id);
331
+ expect(newMsg3.parentId).toBe(newMsg2.id);
332
+ expect(newMsg4.parentId).toBe(newMsg3.id);
333
+
334
+ expect(newMsg1.id).not.toBe('msg1');
335
+ expect(newMsg2.id).not.toBe('msg2');
336
+ expect(newMsg3.id).not.toBe('msg3');
337
+ expect(newMsg4.id).not.toBe('msg4');
338
+ });
339
+
340
+ it('should correctly map tool_call_id when duplicating messages with tools', async () => {
341
+ const topicId = 'topic-with-tools';
342
+ const originalToolId = 'toolu_original_123';
343
+
344
+ await serverDB.transaction(async (tx) => {
345
+ await tx.insert(topics).values({ id: topicId, sessionId, userId, title: 'Original Topic' });
346
+
347
+ // Insert assistant message with tools
348
+ await tx.insert(messages).values({
349
+ id: 'msg1',
350
+ role: 'assistant',
351
+ topicId,
352
+ userId,
353
+ content: 'Using tool',
354
+ parentId: null,
355
+ tools: [{ id: originalToolId, type: 'builtin', apiName: 'broadcast' }],
356
+ });
357
+
358
+ // Insert tool message
359
+ await tx.insert(messages).values({
360
+ id: 'msg2',
361
+ role: 'tool',
362
+ topicId,
363
+ userId,
364
+ content: 'Tool response',
365
+ parentId: 'msg1',
366
+ });
367
+
368
+ // Insert messagePlugins entry
369
+ await tx.insert(messagePlugins).values({
370
+ id: 'msg2',
371
+ userId,
372
+ toolCallId: originalToolId,
373
+ apiName: 'broadcast',
374
+ });
375
+ });
376
+
377
+ const { topic: duplicatedTopic, messages: duplicatedMessages } = await topicModel.duplicate(
378
+ topicId,
379
+ 'Duplicated Topic',
380
+ );
381
+
382
+ expect(duplicatedMessages).toHaveLength(2);
383
+
384
+ const msgMap = new Map(duplicatedMessages.map((m) => [m.role, m]));
385
+ const newAssistant = msgMap.get('assistant')!;
386
+ const newTool = msgMap.get('tool')!;
387
+
388
+ // Check that tools array has new IDs
389
+ expect(newAssistant.tools).toBeDefined();
390
+ const newTools = newAssistant.tools as any[];
391
+ expect(newTools).toHaveLength(1);
392
+ expect(newTools[0].id).not.toBe(originalToolId);
393
+ expect(newTools[0].id).toMatch(/^toolu_/);
394
+
395
+ // Check that messagePlugins was copied with new toolCallId
396
+ const newPlugin = await serverDB.query.messagePlugins.findFirst({
397
+ where: eq(messagePlugins.id, newTool.id),
398
+ });
399
+
400
+ expect(newPlugin).toBeDefined();
401
+ expect(newPlugin!.toolCallId).toBe(newTools[0].id);
402
+ expect(newPlugin!.toolCallId).not.toBe(originalToolId);
403
+ });
404
+
282
405
  it('should throw an error if the topic to duplicate does not exist', async () => {
283
406
  const topicId = 'nonexistent-topic';
284
407
 
@@ -17,7 +17,14 @@ import {
17
17
  sql,
18
18
  } from 'drizzle-orm';
19
19
 
20
- import { TopicItem, agents, agentsToSessions, messages, topics } from '../schemas';
20
+ import {
21
+ TopicItem,
22
+ agents,
23
+ agentsToSessions,
24
+ messagePlugins,
25
+ messages,
26
+ topics,
27
+ } from '../schemas';
21
28
  import { LobeChatDatabase } from '../type';
22
29
  import { genEndDateWhere, genRangeWhere, genStartDateWhere, genWhere } from '../utils/genWhere';
23
30
  import { idGenerator } from '../utils/idGenerator';
@@ -498,28 +505,83 @@ export class TopicModel {
498
505
  })
499
506
  .returning();
500
507
 
501
- // Find messages associated with the original topic
508
+ // Find messages associated with the original topic, ordered by createdAt
502
509
  const originalMessages = await tx
503
510
  .select()
504
511
  .from(messages)
505
- .where(and(eq(messages.topicId, topicId), eq(messages.userId, this.userId)));
506
-
507
- // copy messages
508
- const duplicatedMessages = await Promise.all(
509
- originalMessages.map(async (message) => {
510
- const result = (await tx
511
- .insert(messages)
512
- .values({
513
- ...message,
514
- clientId: null,
515
- id: idGenerator('messages'),
516
- topicId: duplicatedTopic.id,
517
- })
518
- .returning()) as DBMessageItem[];
519
-
520
- return result[0];
521
- }),
522
- );
512
+ .where(and(eq(messages.topicId, topicId), eq(messages.userId, this.userId)))
513
+ .orderBy(messages.createdAt);
514
+
515
+ // Find all messagePlugins for this topic
516
+ const messageIds = originalMessages.map((m) => m.id);
517
+ const originalPlugins =
518
+ messageIds.length > 0
519
+ ? await tx
520
+ .select()
521
+ .from(messagePlugins)
522
+ .where(inArray(messagePlugins.id, messageIds))
523
+ : [];
524
+
525
+ // Build oldId -> newId mapping for messages
526
+ const idMap = new Map<string, string>();
527
+ originalMessages.forEach((message) => {
528
+ idMap.set(message.id, idGenerator('messages'));
529
+ });
530
+
531
+ // Build oldToolId -> newToolId mapping for tools
532
+ const toolIdMap = new Map<string, string>();
533
+ originalMessages.forEach((message) => {
534
+ if (message.tools && Array.isArray(message.tools)) {
535
+ (message.tools as any[]).forEach((tool: any) => {
536
+ if (tool.id) {
537
+ toolIdMap.set(tool.id, `toolu_${idGenerator('messages')}`);
538
+ }
539
+ });
540
+ }
541
+ });
542
+
543
+ // copy messages sequentially to respect foreign key constraints
544
+ const duplicatedMessages: DBMessageItem[] = [];
545
+ for (const message of originalMessages) {
546
+ const newId = idMap.get(message.id)!;
547
+ const newParentId = message.parentId ? idMap.get(message.parentId) || null : null;
548
+
549
+ // Update tool IDs in tools array
550
+ let newTools = message.tools;
551
+ if (newTools && Array.isArray(newTools)) {
552
+ newTools = (newTools as any[]).map((tool: any) => ({
553
+ ...tool,
554
+ id: tool.id ? toolIdMap.get(tool.id) || tool.id : tool.id,
555
+ }));
556
+ }
557
+
558
+ const result = (await tx
559
+ .insert(messages)
560
+ .values({
561
+ ...message,
562
+ clientId: null,
563
+ id: newId,
564
+ parentId: newParentId,
565
+ topicId: duplicatedTopic.id,
566
+ tools: newTools,
567
+ })
568
+ .returning()) as DBMessageItem[];
569
+
570
+ duplicatedMessages.push(result[0]);
571
+
572
+ // Copy messagePlugins if exists for this message
573
+ const plugin = originalPlugins.find((p) => p.id === message.id);
574
+ if (plugin) {
575
+ const newToolCallId = plugin.toolCallId ? toolIdMap.get(plugin.toolCallId) || null : null;
576
+
577
+ await tx.insert(messagePlugins).values({
578
+ ...plugin,
579
+ id: newId,
580
+ clientId: null,
581
+ toolCallId: newToolCallId,
582
+ });
583
+ }
584
+ }
523
585
 
524
586
  return {
525
587
  messages: duplicatedMessages,
@@ -130,7 +130,7 @@ const ForkGroupAndChat = memo<{ mobile?: boolean }>(() => {
130
130
  forkedFromIdentifier: identifier, // Store the source group identifier
131
131
  },
132
132
  // Group content is the supervisor's systemRole (for backward compatibility)
133
- content: supervisorConfig?.systemRole || config.systemRole,
133
+ content: config.systemRole || supervisorConfig?.systemRole,
134
134
  ...meta,
135
135
  };
136
136
 
@@ -62,7 +62,7 @@ const CouncilList = memo<CouncilListProps>(({ members, displayMode, activeTab })
62
62
  minWidth: MIN_WIDTH * members.length + 32 + 32 * (members.length - 1),
63
63
  }}
64
64
  >
65
- {members.map((member, idx) => {
65
+ {members?.map((member, idx) => {
66
66
  if (!member) return null;
67
67
  return (
68
68
  <Fragment key={member.id}>
@@ -77,7 +77,7 @@ const CouncilList = memo<CouncilListProps>(({ members, displayMode, activeTab })
77
77
  >
78
78
  <CouncilMember index={idx} item={member} />
79
79
  </Flexbox>
80
- {idx < members.length - 1 && (
80
+ {idx < members?.length - 1 && (
81
81
  <Divider
82
82
  dashed
83
83
  orientation={'vertical'}
@@ -1,7 +1,7 @@
1
1
  import { ActionIcon, Block, Flexbox, Popover } from '@lobehub/ui';
2
2
  import { createStaticStyles, cx } from 'antd-style';
3
3
  import { ChevronsUpDownIcon, Clock3Icon, PanelRightCloseIcon, PlusIcon } from 'lucide-react';
4
- import { Suspense, memo, useMemo, useState } from 'react';
4
+ import { Suspense, memo, useCallback, useMemo, useState } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
6
 
7
7
  import AgentAvatar from '@/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentItem/Avatar';
@@ -164,6 +164,15 @@ const CopilotToolbar = memo<CopilotToolbarProps>(({ agentId, isHovered }) => {
164
164
  const setActiveAgentId = useAgentStore((s) => s.setActiveAgentId);
165
165
  const [topicPopoverOpen, setTopicPopoverOpen] = useState(false);
166
166
 
167
+ const handleAgentChange = useCallback(
168
+ (id: string) => {
169
+ setActiveAgentId(id);
170
+ // Sync chatStore's activeAgentId to ensure topic selectors work correctly
171
+ useChatStore.setState({ activeAgentId: id });
172
+ },
173
+ [setActiveAgentId],
174
+ );
175
+
167
176
  // Fetch topics for the agent builder
168
177
  useChatStore((s) => s.useFetchTopics)(true, { agentId });
169
178
 
@@ -175,13 +184,15 @@ const CopilotToolbar = memo<CopilotToolbarProps>(({ agentId, isHovered }) => {
175
184
 
176
185
  const [toggleRightPanel] = useGlobalStore((s) => [s.toggleRightPanel]);
177
186
 
178
- const hideHistory = !topics || topics.length === 0;
187
+ // topics === undefined means still loading, topics.length === 0 means confirmed empty
188
+ const isLoadingTopics = topics === undefined;
189
+ const hideHistory = !isLoadingTopics && topics.length === 0;
179
190
 
180
191
  return (
181
192
  <NavHeader
182
193
  left={
183
194
  <Flexbox align="center" gap={8} horizontal>
184
- <AgentSelector agentId={agentId} onAgentChange={setActiveAgentId} />
195
+ <AgentSelector agentId={agentId} onAgentChange={handleAgentChange} />
185
196
  </Flexbox>
186
197
  }
187
198
  right={
@@ -218,7 +229,7 @@ const CopilotToolbar = memo<CopilotToolbarProps>(({ agentId, isHovered }) => {
218
229
  </Flexbox>
219
230
  }
220
231
  onOpenChange={setTopicPopoverOpen}
221
- open={topicPopoverOpen}
232
+ open={isLoadingTopics ? false : topicPopoverOpen}
222
233
  placement="bottomRight"
223
234
  styles={{
224
235
  content: {
@@ -228,7 +239,12 @@ const CopilotToolbar = memo<CopilotToolbarProps>(({ agentId, isHovered }) => {
228
239
  }}
229
240
  trigger="click"
230
241
  >
231
- <ActionIcon icon={Clock3Icon} size={DESKTOP_HEADER_ICON_SIZE} />
242
+ <ActionIcon
243
+ disabled={isLoadingTopics}
244
+ icon={Clock3Icon}
245
+ loading={isLoadingTopics}
246
+ size={DESKTOP_HEADER_ICON_SIZE}
247
+ />
232
248
  </Popover>
233
249
  )}
234
250
  </div>
@@ -35,7 +35,6 @@ import { type LobeToolMetaWithAvailability } from '@/store/tool/slices/builtin/s
35
35
 
36
36
  import PluginTag from './PluginTag';
37
37
 
38
-
39
38
  const WEB_BROWSING_IDENTIFIER = 'lobe-web-browsing';
40
39
 
41
40
  type TabType = 'all' | 'installed';
@@ -334,7 +333,9 @@ const AgentTool = memo<AgentToolProps>(
334
333
  () => [
335
334
  // 原有的 builtin 工具
336
335
  ...filteredBuiltinList.map((item) => ({
337
- icon: <Avatar avatar={item.meta.avatar} size={20} style={{ flex: 'none', marginRight: 0 }} />,
336
+ icon: (
337
+ <Avatar avatar={item.meta.avatar} size={20} style={{ flex: 'none', marginRight: 0 }} />
338
+ ),
338
339
  key: item.identifier,
339
340
  label: (
340
341
  <ToolItem