@lobehub/lobehub 2.0.0-next.305 → 2.0.0-next.306
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 +25 -0
- package/changelog/v1.json +9 -0
- package/e2e/src/steps/community/detail-pages.steps.ts +3 -1
- package/e2e/src/steps/community/interactions.steps.ts +4 -4
- package/package.json +1 -1
- package/packages/context-engine/src/processors/GroupMessageFlatten.ts +9 -6
- package/packages/context-engine/src/processors/__tests__/GroupMessageFlatten.test.ts +103 -0
- package/packages/context-engine/src/providers/GroupAgentBuilderContextInjector.ts +18 -31
- package/packages/context-engine/src/providers/__tests__/GroupAgentBuilderContextInjector.test.ts +307 -0
- package/packages/prompts/src/prompts/userMemory/__snapshots__/index.test.ts.snap +14 -38
- package/packages/prompts/src/prompts/userMemory/index.ts +5 -24
- package/src/app/[variants]/(main)/community/(detail)/assistant/index.tsx +1 -1
- package/src/app/[variants]/(main)/community/(detail)/mcp/index.tsx +1 -1
- package/src/app/[variants]/(main)/group/features/Conversation/MainChatInput/index.tsx +2 -2
- package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentItem/index.tsx +2 -2
- package/src/features/Conversation/Messages/Supervisor/index.tsx +2 -1
- package/src/features/Conversation/Messages/components/ContentLoading.tsx +8 -2
- package/src/services/chat/mecha/agentConfigResolver.ts +65 -0
- package/src/services/chat/mecha/modelParamsResolver.test.ts +211 -0
- package/src/store/agentGroup/action.ts +30 -0
- package/src/store/agentGroup/slices/lifecycle.test.ts +77 -18
- package/src/store/agentGroup/slices/lifecycle.ts +7 -9
- package/src/store/chat/slices/operation/__tests__/selectors.test.ts +124 -0
- package/src/store/chat/slices/operation/selectors.ts +22 -0
|
@@ -10,19 +10,31 @@ vi.mock('@/services/chatGroup', () => ({
|
|
|
10
10
|
chatGroupService: {
|
|
11
11
|
addAgentsToGroup: vi.fn(),
|
|
12
12
|
createGroup: vi.fn(),
|
|
13
|
+
getGroupDetail: vi.fn(),
|
|
13
14
|
getGroups: vi.fn(),
|
|
14
15
|
},
|
|
15
16
|
}));
|
|
16
17
|
|
|
17
|
-
vi.mock('@/store/
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
sessions: [],
|
|
22
|
-
switchSession: vi.fn(),
|
|
18
|
+
vi.mock('@/store/home', () => ({
|
|
19
|
+
getHomeStoreState: vi.fn(() => ({
|
|
20
|
+
refreshAgentList: vi.fn(),
|
|
21
|
+
switchToGroup: vi.fn(),
|
|
23
22
|
})),
|
|
24
23
|
}));
|
|
25
24
|
|
|
25
|
+
vi.mock('@/store/agent', () => ({
|
|
26
|
+
getAgentStoreState: vi.fn(() => ({
|
|
27
|
+
internal_dispatchAgentMap: vi.fn(),
|
|
28
|
+
setActiveAgentId: vi.fn(),
|
|
29
|
+
})),
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
vi.mock('@/store/chat', () => ({
|
|
33
|
+
useChatStore: {
|
|
34
|
+
setState: vi.fn(),
|
|
35
|
+
},
|
|
36
|
+
}));
|
|
37
|
+
|
|
26
38
|
describe('ChatGroupLifecycleSlice', () => {
|
|
27
39
|
beforeEach(() => {
|
|
28
40
|
vi.clearAllMocks();
|
|
@@ -47,12 +59,17 @@ describe('ChatGroupLifecycleSlice', () => {
|
|
|
47
59
|
title: 'Test Group',
|
|
48
60
|
userId: 'user-1',
|
|
49
61
|
};
|
|
62
|
+
const mockGroupDetail = {
|
|
63
|
+
...mockGroup,
|
|
64
|
+
agents: [],
|
|
65
|
+
supervisorAgentId: 'supervisor-1',
|
|
66
|
+
};
|
|
50
67
|
|
|
51
68
|
vi.mocked(chatGroupService.createGroup).mockResolvedValue({
|
|
52
69
|
group: mockGroup as any,
|
|
53
70
|
supervisorAgentId: 'supervisor-1',
|
|
54
71
|
});
|
|
55
|
-
vi.mocked(chatGroupService.
|
|
72
|
+
vi.mocked(chatGroupService.getGroupDetail).mockResolvedValue(mockGroupDetail as any);
|
|
56
73
|
|
|
57
74
|
const { result } = renderHook(() => useAgentGroupStore());
|
|
58
75
|
|
|
@@ -71,13 +88,18 @@ describe('ChatGroupLifecycleSlice', () => {
|
|
|
71
88
|
title: 'Test Group',
|
|
72
89
|
userId: 'user-1',
|
|
73
90
|
};
|
|
91
|
+
const mockGroupDetail = {
|
|
92
|
+
...mockGroup,
|
|
93
|
+
agents: [],
|
|
94
|
+
supervisorAgentId: 'supervisor-1',
|
|
95
|
+
};
|
|
74
96
|
|
|
75
97
|
vi.mocked(chatGroupService.createGroup).mockResolvedValue({
|
|
76
98
|
group: mockGroup as any,
|
|
77
99
|
supervisorAgentId: 'supervisor-1',
|
|
78
100
|
});
|
|
79
101
|
vi.mocked(chatGroupService.addAgentsToGroup).mockResolvedValue({ added: [], existing: [] });
|
|
80
|
-
vi.mocked(chatGroupService.
|
|
102
|
+
vi.mocked(chatGroupService.getGroupDetail).mockResolvedValue(mockGroupDetail as any);
|
|
81
103
|
|
|
82
104
|
const { result } = renderHook(() => useAgentGroupStore());
|
|
83
105
|
|
|
@@ -91,14 +113,46 @@ describe('ChatGroupLifecycleSlice', () => {
|
|
|
91
113
|
]);
|
|
92
114
|
});
|
|
93
115
|
|
|
94
|
-
it('should
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
116
|
+
it('should fetch group detail and store supervisorAgentId for tools injection', async () => {
|
|
117
|
+
const mockGroup = {
|
|
118
|
+
id: 'new-group-id',
|
|
119
|
+
title: 'Test Group',
|
|
120
|
+
userId: 'user-1',
|
|
121
|
+
};
|
|
122
|
+
const mockSupervisorAgentId = 'supervisor-agent-123';
|
|
123
|
+
const mockGroupDetail = {
|
|
124
|
+
...mockGroup,
|
|
125
|
+
agents: [],
|
|
126
|
+
supervisorAgentId: mockSupervisorAgentId,
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
vi.mocked(chatGroupService.createGroup).mockResolvedValue({
|
|
130
|
+
group: mockGroup as any,
|
|
131
|
+
supervisorAgentId: mockSupervisorAgentId,
|
|
132
|
+
});
|
|
133
|
+
vi.mocked(chatGroupService.getGroupDetail).mockResolvedValue(mockGroupDetail as any);
|
|
134
|
+
|
|
135
|
+
const { result } = renderHook(() => useAgentGroupStore());
|
|
136
|
+
|
|
137
|
+
await act(async () => {
|
|
138
|
+
await result.current.createGroup({ title: 'Test Group' });
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Verify getGroupDetail was called to fetch full group info
|
|
142
|
+
expect(chatGroupService.getGroupDetail).toHaveBeenCalledWith('new-group-id');
|
|
143
|
+
|
|
144
|
+
// Verify supervisorAgentId is stored in groupMap for tools injection
|
|
145
|
+
const groupDetail = result.current.groupMap['new-group-id'];
|
|
146
|
+
expect(groupDetail).toBeDefined();
|
|
147
|
+
expect(groupDetail.supervisorAgentId).toBe(mockSupervisorAgentId);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('should not switch to group when silent is true', async () => {
|
|
151
|
+
const mockSwitchToGroup = vi.fn();
|
|
152
|
+
const { getHomeStoreState } = await import('@/store/home');
|
|
153
|
+
vi.mocked(getHomeStoreState).mockReturnValue({
|
|
154
|
+
refreshAgentList: vi.fn(),
|
|
155
|
+
switchToGroup: mockSwitchToGroup,
|
|
102
156
|
} as any);
|
|
103
157
|
|
|
104
158
|
const mockGroup = {
|
|
@@ -106,12 +160,17 @@ describe('ChatGroupLifecycleSlice', () => {
|
|
|
106
160
|
title: 'Test Group',
|
|
107
161
|
userId: 'user-1',
|
|
108
162
|
};
|
|
163
|
+
const mockGroupDetail = {
|
|
164
|
+
...mockGroup,
|
|
165
|
+
agents: [],
|
|
166
|
+
supervisorAgentId: 'supervisor-1',
|
|
167
|
+
};
|
|
109
168
|
|
|
110
169
|
vi.mocked(chatGroupService.createGroup).mockResolvedValue({
|
|
111
170
|
group: mockGroup as any,
|
|
112
171
|
supervisorAgentId: 'supervisor-1',
|
|
113
172
|
});
|
|
114
|
-
vi.mocked(chatGroupService.
|
|
173
|
+
vi.mocked(chatGroupService.getGroupDetail).mockResolvedValue(mockGroupDetail as any);
|
|
115
174
|
|
|
116
175
|
const { result } = renderHook(() => useAgentGroupStore());
|
|
117
176
|
|
|
@@ -119,7 +178,7 @@ describe('ChatGroupLifecycleSlice', () => {
|
|
|
119
178
|
await result.current.createGroup({ title: 'Test Group' }, [], true);
|
|
120
179
|
});
|
|
121
180
|
|
|
122
|
-
expect(
|
|
181
|
+
expect(mockSwitchToGroup).not.toHaveBeenCalled();
|
|
123
182
|
});
|
|
124
183
|
});
|
|
125
184
|
});
|
|
@@ -5,7 +5,7 @@ import { type StateCreator } from 'zustand/vanilla';
|
|
|
5
5
|
import { chatGroupService } from '@/services/chatGroup';
|
|
6
6
|
import { type ChatGroupStore } from '@/store/agentGroup/store';
|
|
7
7
|
import { useChatStore } from '@/store/chat';
|
|
8
|
-
import {
|
|
8
|
+
import { getHomeStoreState } from '@/store/home';
|
|
9
9
|
|
|
10
10
|
export interface ChatGroupLifecycleAction {
|
|
11
11
|
createGroup: (
|
|
@@ -14,7 +14,6 @@ export interface ChatGroupLifecycleAction {
|
|
|
14
14
|
silent?: boolean,
|
|
15
15
|
) => Promise<string>;
|
|
16
16
|
/**
|
|
17
|
-
* @deprecated Use switchTopic(undefined) instead
|
|
18
17
|
* Switch to a new topic in the group
|
|
19
18
|
* Clears activeTopicId and navigates to group root
|
|
20
19
|
*/
|
|
@@ -32,11 +31,8 @@ export const chatGroupLifecycleSlice: StateCreator<
|
|
|
32
31
|
[],
|
|
33
32
|
ChatGroupLifecycleAction
|
|
34
33
|
> = (_, get) => ({
|
|
35
|
-
/**
|
|
36
|
-
* @param silent - if true, do not switch to the new group session
|
|
37
|
-
*/
|
|
38
34
|
createGroup: async (newGroup, agentIds, silent = false) => {
|
|
39
|
-
const {
|
|
35
|
+
const { switchToGroup, refreshAgentList } = getHomeStoreState();
|
|
40
36
|
|
|
41
37
|
const { group } = await chatGroupService.createGroup(newGroup);
|
|
42
38
|
|
|
@@ -52,11 +48,13 @@ export const chatGroupLifecycleSlice: StateCreator<
|
|
|
52
48
|
|
|
53
49
|
get().internal_dispatchChatGroup({ payload: group, type: 'addGroup' });
|
|
54
50
|
|
|
55
|
-
|
|
56
|
-
await
|
|
51
|
+
// Fetch full group detail to get supervisorAgentId and agents for tools injection
|
|
52
|
+
await get().internal_fetchGroupDetail(group.id);
|
|
53
|
+
|
|
54
|
+
refreshAgentList();
|
|
57
55
|
|
|
58
56
|
if (!silent) {
|
|
59
|
-
|
|
57
|
+
switchToGroup(group.id);
|
|
60
58
|
}
|
|
61
59
|
|
|
62
60
|
return group.id;
|
|
@@ -509,6 +509,130 @@ describe('Operation Selectors', () => {
|
|
|
509
509
|
});
|
|
510
510
|
});
|
|
511
511
|
|
|
512
|
+
describe('isAgentRunning', () => {
|
|
513
|
+
it('should return false when no operations exist', () => {
|
|
514
|
+
const { result } = renderHook(() => useChatStore());
|
|
515
|
+
|
|
516
|
+
expect(operationSelectors.isAgentRunning('agent1')(result.current)).toBe(false);
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
it('should return true only for the agent with running operations', () => {
|
|
520
|
+
const { result } = renderHook(() => useChatStore());
|
|
521
|
+
|
|
522
|
+
act(() => {
|
|
523
|
+
result.current.startOperation({
|
|
524
|
+
type: 'execAgentRuntime',
|
|
525
|
+
context: { agentId: 'agent1' },
|
|
526
|
+
});
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
expect(operationSelectors.isAgentRunning('agent1')(result.current)).toBe(true);
|
|
530
|
+
expect(operationSelectors.isAgentRunning('agent2')(result.current)).toBe(false);
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
it('should return false when operation completes', () => {
|
|
534
|
+
const { result } = renderHook(() => useChatStore());
|
|
535
|
+
|
|
536
|
+
let opId: string;
|
|
537
|
+
|
|
538
|
+
act(() => {
|
|
539
|
+
opId = result.current.startOperation({
|
|
540
|
+
type: 'execAgentRuntime',
|
|
541
|
+
context: { agentId: 'agent1' },
|
|
542
|
+
}).operationId;
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
expect(operationSelectors.isAgentRunning('agent1')(result.current)).toBe(true);
|
|
546
|
+
|
|
547
|
+
act(() => {
|
|
548
|
+
result.current.completeOperation(opId!);
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
expect(operationSelectors.isAgentRunning('agent1')(result.current)).toBe(false);
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
it('should exclude aborting operations', () => {
|
|
555
|
+
const { result } = renderHook(() => useChatStore());
|
|
556
|
+
|
|
557
|
+
let opId: string;
|
|
558
|
+
|
|
559
|
+
act(() => {
|
|
560
|
+
opId = result.current.startOperation({
|
|
561
|
+
type: 'execAgentRuntime',
|
|
562
|
+
context: { agentId: 'agent1' },
|
|
563
|
+
}).operationId;
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
expect(operationSelectors.isAgentRunning('agent1')(result.current)).toBe(true);
|
|
567
|
+
|
|
568
|
+
act(() => {
|
|
569
|
+
result.current.updateOperationMetadata(opId!, { isAborting: true });
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
expect(operationSelectors.isAgentRunning('agent1')(result.current)).toBe(false);
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
it('should detect any topic with running operations for the agent', () => {
|
|
576
|
+
const { result } = renderHook(() => useChatStore());
|
|
577
|
+
|
|
578
|
+
act(() => {
|
|
579
|
+
// Agent 1, topic 1
|
|
580
|
+
result.current.startOperation({
|
|
581
|
+
type: 'execAgentRuntime',
|
|
582
|
+
context: { agentId: 'agent1', topicId: 'topic1' },
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
// Agent 1, topic 2
|
|
586
|
+
result.current.startOperation({
|
|
587
|
+
type: 'execAgentRuntime',
|
|
588
|
+
context: { agentId: 'agent1', topicId: 'topic2' },
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
// Agent 2, topic 3
|
|
592
|
+
result.current.startOperation({
|
|
593
|
+
type: 'execAgentRuntime',
|
|
594
|
+
context: { agentId: 'agent2', topicId: 'topic3' },
|
|
595
|
+
});
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
// Agent 1 should be running (has 2 topics with operations)
|
|
599
|
+
expect(operationSelectors.isAgentRunning('agent1')(result.current)).toBe(true);
|
|
600
|
+
// Agent 2 should also be running
|
|
601
|
+
expect(operationSelectors.isAgentRunning('agent2')(result.current)).toBe(true);
|
|
602
|
+
// Agent 3 should not be running
|
|
603
|
+
expect(operationSelectors.isAgentRunning('agent3')(result.current)).toBe(false);
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
it('should detect server agent runtime operations', () => {
|
|
607
|
+
const { result } = renderHook(() => useChatStore());
|
|
608
|
+
|
|
609
|
+
act(() => {
|
|
610
|
+
result.current.startOperation({
|
|
611
|
+
type: 'execServerAgentRuntime',
|
|
612
|
+
context: { agentId: 'agent1', groupId: 'group1' },
|
|
613
|
+
});
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
expect(operationSelectors.isAgentRunning('agent1')(result.current)).toBe(true);
|
|
617
|
+
expect(operationSelectors.isAgentRunning('agent2')(result.current)).toBe(false);
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
it('should not detect non-AI-runtime operations', () => {
|
|
621
|
+
const { result } = renderHook(() => useChatStore());
|
|
622
|
+
|
|
623
|
+
act(() => {
|
|
624
|
+
// sendMessage is not an AI runtime operation type
|
|
625
|
+
result.current.startOperation({
|
|
626
|
+
type: 'sendMessage',
|
|
627
|
+
context: { agentId: 'agent1' },
|
|
628
|
+
});
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
// sendMessage is not in AI_RUNTIME_OPERATION_TYPES, so should return false
|
|
632
|
+
expect(operationSelectors.isAgentRunning('agent1')(result.current)).toBe(false);
|
|
633
|
+
});
|
|
634
|
+
});
|
|
635
|
+
|
|
512
636
|
describe('backward compatibility selectors', () => {
|
|
513
637
|
it('isAgentRuntimeRunning should work', () => {
|
|
514
638
|
const { result } = renderHook(() => useChatStore());
|
|
@@ -234,6 +234,27 @@ const isAgentRuntimeRunningByContext =
|
|
|
234
234
|
};
|
|
235
235
|
|
|
236
236
|
// === Backward Compatibility ===
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Check if a specific agent has running AI runtime operations
|
|
240
|
+
* Used for agent list item loading states where we need per-agent granularity
|
|
241
|
+
*/
|
|
242
|
+
const isAgentRunning =
|
|
243
|
+
(agentId: string) =>
|
|
244
|
+
(s: ChatStoreState): boolean => {
|
|
245
|
+
for (const type of AI_RUNTIME_OPERATION_TYPES) {
|
|
246
|
+
const operationIds = s.operationsByType[type] || [];
|
|
247
|
+
const hasRunning = operationIds.some((id) => {
|
|
248
|
+
const op = s.operations[id];
|
|
249
|
+
return (
|
|
250
|
+
op && op.status === 'running' && !op.metadata.isAborting && op.context.agentId === agentId
|
|
251
|
+
);
|
|
252
|
+
});
|
|
253
|
+
if (hasRunning) return true;
|
|
254
|
+
}
|
|
255
|
+
return false;
|
|
256
|
+
};
|
|
257
|
+
|
|
237
258
|
/**
|
|
238
259
|
* Check if agent runtime is running (including both main window and thread)
|
|
239
260
|
* Checks both client-side (execAgentRuntime) and server-side (execServerAgentRuntime) operations
|
|
@@ -477,6 +498,7 @@ export const operationSelectors = {
|
|
|
477
498
|
|
|
478
499
|
isAborting,
|
|
479
500
|
|
|
501
|
+
isAgentRunning,
|
|
480
502
|
isAgentRuntimeRunning,
|
|
481
503
|
isAgentRuntimeRunningByContext,
|
|
482
504
|
isAnyMessageLoading,
|