@lobehub/lobehub 2.0.0-next.287 → 2.0.0-next.289
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 +50 -0
- package/changelog/v1.json +18 -0
- package/locales/en-US/plugin.json +3 -5
- package/locales/zh-CN/plugin.json +3 -5
- package/locales/zh-CN/tool.json +2 -0
- package/package.json +1 -1
- package/packages/builtin-agents/src/agents/group-supervisor/index.ts +12 -1
- package/packages/builtin-agents/src/agents/group-supervisor/systemRole.ts +0 -7
- package/packages/builtin-tool-cloud-sandbox/src/client/Inspector/EditLocalFile/index.tsx +93 -0
- package/packages/builtin-tool-cloud-sandbox/src/client/Inspector/GlobLocalFiles/index.tsx +73 -0
- package/packages/builtin-tool-cloud-sandbox/src/client/Inspector/GrepContent/index.tsx +69 -0
- package/packages/builtin-tool-cloud-sandbox/src/client/Inspector/ListLocalFiles/index.tsx +68 -0
- package/packages/builtin-tool-cloud-sandbox/src/client/Inspector/ReadLocalFile/index.tsx +74 -0
- package/packages/builtin-tool-cloud-sandbox/src/client/Inspector/SearchLocalFiles/index.tsx +70 -0
- package/packages/builtin-tool-cloud-sandbox/src/client/Inspector/WriteLocalFile/index.tsx +57 -0
- package/packages/builtin-tool-cloud-sandbox/src/client/Inspector/index.ts +14 -0
- package/packages/builtin-tool-cloud-sandbox/src/client/Render/WriteFile/index.tsx +54 -35
- package/packages/builtin-tool-cloud-sandbox/src/client/components/FilePathDisplay.tsx +52 -0
- package/packages/builtin-tool-group-management/src/client/Inspector/ExecuteTasks/index.tsx +90 -0
- package/packages/builtin-tool-group-management/src/client/Inspector/index.ts +2 -0
- package/packages/builtin-tool-group-management/src/client/Intervention/ExecuteTasks.tsx +237 -0
- package/packages/builtin-tool-group-management/src/client/Intervention/index.ts +4 -1
- package/packages/builtin-tool-group-management/src/client/Render/index.ts +1 -1
- package/packages/builtin-tool-group-management/src/client/Streaming/ExecuteTask/index.tsx +69 -0
- package/packages/builtin-tool-group-management/src/client/Streaming/ExecuteTasks/index.tsx +87 -0
- package/packages/builtin-tool-group-management/src/client/Streaming/index.ts +4 -0
- package/packages/builtin-tool-group-management/src/executor.test.ts +8 -311
- package/packages/builtin-tool-group-management/src/executor.ts +5 -160
- package/packages/builtin-tool-group-management/src/manifest.ts +50 -94
- package/packages/builtin-tool-group-management/src/systemRole.ts +251 -172
- package/packages/builtin-tool-group-management/src/types.ts +29 -40
- package/packages/context-engine/src/engine/messages/MessagesEngine.ts +22 -4
- package/packages/context-engine/src/engine/messages/types.ts +4 -4
- package/packages/context-engine/src/processors/GroupOrchestrationFilter.ts +211 -0
- package/packages/context-engine/src/processors/GroupRoleTransform.ts +261 -0
- package/packages/context-engine/src/processors/__tests__/GroupOrchestrationFilter.test.ts +770 -0
- package/packages/context-engine/src/processors/__tests__/GroupRoleTransform.test.ts +553 -0
- package/packages/context-engine/src/processors/index.ts +7 -2
- package/packages/context-engine/src/providers/__tests__/GroupContextInjector.test.ts +4 -16
- package/packages/context-engine/src/providers/__tests__/__snapshots__/GroupContextInjector.test.ts.snap +23 -28
- package/packages/prompts/src/prompts/agentGroup/__snapshots__/index.test.ts.snap +0 -7
- package/packages/prompts/src/prompts/agentGroup/groupContext.ts +0 -7
- package/src/app/[variants]/(main)/group/features/Conversation/AgentWelcome/OpeningQuestions.tsx +4 -8
- package/src/app/[variants]/(main)/group/features/Conversation/MainChatInput/GroupChat.tsx +0 -3
- package/src/app/[variants]/(main)/group/features/Conversation/useGroupContext.ts +3 -0
- package/src/features/ChatInput/Desktop/index.tsx +1 -3
- package/src/features/Conversation/store/slices/message/action/crud.ts +2 -2
- package/src/locales/default/plugin.ts +3 -5
- package/src/locales/default/tool.ts +3 -0
- package/src/services/chat/mecha/agentConfigResolver.test.ts +160 -0
- package/src/services/chat/mecha/agentConfigResolver.ts +15 -3
- package/src/services/chat/mecha/contextEngineering.ts +2 -1
- package/src/store/chat/agents/GroupOrchestration/createGroupOrchestrationExecutors.ts +4 -2
- package/src/store/chat/slices/aiChat/actions/conversationLifecycle.ts +2 -0
- package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +1 -18
- package/src/store/chat/slices/message/selectors/displayMessage.test.ts +24 -0
- package/src/store/chat/slices/message/selectors/displayMessage.ts +6 -1
- package/src/store/chat/slices/topic/action.test.ts +10 -4
- package/src/store/chat/slices/topic/action.ts +3 -2
- package/src/store/document/slices/document/action.ts +8 -0
- package/packages/context-engine/src/processors/GroupMessageSender.ts +0 -138
- package/packages/context-engine/src/processors/__tests__/GroupMessageSender.test.ts +0 -274
|
@@ -7,13 +7,6 @@ exports[`GroupContextInjector > Edge Cases > should handle empty members array 1
|
|
|
7
7
|
You are "", acting as a in the multi-agent group "Empty Group".
|
|
8
8
|
Your internal agent ID is (for system use only, never expose to users).
|
|
9
9
|
|
|
10
|
-
<critical_output_rules>
|
|
11
|
-
IMPORTANT: Your responses must contain ONLY your actual reply content.
|
|
12
|
-
- Messages in conversation history start with '<speaker name="..." />' - this identifies who sent each message
|
|
13
|
-
- NEVER start your response with '<speaker' tag - the system adds this automatically
|
|
14
|
-
- Just output your actual response content directly
|
|
15
|
-
</critical_output_rules>
|
|
16
|
-
|
|
17
10
|
<group_description>The following describes the purpose and goals of this agent group:
|
|
18
11
|
|
|
19
12
|
Empty group description
|
|
@@ -22,6 +15,29 @@ Empty group description
|
|
|
22
15
|
<group_participants>The following agents are available in this group:
|
|
23
16
|
|
|
24
17
|
|
|
18
|
+
</group_participants>
|
|
19
|
+
|
|
20
|
+
<identity_rules>
|
|
21
|
+
- NEVER expose or display agent IDs to users - always refer to agents by their names
|
|
22
|
+
</identity_rules>
|
|
23
|
+
</group_context>"
|
|
24
|
+
`;
|
|
25
|
+
|
|
26
|
+
exports[`GroupContextInjector > Identity Rules Section > should always include identity rules 1`] = `
|
|
27
|
+
"Base prompt.
|
|
28
|
+
|
|
29
|
+
<group_context>
|
|
30
|
+
You are "", acting as a in the multi-agent group "".
|
|
31
|
+
Your internal agent ID is (for system use only, never expose to users).
|
|
32
|
+
|
|
33
|
+
<group_description>The following describes the purpose and goals of this agent group:
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
</group_description>
|
|
37
|
+
|
|
38
|
+
<group_participants>The following agents are available in this group:
|
|
39
|
+
|
|
40
|
+
|
|
25
41
|
</group_participants>
|
|
26
42
|
|
|
27
43
|
<identity_rules>
|
|
@@ -37,13 +53,6 @@ exports[`GroupContextInjector > Variable Replacement > should handle config with
|
|
|
37
53
|
You are "", acting as a in the multi-agent group "Test Group".
|
|
38
54
|
Your internal agent ID is (for system use only, never expose to users).
|
|
39
55
|
|
|
40
|
-
<critical_output_rules>
|
|
41
|
-
IMPORTANT: Your responses must contain ONLY your actual reply content.
|
|
42
|
-
- Messages in conversation history start with '<speaker name="..." />' - this identifies who sent each message
|
|
43
|
-
- NEVER start your response with '<speaker' tag - the system adds this automatically
|
|
44
|
-
- Just output your actual response content directly
|
|
45
|
-
</critical_output_rules>
|
|
46
|
-
|
|
47
56
|
<group_description>The following describes the purpose and goals of this agent group:
|
|
48
57
|
|
|
49
58
|
Test group description
|
|
@@ -67,13 +76,6 @@ exports[`GroupContextInjector > Variable Replacement > should handle config with
|
|
|
67
76
|
You are "Editor", acting as a participant in the multi-agent group "".
|
|
68
77
|
Your internal agent ID is agt_editor (for system use only, never expose to users).
|
|
69
78
|
|
|
70
|
-
<critical_output_rules>
|
|
71
|
-
IMPORTANT: Your responses must contain ONLY your actual reply content.
|
|
72
|
-
- Messages in conversation history start with '<speaker name="..." />' - this identifies who sent each message
|
|
73
|
-
- NEVER start your response with '<speaker' tag - the system adds this automatically
|
|
74
|
-
- Just output your actual response content directly
|
|
75
|
-
</critical_output_rules>
|
|
76
|
-
|
|
77
79
|
<group_description>The following describes the purpose and goals of this agent group:
|
|
78
80
|
|
|
79
81
|
|
|
@@ -97,13 +99,6 @@ exports[`GroupContextInjector > Variable Replacement > should handle empty confi
|
|
|
97
99
|
You are "", acting as a in the multi-agent group "".
|
|
98
100
|
Your internal agent ID is (for system use only, never expose to users).
|
|
99
101
|
|
|
100
|
-
<critical_output_rules>
|
|
101
|
-
IMPORTANT: Your responses must contain ONLY your actual reply content.
|
|
102
|
-
- Messages in conversation history start with '<speaker name="..." />' - this identifies who sent each message
|
|
103
|
-
- NEVER start your response with '<speaker' tag - the system adds this automatically
|
|
104
|
-
- Just output your actual response content directly
|
|
105
|
-
</critical_output_rules>
|
|
106
|
-
|
|
107
102
|
<group_description>The following describes the purpose and goals of this agent group:
|
|
108
103
|
|
|
109
104
|
|
|
@@ -65,13 +65,6 @@ exports[`groupContextTemplate > should match snapshot 1`] = `
|
|
|
65
65
|
"You are "{{AGENT_NAME}}", acting as a {{AGENT_ROLE}} in the multi-agent group "{{GROUP_TITLE}}".
|
|
66
66
|
Your internal agent ID is {{AGENT_ID}} (for system use only, never expose to users).
|
|
67
67
|
|
|
68
|
-
<critical_output_rules>
|
|
69
|
-
IMPORTANT: Your responses must contain ONLY your actual reply content.
|
|
70
|
-
- Messages in conversation history start with '<speaker name="..." />' - this identifies who sent each message
|
|
71
|
-
- NEVER start your response with '<speaker' tag - the system adds this automatically
|
|
72
|
-
- Just output your actual response content directly
|
|
73
|
-
</critical_output_rules>
|
|
74
|
-
|
|
75
68
|
<group_description>The following describes the purpose and goals of this agent group:
|
|
76
69
|
|
|
77
70
|
{{SYSTEM_PROMPT}}
|
|
@@ -19,13 +19,6 @@ export interface GroupContextMemberInfo {
|
|
|
19
19
|
export const groupContextTemplate = `You are "{{AGENT_NAME}}", acting as a {{AGENT_ROLE}} in the multi-agent group "{{GROUP_TITLE}}".
|
|
20
20
|
Your internal agent ID is {{AGENT_ID}} (for system use only, never expose to users).
|
|
21
21
|
|
|
22
|
-
<critical_output_rules>
|
|
23
|
-
IMPORTANT: Your responses must contain ONLY your actual reply content.
|
|
24
|
-
- Messages in conversation history start with '<speaker name="..." />' - this identifies who sent each message
|
|
25
|
-
- NEVER start your response with '<speaker' tag - the system adds this automatically
|
|
26
|
-
- Just output your actual response content directly
|
|
27
|
-
</critical_output_rules>
|
|
28
|
-
|
|
29
22
|
<group_description>The following describes the purpose and goals of this agent group:
|
|
30
23
|
|
|
31
24
|
{{SYSTEM_PROMPT}}
|
package/src/app/[variants]/(main)/group/features/Conversation/AgentWelcome/OpeningQuestions.tsx
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { Block, Flexbox } from '@lobehub/ui';
|
|
4
|
-
import { createStaticStyles
|
|
4
|
+
import { createStaticStyles, responsive } from 'antd-style';
|
|
5
5
|
import { memo } from 'react';
|
|
6
6
|
import { useTranslation } from 'react-i18next';
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
// import { useSend } from '../../features/ChatInput/useSend';
|
|
8
|
+
import { useConversationStore } from '@/features/Conversation';
|
|
11
9
|
|
|
12
10
|
const styles = createStaticStyles(({ css, cssVar }) => ({
|
|
13
11
|
card: css`
|
|
@@ -38,8 +36,7 @@ interface OpeningQuestionsProps {
|
|
|
38
36
|
|
|
39
37
|
const OpeningQuestions = memo<OpeningQuestionsProps>(({ mobile, questions }) => {
|
|
40
38
|
const { t } = useTranslation('welcome');
|
|
41
|
-
const [
|
|
42
|
-
// const { send: sendMessage } = useSend();
|
|
39
|
+
const [sendMessage] = useConversationStore((s) => [s.sendMessage]);
|
|
43
40
|
|
|
44
41
|
return (
|
|
45
42
|
<div className={styles.container}>
|
|
@@ -52,8 +49,7 @@ const OpeningQuestions = memo<OpeningQuestionsProps>(({ mobile, questions }) =>
|
|
|
52
49
|
clickable
|
|
53
50
|
key={question}
|
|
54
51
|
onClick={() => {
|
|
55
|
-
|
|
56
|
-
// sendMessage({ isWelcomeQuestion: true });
|
|
52
|
+
sendMessage({ message: question });
|
|
57
53
|
}}
|
|
58
54
|
paddingBlock={8}
|
|
59
55
|
paddingInline={12}
|
|
@@ -92,9 +92,6 @@ const Desktop = memo((props: { targetMemberId?: string }) => {
|
|
|
92
92
|
onMarkdownContentChange={(content) => {
|
|
93
93
|
useChatStore.setState({ inputMessage: content });
|
|
94
94
|
}}
|
|
95
|
-
onSend={() => {
|
|
96
|
-
// send({ targetMemberId: props.targetMemberId });
|
|
97
|
-
}}
|
|
98
95
|
rightActions={isDMPortal ? [] : rightActions}
|
|
99
96
|
// sendButtonProps={{ disabled, generating, onStop: stop }}
|
|
100
97
|
sendMenu={{
|
|
@@ -23,9 +23,12 @@ export function useGroupContext(): ConversationContext {
|
|
|
23
23
|
const supervisorAgentId = currentGroup?.supervisorAgentId;
|
|
24
24
|
|
|
25
25
|
// Group context uses supervisorAgentId as agentId for message storage
|
|
26
|
+
// When in group mode (not group_agent thread mode), the supervisor is responding
|
|
27
|
+
// so we mark isSupervisor: true for proper UI rendering
|
|
26
28
|
return {
|
|
27
29
|
agentId: supervisorAgentId || '',
|
|
28
30
|
groupId: groupId ?? undefined,
|
|
31
|
+
isSupervisor: !threadId, // Supervisor responds in main group chat, not in agent threads
|
|
29
32
|
scope: threadId ? 'group_agent' : 'group',
|
|
30
33
|
threadId,
|
|
31
34
|
topicId,
|
|
@@ -95,9 +95,7 @@ const DesktopChatInput = memo<DesktopChatInputProps>(
|
|
|
95
95
|
<ChatInputActionBar
|
|
96
96
|
left={<ActionBar dropdownPlacement={dropdownPlacement} />}
|
|
97
97
|
right={<SendArea />}
|
|
98
|
-
style={{
|
|
99
|
-
paddingRight: 8,
|
|
100
|
-
}}
|
|
98
|
+
style={{ paddingRight: 8 }}
|
|
101
99
|
/>
|
|
102
100
|
}
|
|
103
101
|
fullscreen={expand}
|
|
@@ -298,8 +298,8 @@ export const messageCRUDSlice: StateCreator<
|
|
|
298
298
|
|
|
299
299
|
let ids = [message.id];
|
|
300
300
|
|
|
301
|
-
// Handle assistantGroup messages: delete all child blocks and tool results
|
|
302
|
-
if (message.role === 'assistantGroup' && message.children) {
|
|
301
|
+
// Handle assistantGroup and supervisor messages: delete all child blocks and tool results
|
|
302
|
+
if ((message.role === 'assistantGroup' || message.role === 'supervisor') && message.children) {
|
|
303
303
|
const childIds = message.children.map((child: AssistantContentBlock) => child.id);
|
|
304
304
|
ids = ids.concat(childIds);
|
|
305
305
|
|
|
@@ -61,18 +61,16 @@ export default {
|
|
|
61
61
|
'builtins.lobe-group-agent-builder.inspector.title': 'Title',
|
|
62
62
|
'builtins.lobe-group-agent-builder.title': 'Group Builder Expert',
|
|
63
63
|
'builtins.lobe-group-management.apiName.broadcast': 'All speak',
|
|
64
|
-
'builtins.lobe-group-management.apiName.createAgent': 'Add group member',
|
|
65
64
|
'builtins.lobe-group-management.apiName.createWorkflow': 'Plan workflow',
|
|
66
|
-
'builtins.lobe-group-management.apiName.
|
|
65
|
+
'builtins.lobe-group-management.apiName.executeAgentTask': 'Execute agent task',
|
|
66
|
+
'builtins.lobe-group-management.apiName.executeAgentTasks': 'Execute parallel agent tasks',
|
|
67
67
|
'builtins.lobe-group-management.apiName.getAgentInfo': 'Get member info',
|
|
68
68
|
'builtins.lobe-group-management.apiName.interrupt': 'Interrupt task',
|
|
69
|
-
'builtins.lobe-group-management.apiName.inviteAgent': 'Invite member',
|
|
70
|
-
'builtins.lobe-group-management.apiName.removeAgent': 'Remove member',
|
|
71
|
-
'builtins.lobe-group-management.apiName.searchAgent': 'Find relevant experts',
|
|
72
69
|
'builtins.lobe-group-management.apiName.speak': 'Designated member speaks',
|
|
73
70
|
'builtins.lobe-group-management.apiName.summarize': 'Summarize conversation',
|
|
74
71
|
'builtins.lobe-group-management.apiName.vote': 'Start vote',
|
|
75
72
|
'builtins.lobe-group-management.inspector.broadcast.title': 'Following Agents speak:',
|
|
73
|
+
'builtins.lobe-group-management.inspector.executeAgentTasks.title': 'Assigning tasks to:',
|
|
76
74
|
'builtins.lobe-group-management.inspector.speak.title': 'Designated Agent speaks:',
|
|
77
75
|
'builtins.lobe-group-management.title': 'Group Coordinator',
|
|
78
76
|
'builtins.lobe-gtd.apiName.clearTodos': 'Clear todos',
|
|
@@ -16,6 +16,9 @@ export default {
|
|
|
16
16
|
'agentGroupManagement.executeTask.thread': 'Thread ID',
|
|
17
17
|
'agentGroupManagement.executeTask.timeout': 'Execution Timed Out',
|
|
18
18
|
'agentGroupManagement.executeTask.tokens': 'Token Usage',
|
|
19
|
+
'agentGroupManagement.executeTasks.intervention.instructionPlaceholder':
|
|
20
|
+
'Detailed instruction for the agent to perform this task...',
|
|
21
|
+
'agentGroupManagement.executeTasks.intervention.titlePlaceholder': 'Task title...',
|
|
19
22
|
'codeInterpreter-legacy.error': 'Execution Error',
|
|
20
23
|
'codeInterpreter-legacy.executing': 'Executing...',
|
|
21
24
|
'codeInterpreter-legacy.files': 'Files:',
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as builtinAgents from '@lobechat/builtin-agents';
|
|
2
|
+
import { GroupManagementIdentifier } from '@lobechat/builtin-tool-group-management';
|
|
2
3
|
import { GTDIdentifier } from '@lobechat/builtin-tool-gtd';
|
|
3
4
|
import { NotebookIdentifier } from '@lobechat/builtin-tool-notebook';
|
|
4
5
|
import { PageAgentIdentifier } from '@lobechat/builtin-tool-page-agent';
|
|
@@ -6,6 +7,8 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
6
7
|
|
|
7
8
|
import * as agentStore from '@/store/agent';
|
|
8
9
|
import * as agentSelectors from '@/store/agent/selectors';
|
|
10
|
+
import * as agentGroupStore from '@/store/agentGroup';
|
|
11
|
+
import * as agentGroupSelectors from '@/store/agentGroup/selectors';
|
|
9
12
|
|
|
10
13
|
import { resolveAgentConfig } from './agentConfigResolver';
|
|
11
14
|
|
|
@@ -623,4 +626,161 @@ describe('resolveAgentConfig', () => {
|
|
|
623
626
|
expect(result.chatConfig.enableHistoryCount).toBe(false);
|
|
624
627
|
});
|
|
625
628
|
});
|
|
629
|
+
|
|
630
|
+
describe('supervisor agent (slug from agentGroup store)', () => {
|
|
631
|
+
const mockGroupStoreState = { groupMap: {} };
|
|
632
|
+
const mockGroupWithSupervisor = {
|
|
633
|
+
agents: [
|
|
634
|
+
{ id: 'supervisor-agent-id', isSupervisor: true, title: 'Supervisor' },
|
|
635
|
+
{ id: 'member-agent-1', isSupervisor: false, title: 'Agent 1' },
|
|
636
|
+
{ id: 'member-agent-2', isSupervisor: false, title: 'Agent 2' },
|
|
637
|
+
],
|
|
638
|
+
config: { systemPrompt: 'Custom group system prompt' },
|
|
639
|
+
id: 'group-123',
|
|
640
|
+
supervisorAgentId: 'supervisor-agent-id',
|
|
641
|
+
title: 'Test Group',
|
|
642
|
+
};
|
|
643
|
+
|
|
644
|
+
beforeEach(() => {
|
|
645
|
+
// No slug in agent store - simulates supervisor agent not being in agentMap with slug
|
|
646
|
+
vi.spyOn(agentSelectors.agentSelectors, 'getAgentSlugById').mockReturnValue(() => undefined);
|
|
647
|
+
|
|
648
|
+
// Mock agentGroup store
|
|
649
|
+
vi.spyOn(agentGroupStore, 'getChatGroupStoreState').mockReturnValue(
|
|
650
|
+
mockGroupStoreState as any,
|
|
651
|
+
);
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
it('should detect supervisor agent from agentGroup store when not found in agent store', () => {
|
|
655
|
+
// Mock: supervisor agent is found in agentGroup store
|
|
656
|
+
vi.spyOn(
|
|
657
|
+
agentGroupSelectors.agentGroupByIdSelectors,
|
|
658
|
+
'groupBySupervisorAgentId',
|
|
659
|
+
).mockReturnValue(() => mockGroupWithSupervisor as any);
|
|
660
|
+
|
|
661
|
+
// Mock: getGroupBySupervisorAgentId for building groupSupervisorContext
|
|
662
|
+
vi.spyOn(
|
|
663
|
+
agentGroupSelectors.agentGroupSelectors,
|
|
664
|
+
'getGroupBySupervisorAgentId',
|
|
665
|
+
).mockReturnValue(() => mockGroupWithSupervisor as any);
|
|
666
|
+
|
|
667
|
+
// Mock: getGroupMembers returns non-supervisor agents
|
|
668
|
+
vi.spyOn(agentGroupSelectors.agentGroupSelectors, 'getGroupMembers').mockReturnValue(
|
|
669
|
+
() =>
|
|
670
|
+
[
|
|
671
|
+
{ id: 'member-agent-1', title: 'Agent 1' },
|
|
672
|
+
{ id: 'member-agent-2', title: 'Agent 2' },
|
|
673
|
+
] as any,
|
|
674
|
+
);
|
|
675
|
+
|
|
676
|
+
// Mock: getAgentRuntimeConfig for supervisor agent
|
|
677
|
+
vi.spyOn(builtinAgents, 'getAgentRuntimeConfig').mockReturnValue({
|
|
678
|
+
chatConfig: { enableHistoryCount: false },
|
|
679
|
+
plugins: [GroupManagementIdentifier, GTDIdentifier],
|
|
680
|
+
systemRole: 'You are a group supervisor...',
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
const result = resolveAgentConfig({ agentId: 'supervisor-agent-id' });
|
|
684
|
+
|
|
685
|
+
expect(result.isBuiltinAgent).toBe(true);
|
|
686
|
+
expect(result.slug).toBe('group-supervisor');
|
|
687
|
+
expect(result.plugins).toContain(GroupManagementIdentifier);
|
|
688
|
+
expect(result.plugins).toContain(GTDIdentifier);
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
it('should pass groupSupervisorContext to getAgentRuntimeConfig', () => {
|
|
692
|
+
// Mock: supervisor agent is found in agentGroup store
|
|
693
|
+
vi.spyOn(
|
|
694
|
+
agentGroupSelectors.agentGroupByIdSelectors,
|
|
695
|
+
'groupBySupervisorAgentId',
|
|
696
|
+
).mockReturnValue(() => mockGroupWithSupervisor as any);
|
|
697
|
+
|
|
698
|
+
// Mock: getGroupBySupervisorAgentId for building groupSupervisorContext
|
|
699
|
+
vi.spyOn(
|
|
700
|
+
agentGroupSelectors.agentGroupSelectors,
|
|
701
|
+
'getGroupBySupervisorAgentId',
|
|
702
|
+
).mockReturnValue(() => mockGroupWithSupervisor as any);
|
|
703
|
+
|
|
704
|
+
// Mock: getGroupMembers returns non-supervisor agents
|
|
705
|
+
vi.spyOn(agentGroupSelectors.agentGroupSelectors, 'getGroupMembers').mockReturnValue(
|
|
706
|
+
() =>
|
|
707
|
+
[
|
|
708
|
+
{ id: 'member-agent-1', title: 'Agent 1' },
|
|
709
|
+
{ id: 'member-agent-2', title: 'Agent 2' },
|
|
710
|
+
] as any,
|
|
711
|
+
);
|
|
712
|
+
|
|
713
|
+
const getAgentRuntimeConfigSpy = vi
|
|
714
|
+
.spyOn(builtinAgents, 'getAgentRuntimeConfig')
|
|
715
|
+
.mockReturnValue({
|
|
716
|
+
chatConfig: { enableHistoryCount: false },
|
|
717
|
+
plugins: [GroupManagementIdentifier],
|
|
718
|
+
systemRole: 'You are a group supervisor...',
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
resolveAgentConfig({ agentId: 'supervisor-agent-id' });
|
|
722
|
+
|
|
723
|
+
expect(getAgentRuntimeConfigSpy).toHaveBeenCalledWith(
|
|
724
|
+
'group-supervisor',
|
|
725
|
+
expect.objectContaining({
|
|
726
|
+
groupSupervisorContext: {
|
|
727
|
+
availableAgents: [
|
|
728
|
+
{ id: 'member-agent-1', title: 'Agent 1' },
|
|
729
|
+
{ id: 'member-agent-2', title: 'Agent 2' },
|
|
730
|
+
],
|
|
731
|
+
groupId: 'group-123',
|
|
732
|
+
groupTitle: 'Test Group',
|
|
733
|
+
systemPrompt: 'Custom group system prompt',
|
|
734
|
+
},
|
|
735
|
+
}),
|
|
736
|
+
);
|
|
737
|
+
});
|
|
738
|
+
|
|
739
|
+
it('should treat as regular agent when supervisor is not found in agentGroup store', () => {
|
|
740
|
+
// Mock: supervisor agent is NOT found in agentGroup store
|
|
741
|
+
vi.spyOn(
|
|
742
|
+
agentGroupSelectors.agentGroupByIdSelectors,
|
|
743
|
+
'groupBySupervisorAgentId',
|
|
744
|
+
).mockReturnValue(() => undefined);
|
|
745
|
+
|
|
746
|
+
const result = resolveAgentConfig({ agentId: 'some-other-agent' });
|
|
747
|
+
|
|
748
|
+
expect(result.isBuiltinAgent).toBe(false);
|
|
749
|
+
expect(result.slug).toBeUndefined();
|
|
750
|
+
expect(result.plugins).toEqual(['plugin-a', 'plugin-b']); // Falls back to agent config plugins
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
it('should work correctly when regenerating supervisor message', () => {
|
|
754
|
+
// This simulates the regenerate flow where agentId is the supervisor agent ID
|
|
755
|
+
vi.spyOn(
|
|
756
|
+
agentGroupSelectors.agentGroupByIdSelectors,
|
|
757
|
+
'groupBySupervisorAgentId',
|
|
758
|
+
).mockReturnValue(() => mockGroupWithSupervisor as any);
|
|
759
|
+
|
|
760
|
+
vi.spyOn(
|
|
761
|
+
agentGroupSelectors.agentGroupSelectors,
|
|
762
|
+
'getGroupBySupervisorAgentId',
|
|
763
|
+
).mockReturnValue(() => mockGroupWithSupervisor as any);
|
|
764
|
+
|
|
765
|
+
vi.spyOn(agentGroupSelectors.agentGroupSelectors, 'getGroupMembers').mockReturnValue(
|
|
766
|
+
() => [{ id: 'member-agent-1', title: 'Agent 1' }] as any,
|
|
767
|
+
);
|
|
768
|
+
|
|
769
|
+
vi.spyOn(builtinAgents, 'getAgentRuntimeConfig').mockReturnValue({
|
|
770
|
+
chatConfig: { enableHistoryCount: false },
|
|
771
|
+
plugins: [GroupManagementIdentifier, GTDIdentifier],
|
|
772
|
+
systemRole: 'Supervisor system role',
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
const result = resolveAgentConfig({ agentId: 'supervisor-agent-id' });
|
|
776
|
+
|
|
777
|
+
// Should correctly identify as builtin supervisor agent
|
|
778
|
+
expect(result.isBuiltinAgent).toBe(true);
|
|
779
|
+
expect(result.slug).toBe('group-supervisor');
|
|
780
|
+
// Should have group management tool injected
|
|
781
|
+
expect(result.plugins).toContain(GroupManagementIdentifier);
|
|
782
|
+
// Should have proper system role
|
|
783
|
+
expect(result.agentConfig.systemRole).toBe('Supervisor system role');
|
|
784
|
+
});
|
|
785
|
+
});
|
|
626
786
|
});
|
|
@@ -10,7 +10,7 @@ import { produce } from 'immer';
|
|
|
10
10
|
import { getAgentStoreState } from '@/store/agent';
|
|
11
11
|
import { agentSelectors, chatConfigByIdSelectors } from '@/store/agent/selectors';
|
|
12
12
|
import { getChatGroupStoreState } from '@/store/agentGroup';
|
|
13
|
-
import { agentGroupSelectors } from '@/store/agentGroup/selectors';
|
|
13
|
+
import { agentGroupByIdSelectors, agentGroupSelectors } from '@/store/agentGroup/selectors';
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Applies params adjustments based on chatConfig settings.
|
|
@@ -49,7 +49,7 @@ export interface AgentConfigResolverContext {
|
|
|
49
49
|
agentId: string;
|
|
50
50
|
|
|
51
51
|
// Builtin agent specific context
|
|
52
|
-
/** Document content for page-agent */
|
|
52
|
+
/** Document content for page-agent */
|
|
53
53
|
documentContent?: string;
|
|
54
54
|
|
|
55
55
|
/** Current model being used (for template variables) */
|
|
@@ -109,7 +109,19 @@ export const resolveAgentConfig = (ctx: AgentConfigResolverContext): ResolvedAge
|
|
|
109
109
|
const basePlugins = agentConfig.plugins ?? [];
|
|
110
110
|
|
|
111
111
|
// Check if this is a builtin agent
|
|
112
|
-
|
|
112
|
+
// First check agent store, then check if this is a supervisor agent in agentGroup store
|
|
113
|
+
let slug = agentSelectors.getAgentSlugById(agentId)(agentStoreState);
|
|
114
|
+
|
|
115
|
+
// If not found in agent store, check if this is a supervisor agent in any group
|
|
116
|
+
// Supervisor agents have their slug stored in agentGroup store, not agent store
|
|
117
|
+
if (!slug) {
|
|
118
|
+
const groupStoreState = getChatGroupStoreState();
|
|
119
|
+
const group = agentGroupByIdSelectors.groupBySupervisorAgentId(agentId)(groupStoreState);
|
|
120
|
+
if (group) {
|
|
121
|
+
// This is a supervisor agent - use the builtin slug
|
|
122
|
+
slug = BUILTIN_AGENT_SLUGS.groupSupervisor;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
113
125
|
|
|
114
126
|
if (!slug) {
|
|
115
127
|
// Regular agent - use provided plugins if available, fallback to agent's plugins
|
|
@@ -143,7 +143,8 @@ export const contextEngineering = async ({
|
|
|
143
143
|
currentAgentRole,
|
|
144
144
|
groupTitle: groupDetail.title || undefined,
|
|
145
145
|
members,
|
|
146
|
-
|
|
146
|
+
// Use group.content as the group description (shared prompt/content)
|
|
147
|
+
systemPrompt: groupDetail.content || undefined,
|
|
147
148
|
};
|
|
148
149
|
log('agentGroup built: %o', agentGroup);
|
|
149
150
|
}
|
|
@@ -190,12 +190,13 @@ export const createGroupOrchestrationExecutors = (
|
|
|
190
190
|
|
|
191
191
|
// If instruction is provided, inject it as a virtual User Message
|
|
192
192
|
// This virtual message is not persisted to database, only used for model context
|
|
193
|
+
// Mark with <speaker> tag so the agent knows this instruction is from the Supervisor
|
|
193
194
|
const now = Date.now();
|
|
194
195
|
const messagesWithInstruction: UIChatMessage[] = agentInstruction
|
|
195
196
|
? [
|
|
196
197
|
...messages,
|
|
197
198
|
{
|
|
198
|
-
content: agentInstruction
|
|
199
|
+
content: `<speaker name="Supervisor" />\n${agentInstruction}`,
|
|
199
200
|
createdAt: now,
|
|
200
201
|
id: `virtual_speak_instruction_${now}`,
|
|
201
202
|
role: 'user',
|
|
@@ -266,12 +267,13 @@ export const createGroupOrchestrationExecutors = (
|
|
|
266
267
|
|
|
267
268
|
// If instruction is provided, inject it as a virtual User Message
|
|
268
269
|
// This virtual message is not persisted to database, only used for model context
|
|
270
|
+
// Mark with <speaker> tag so the agent knows this instruction is from the Supervisor
|
|
269
271
|
const now = Date.now();
|
|
270
272
|
const messagesWithInstruction: UIChatMessage[] = agentInstruction
|
|
271
273
|
? [
|
|
272
274
|
...messages,
|
|
273
275
|
{
|
|
274
|
-
content: agentInstruction
|
|
276
|
+
content: `<speaker name="Supervisor" />\n${agentInstruction}`,
|
|
275
277
|
createdAt: now,
|
|
276
278
|
id: `virtual_broadcast_instruction_${now}`,
|
|
277
279
|
role: 'user',
|
|
@@ -197,6 +197,8 @@ export const conversationLifecycle: StateCreator<
|
|
|
197
197
|
// if there is topicId,then add topicId to message
|
|
198
198
|
topicId: operationContext.topicId ?? undefined,
|
|
199
199
|
threadId: operationContext.threadId ?? undefined,
|
|
200
|
+
// Pass isSupervisor metadata for group orchestration (consistent with server)
|
|
201
|
+
metadata: operationContext.isSupervisor ? { isSupervisor: true } : undefined,
|
|
200
202
|
},
|
|
201
203
|
{ operationId, tempMessageId: tempAssistantId },
|
|
202
204
|
);
|
|
@@ -592,7 +592,7 @@ export const streamingExecutor: StateCreator<
|
|
|
592
592
|
// resolveAgentConfig handles:
|
|
593
593
|
// - Builtin agent runtime config merging
|
|
594
594
|
// - max_tokens/reasoning_effort based on chatConfig settings
|
|
595
|
-
const { agentConfig: agentConfigData
|
|
595
|
+
const { agentConfig: agentConfigData } = resolveAgentConfig({
|
|
596
596
|
agentId: effectiveAgentId || '',
|
|
597
597
|
scope: context.scope, // Pass scope from context parameter (available at line 883)
|
|
598
598
|
});
|
|
@@ -863,22 +863,5 @@ export const streamingExecutor: StateCreator<
|
|
|
863
863
|
console.error('Desktop notification error:', error);
|
|
864
864
|
}
|
|
865
865
|
}
|
|
866
|
-
|
|
867
|
-
// Summary history if context messages is larger than historyCount
|
|
868
|
-
const historyCount = chatConfig.historyCount ?? 0;
|
|
869
|
-
|
|
870
|
-
if (
|
|
871
|
-
chatConfig.enableHistoryCount &&
|
|
872
|
-
chatConfig.enableCompressHistory &&
|
|
873
|
-
messages.length > historyCount
|
|
874
|
-
) {
|
|
875
|
-
// after generation: [u1,a1,u2,a2,u3,a3]
|
|
876
|
-
// but the `messages` is still: [u1,a1,u2,a2,u3]
|
|
877
|
-
// So if historyCount=2, we need to summary [u1,a1,u2,a2]
|
|
878
|
-
// because user find UI is [u1,a1,u2,a2 | u3,a3]
|
|
879
|
-
const historyMessages = messages.slice(0, -historyCount + 1);
|
|
880
|
-
|
|
881
|
-
await get().internal_summaryHistory(historyMessages);
|
|
882
|
-
}
|
|
883
866
|
},
|
|
884
867
|
});
|
|
@@ -323,6 +323,30 @@ describe('displayMessageSelectors', () => {
|
|
|
323
323
|
const result = displayMessageSelectors.currentDisplayChatKey(state as ChatStore);
|
|
324
324
|
expect(result).toBe(messageMapKey({ agentId: '', topicId: undefined }));
|
|
325
325
|
});
|
|
326
|
+
|
|
327
|
+
it('should generate correct key with activeGroupId for group conversations', () => {
|
|
328
|
+
const state: Partial<ChatStore> = {
|
|
329
|
+
activeAgentId: 'testId',
|
|
330
|
+
activeGroupId: 'groupId',
|
|
331
|
+
activeTopicId: undefined,
|
|
332
|
+
};
|
|
333
|
+
const result = displayMessageSelectors.currentDisplayChatKey(state as ChatStore);
|
|
334
|
+
expect(result).toBe(
|
|
335
|
+
messageMapKey({ agentId: 'testId', groupId: 'groupId', topicId: undefined }),
|
|
336
|
+
);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
it('should generate correct key with activeGroupId and activeTopicId', () => {
|
|
340
|
+
const state: Partial<ChatStore> = {
|
|
341
|
+
activeAgentId: 'testId',
|
|
342
|
+
activeGroupId: 'groupId',
|
|
343
|
+
activeTopicId: 'topicId',
|
|
344
|
+
};
|
|
345
|
+
const result = displayMessageSelectors.currentDisplayChatKey(state as ChatStore);
|
|
346
|
+
expect(result).toBe(
|
|
347
|
+
messageMapKey({ agentId: 'testId', groupId: 'groupId', topicId: 'topicId' }),
|
|
348
|
+
);
|
|
349
|
+
});
|
|
326
350
|
});
|
|
327
351
|
|
|
328
352
|
describe('activeDisplayMessages with group chat messages', () => {
|
|
@@ -24,9 +24,14 @@ import { messageMapKey } from '../../../utils/messageMapKey';
|
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* Get the current chat key for accessing messagesMap
|
|
27
|
+
* For group conversations, uses groupId to generate the correct key
|
|
27
28
|
*/
|
|
28
29
|
export const currentDisplayChatKey = (s: ChatStoreState) =>
|
|
29
|
-
messageMapKey({
|
|
30
|
+
messageMapKey({
|
|
31
|
+
agentId: s.activeAgentId,
|
|
32
|
+
groupId: s.activeGroupId,
|
|
33
|
+
topicId: s.activeTopicId,
|
|
34
|
+
});
|
|
30
35
|
|
|
31
36
|
/**
|
|
32
37
|
* Get display messages by key
|
|
@@ -462,7 +462,7 @@ describe('topic action', () => {
|
|
|
462
462
|
});
|
|
463
463
|
});
|
|
464
464
|
|
|
465
|
-
it('should
|
|
465
|
+
it('should clear new key data when switching with undefined (same as null)', async () => {
|
|
466
466
|
const { result } = renderHook(() => useChatStore());
|
|
467
467
|
const activeAgentId = 'test-agent-id';
|
|
468
468
|
|
|
@@ -475,13 +475,19 @@ describe('topic action', () => {
|
|
|
475
475
|
|
|
476
476
|
const replaceMessagesSpy = vi.spyOn(result.current, 'replaceMessages');
|
|
477
477
|
|
|
478
|
-
// Switch with undefined (should
|
|
478
|
+
// Switch with undefined (should clear because id == null matches both null and undefined)
|
|
479
479
|
await act(async () => {
|
|
480
480
|
await result.current.switchTopic(undefined, { skipRefreshMessage: true });
|
|
481
481
|
});
|
|
482
482
|
|
|
483
|
-
// replaceMessages
|
|
484
|
-
expect(replaceMessagesSpy).
|
|
483
|
+
// replaceMessages SHOULD be called when switching with undefined
|
|
484
|
+
expect(replaceMessagesSpy).toHaveBeenCalledWith([], {
|
|
485
|
+
context: expect.objectContaining({
|
|
486
|
+
agentId: activeAgentId,
|
|
487
|
+
topicId: null,
|
|
488
|
+
}),
|
|
489
|
+
action: expect.any(String),
|
|
490
|
+
});
|
|
485
491
|
});
|
|
486
492
|
|
|
487
493
|
it('should not clear new key data when switching to an existing topic', async () => {
|
|
@@ -532,10 +532,11 @@ export const chatTopic: StateCreator<
|
|
|
532
532
|
const { activeAgentId, activeGroupId } = get();
|
|
533
533
|
|
|
534
534
|
// Clear the _new key data in the following cases:
|
|
535
|
-
// 1. When id is
|
|
535
|
+
// 1. When id is null or undefined (switching to empty topic state)
|
|
536
536
|
// 2. When clearNewKey option is explicitly true
|
|
537
537
|
// This prevents stale data from previous conversations showing up
|
|
538
|
-
|
|
538
|
+
// Note: Use == null to match both null and undefined
|
|
539
|
+
const shouldClearNewKey = !id || opts.clearNewKey;
|
|
539
540
|
|
|
540
541
|
if (shouldClearNewKey && activeAgentId) {
|
|
541
542
|
// Determine scope: use explicit scope from options, or infer from activeGroupId
|
|
@@ -191,6 +191,14 @@ export const createDocumentSlice: StateCreator<
|
|
|
191
191
|
// Both documentId and editor are guaranteed to be defined when this callback is called
|
|
192
192
|
if (!document || !documentId || !editor) return;
|
|
193
193
|
|
|
194
|
+
// Check if this response is still for the current active document
|
|
195
|
+
// This prevents race conditions when quickly switching between documents
|
|
196
|
+
const currentActiveId = get().activeDocumentId;
|
|
197
|
+
if (currentActiveId && currentActiveId !== documentId) {
|
|
198
|
+
// User has already switched to another document, discard this stale response
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
194
202
|
// Initialize document with editor
|
|
195
203
|
get().initDocumentWithEditor({
|
|
196
204
|
autoSave,
|