@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.
Files changed (62) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/changelog/v1.json +18 -0
  3. package/locales/en-US/plugin.json +3 -5
  4. package/locales/zh-CN/plugin.json +3 -5
  5. package/locales/zh-CN/tool.json +2 -0
  6. package/package.json +1 -1
  7. package/packages/builtin-agents/src/agents/group-supervisor/index.ts +12 -1
  8. package/packages/builtin-agents/src/agents/group-supervisor/systemRole.ts +0 -7
  9. package/packages/builtin-tool-cloud-sandbox/src/client/Inspector/EditLocalFile/index.tsx +93 -0
  10. package/packages/builtin-tool-cloud-sandbox/src/client/Inspector/GlobLocalFiles/index.tsx +73 -0
  11. package/packages/builtin-tool-cloud-sandbox/src/client/Inspector/GrepContent/index.tsx +69 -0
  12. package/packages/builtin-tool-cloud-sandbox/src/client/Inspector/ListLocalFiles/index.tsx +68 -0
  13. package/packages/builtin-tool-cloud-sandbox/src/client/Inspector/ReadLocalFile/index.tsx +74 -0
  14. package/packages/builtin-tool-cloud-sandbox/src/client/Inspector/SearchLocalFiles/index.tsx +70 -0
  15. package/packages/builtin-tool-cloud-sandbox/src/client/Inspector/WriteLocalFile/index.tsx +57 -0
  16. package/packages/builtin-tool-cloud-sandbox/src/client/Inspector/index.ts +14 -0
  17. package/packages/builtin-tool-cloud-sandbox/src/client/Render/WriteFile/index.tsx +54 -35
  18. package/packages/builtin-tool-cloud-sandbox/src/client/components/FilePathDisplay.tsx +52 -0
  19. package/packages/builtin-tool-group-management/src/client/Inspector/ExecuteTasks/index.tsx +90 -0
  20. package/packages/builtin-tool-group-management/src/client/Inspector/index.ts +2 -0
  21. package/packages/builtin-tool-group-management/src/client/Intervention/ExecuteTasks.tsx +237 -0
  22. package/packages/builtin-tool-group-management/src/client/Intervention/index.ts +4 -1
  23. package/packages/builtin-tool-group-management/src/client/Render/index.ts +1 -1
  24. package/packages/builtin-tool-group-management/src/client/Streaming/ExecuteTask/index.tsx +69 -0
  25. package/packages/builtin-tool-group-management/src/client/Streaming/ExecuteTasks/index.tsx +87 -0
  26. package/packages/builtin-tool-group-management/src/client/Streaming/index.ts +4 -0
  27. package/packages/builtin-tool-group-management/src/executor.test.ts +8 -311
  28. package/packages/builtin-tool-group-management/src/executor.ts +5 -160
  29. package/packages/builtin-tool-group-management/src/manifest.ts +50 -94
  30. package/packages/builtin-tool-group-management/src/systemRole.ts +251 -172
  31. package/packages/builtin-tool-group-management/src/types.ts +29 -40
  32. package/packages/context-engine/src/engine/messages/MessagesEngine.ts +22 -4
  33. package/packages/context-engine/src/engine/messages/types.ts +4 -4
  34. package/packages/context-engine/src/processors/GroupOrchestrationFilter.ts +211 -0
  35. package/packages/context-engine/src/processors/GroupRoleTransform.ts +261 -0
  36. package/packages/context-engine/src/processors/__tests__/GroupOrchestrationFilter.test.ts +770 -0
  37. package/packages/context-engine/src/processors/__tests__/GroupRoleTransform.test.ts +553 -0
  38. package/packages/context-engine/src/processors/index.ts +7 -2
  39. package/packages/context-engine/src/providers/__tests__/GroupContextInjector.test.ts +4 -16
  40. package/packages/context-engine/src/providers/__tests__/__snapshots__/GroupContextInjector.test.ts.snap +23 -28
  41. package/packages/prompts/src/prompts/agentGroup/__snapshots__/index.test.ts.snap +0 -7
  42. package/packages/prompts/src/prompts/agentGroup/groupContext.ts +0 -7
  43. package/src/app/[variants]/(main)/group/features/Conversation/AgentWelcome/OpeningQuestions.tsx +4 -8
  44. package/src/app/[variants]/(main)/group/features/Conversation/MainChatInput/GroupChat.tsx +0 -3
  45. package/src/app/[variants]/(main)/group/features/Conversation/useGroupContext.ts +3 -0
  46. package/src/features/ChatInput/Desktop/index.tsx +1 -3
  47. package/src/features/Conversation/store/slices/message/action/crud.ts +2 -2
  48. package/src/locales/default/plugin.ts +3 -5
  49. package/src/locales/default/tool.ts +3 -0
  50. package/src/services/chat/mecha/agentConfigResolver.test.ts +160 -0
  51. package/src/services/chat/mecha/agentConfigResolver.ts +15 -3
  52. package/src/services/chat/mecha/contextEngineering.ts +2 -1
  53. package/src/store/chat/agents/GroupOrchestration/createGroupOrchestrationExecutors.ts +4 -2
  54. package/src/store/chat/slices/aiChat/actions/conversationLifecycle.ts +2 -0
  55. package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +1 -18
  56. package/src/store/chat/slices/message/selectors/displayMessage.test.ts +24 -0
  57. package/src/store/chat/slices/message/selectors/displayMessage.ts +6 -1
  58. package/src/store/chat/slices/topic/action.test.ts +10 -4
  59. package/src/store/chat/slices/topic/action.ts +3 -2
  60. package/src/store/document/slices/document/action.ts +8 -0
  61. package/packages/context-engine/src/processors/GroupMessageSender.ts +0 -138
  62. 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}}
@@ -1,13 +1,11 @@
1
1
  'use client';
2
2
 
3
3
  import { Block, Flexbox } from '@lobehub/ui';
4
- import { createStaticStyles , responsive } from 'antd-style';
4
+ import { createStaticStyles, responsive } from 'antd-style';
5
5
  import { memo } from 'react';
6
6
  import { useTranslation } from 'react-i18next';
7
7
 
8
- import { useChatStore } from '@/store/chat';
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 [updateMessageInput] = useChatStore((s) => [s.updateMessageInput]);
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
- updateMessageInput(question);
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.executeTask': 'Execute task',
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
- const slug = agentSelectors.getAgentSlugById(agentId)(agentStoreState);
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
- systemPrompt: groupDetail.config?.systemPrompt || undefined,
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, chatConfig } = resolveAgentConfig({
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({ agentId: s.activeAgentId, topicId: s.activeTopicId });
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 NOT clear new key data when switching with undefined (backward compatibility)', async () => {
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 NOT clear because id !== null)
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 should NOT be called when switching with undefined
484
- expect(replaceMessagesSpy).not.toHaveBeenCalled();
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 explicitly null (switching to empty topic state)
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
- const shouldClearNewKey = id === null || opts.clearNewKey;
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,