@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
@@ -2,17 +2,12 @@
2
2
 
3
3
  /**
4
4
  * API names for Group Management tool
5
+ *
6
+ * Note: Member management APIs (searchAgent, inviteAgent, createAgent, removeAgent)
7
+ * are handled by group-agent-builder tool. This tool focuses on orchestration.
5
8
  */
6
9
  export const GroupManagementApiName = {
7
- // ==================== Member Management ====================
8
- /** Search for agents that can be invited to the group */
9
- searchAgent: 'searchAgent',
10
- /** Invite an agent to join the group */
11
- inviteAgent: 'inviteAgent',
12
- /** Create a new agent and add it to the group */
13
- createAgent: 'createAgent',
14
- /** Remove an agent from the group */
15
- removeAgent: 'removeAgent',
10
+ // ==================== Agent Info ====================
16
11
  /** Get detailed information about an agent */
17
12
  getAgentInfo: 'getAgentInfo',
18
13
 
@@ -26,7 +21,9 @@ export const GroupManagementApiName = {
26
21
 
27
22
  // ==================== Task Execution ====================
28
23
  /** Let an agent execute a task asynchronously */
29
- executeTask: 'executeTask',
24
+ executeAgentTask: 'executeAgentTask',
25
+ /** Let multiple agents execute different tasks in parallel */
26
+ executeAgentTasks: 'executeAgentTasks',
30
27
  /** Interrupt a running agent task */
31
28
  interrupt: 'interrupt',
32
29
 
@@ -44,28 +41,7 @@ export const GroupManagementApiName = {
44
41
  export type GroupManagementApiNameType =
45
42
  (typeof GroupManagementApiName)[keyof typeof GroupManagementApiName];
46
43
 
47
- // ==================== Member Management Params ====================
48
-
49
- export interface SearchAgentParams {
50
- limit?: number;
51
- query?: string;
52
- source?: 'user' | 'community';
53
- }
54
-
55
- export interface InviteAgentParams {
56
- agentId: string;
57
- }
58
-
59
- export interface CreateAgentParams {
60
- avatar?: string;
61
- description?: string;
62
- systemRole: string;
63
- title: string;
64
- }
65
-
66
- export interface RemoveAgentParams {
67
- agentId: string;
68
- }
44
+ // ==================== Agent Info Params ====================
69
45
 
70
46
  export interface GetAgentInfoParams {
71
47
  agentId: string;
@@ -116,6 +92,27 @@ export interface ExecuteTaskParams {
116
92
  timeout?: number;
117
93
  }
118
94
 
95
+ export interface TaskItem {
96
+ /** The ID of the agent to execute this task */
97
+ agentId: string;
98
+ /** Detailed instruction/prompt for the task execution */
99
+ instruction: string;
100
+ /** Optional timeout in milliseconds for this specific task */
101
+ timeout?: number;
102
+ /** Brief title describing what this task does (shown in UI) */
103
+ title: string;
104
+ }
105
+
106
+ export interface ExecuteTasksParams {
107
+ /**
108
+ * If true, the orchestration will end after all tasks complete,
109
+ * without calling the supervisor again.
110
+ */
111
+ skipCallSupervisor?: boolean;
112
+ /** Array of tasks to execute, each assigned to a specific agent */
113
+ tasks: TaskItem[];
114
+ }
115
+
119
116
  export interface InterruptParams {
120
117
  taskId: string;
121
118
  }
@@ -156,14 +153,6 @@ export interface VoteParams {
156
153
 
157
154
  // ==================== Result Types ====================
158
155
 
159
- export interface AgentSearchResult {
160
- avatar?: string;
161
- description?: string;
162
- id: string;
163
- source: 'user' | 'community';
164
- title: string;
165
- }
166
-
167
156
  export interface VoteResult {
168
157
  agentId: string;
169
158
  reasoning?: string;
@@ -7,7 +7,8 @@ import { ContextEngine } from '../../pipeline';
7
7
  import {
8
8
  AgentCouncilFlattenProcessor,
9
9
  GroupMessageFlattenProcessor,
10
- GroupMessageSenderProcessor,
10
+ GroupOrchestrationFilterProcessor,
11
+ GroupRoleTransformProcessor,
11
12
  HistoryTruncateProcessor,
12
13
  InputTemplateProcessor,
13
14
  MessageCleanupProcessor,
@@ -274,11 +275,28 @@ export class MessagesEngine {
274
275
  // 15. Supervisor role restore (convert role=supervisor back to role=assistant for model)
275
276
  new SupervisorRoleRestoreProcessor(),
276
277
 
277
- // 16. Group message sender identity injection (for multi-agent chat)
278
- ...(isAgentGroupEnabled
278
+ // 15.5. Group orchestration filter (remove supervisor's orchestration messages like broadcast/speak)
279
+ // This must be BEFORE GroupRoleTransformProcessor so we filter based on original agentId/tools
280
+ ...(isAgentGroupEnabled && agentGroup.agentMap && agentGroup.currentAgentId
279
281
  ? [
280
- new GroupMessageSenderProcessor({
282
+ new GroupOrchestrationFilterProcessor({
283
+ agentMap: Object.fromEntries(
284
+ Object.entries(agentGroup.agentMap).map(([id, info]) => [id, { role: info.role }]),
285
+ ),
286
+ currentAgentId: agentGroup.currentAgentId,
287
+ // Only enabled when current agent is NOT supervisor (supervisor needs to see orchestration history)
288
+ enabled: agentGroup.currentAgentRole !== 'supervisor',
289
+ }),
290
+ ]
291
+ : []),
292
+
293
+ // 16. Group role transform (convert other agents' messages to user role with speaker tags)
294
+ // This must be BEFORE ToolCallProcessor so other agents' tool messages are converted first
295
+ ...(isAgentGroupEnabled && agentGroup.currentAgentId
296
+ ? [
297
+ new GroupRoleTransformProcessor({
281
298
  agentMap: agentGroup.agentMap!,
299
+ currentAgentId: agentGroup.currentAgentId,
282
300
  }),
283
301
  ]
284
302
  : []),
@@ -4,12 +4,12 @@ import type { RuntimeInitialContext, RuntimeStepContext } from '@lobechat/types'
4
4
 
5
5
  import type { OpenAIChatMessage, UIChatMessage } from '@/types/index';
6
6
 
7
- import type { AgentInfo } from '../../processors/GroupMessageSender';
7
+ import type { AgentInfo } from '../../processors/GroupRoleTransform';
8
8
  import type { AgentBuilderContext } from '../../providers/AgentBuilderContextInjector';
9
- import type { GroupAgentBuilderContext } from '../../providers/GroupAgentBuilderContextInjector';
10
- import type { GroupMemberInfo } from '../../providers/GroupContextInjector';
11
9
  import type { GTDPlan } from '../../providers/GTDPlanInjector';
12
10
  import type { GTDTodoList } from '../../providers/GTDTodoInjector';
11
+ import type { GroupAgentBuilderContext } from '../../providers/GroupAgentBuilderContextInjector';
12
+ import type { GroupMemberInfo } from '../../providers/GroupContextInjector';
13
13
  import type { LobeToolManifest } from '../tools/types';
14
14
 
15
15
  /**
@@ -249,7 +249,7 @@ export interface MessagesEngineResult {
249
249
 
250
250
  // Re-export types for convenience
251
251
 
252
- export { type AgentInfo } from '../../processors/GroupMessageSender';
252
+ export { type AgentInfo } from '../../processors/GroupRoleTransform';
253
253
  export { type AgentBuilderContext } from '../../providers/AgentBuilderContextInjector';
254
254
  export { type GroupAgentBuilderContext } from '../../providers/GroupAgentBuilderContextInjector';
255
255
  export { type GTDPlan } from '../../providers/GTDPlanInjector';
@@ -0,0 +1,211 @@
1
+ import debug from 'debug';
2
+
3
+ import { BaseProcessor } from '../base/BaseProcessor';
4
+ import type { Message, PipelineContext, ProcessorOptions } from '../types';
5
+
6
+ const log = debug('context-engine:processor:GroupOrchestrationFilterProcessor');
7
+
8
+ /**
9
+ * Default orchestration tool identifier
10
+ */
11
+ const DEFAULT_ORCHESTRATION_IDENTIFIER = 'lobe-group-management';
12
+
13
+ /**
14
+ * Default orchestration api names that should be filtered
15
+ */
16
+ const DEFAULT_ORCHESTRATION_API_NAMES = ['broadcast', 'speak', 'executeTask', 'executeTasks'];
17
+
18
+ /**
19
+ * Agent info for identifying supervisor
20
+ */
21
+ export interface OrchestrationAgentInfo {
22
+ role: 'supervisor' | 'participant';
23
+ }
24
+
25
+ /**
26
+ * Tool info structure
27
+ */
28
+ interface ToolInfo {
29
+ apiName?: string;
30
+ identifier?: string;
31
+ }
32
+
33
+ /**
34
+ * Configuration for GroupOrchestrationFilterProcessor
35
+ */
36
+ export interface GroupOrchestrationFilterConfig {
37
+ /**
38
+ * Mapping from agentId to agent info
39
+ * Used to identify supervisor messages
40
+ */
41
+ agentMap?: Record<string, OrchestrationAgentInfo>;
42
+ /**
43
+ * The current agent ID that is responding
44
+ * If the current agent is supervisor, filtering will be skipped
45
+ * (Supervisor needs to see its own orchestration history)
46
+ */
47
+ currentAgentId?: string;
48
+ /**
49
+ * Whether to enable filtering
50
+ * @default true
51
+ */
52
+ enabled?: boolean;
53
+ /**
54
+ * Api names of orchestration tools to filter
55
+ * @default ['broadcast', 'speak', 'executeTask', 'executeTasks']
56
+ */
57
+ orchestrationApiNames?: string[];
58
+ /**
59
+ * Tool identifiers that are considered orchestration tools
60
+ * @default ['lobe-group-management']
61
+ */
62
+ orchestrationToolIdentifiers?: string[];
63
+ }
64
+
65
+ /**
66
+ * Group Orchestration Filter Processor
67
+ *
68
+ * Filters out Supervisor's orchestration messages (broadcast, speak, executeTask, etc.)
69
+ * from the context to reduce noise for participant agents.
70
+ *
71
+ * These messages are coordination metadata that participant agents don't need to see.
72
+ * Filtering them reduces context window usage and prevents model confusion.
73
+ *
74
+ * Filtering rules:
75
+ * - Supervisor assistant + orchestration tool_use: REMOVE
76
+ * - Supervisor tool_result for orchestration tools: REMOVE
77
+ * - Supervisor assistant without tools: KEEP (may contain meaningful summaries)
78
+ * - Supervisor assistant + non-orchestration tools: KEEP (e.g., search)
79
+ *
80
+ * @example
81
+ * ```typescript
82
+ * const processor = new GroupOrchestrationFilterProcessor({
83
+ * agentMap: {
84
+ * 'supervisor-id': { role: 'supervisor' },
85
+ * 'agent-1': { role: 'participant' },
86
+ * },
87
+ * });
88
+ * ```
89
+ */
90
+ export class GroupOrchestrationFilterProcessor extends BaseProcessor {
91
+ readonly name = 'GroupOrchestrationFilterProcessor';
92
+
93
+ private config: GroupOrchestrationFilterConfig;
94
+ private orchestrationIdentifiers: Set<string>;
95
+ private orchestrationApiNames: Set<string>;
96
+
97
+ constructor(config: GroupOrchestrationFilterConfig = {}, options: ProcessorOptions = {}) {
98
+ super(options);
99
+ this.config = config;
100
+ this.orchestrationIdentifiers = new Set(
101
+ config.orchestrationToolIdentifiers || [DEFAULT_ORCHESTRATION_IDENTIFIER],
102
+ );
103
+ this.orchestrationApiNames = new Set(
104
+ config.orchestrationApiNames || DEFAULT_ORCHESTRATION_API_NAMES,
105
+ );
106
+ }
107
+
108
+ protected async doProcess(context: PipelineContext): Promise<PipelineContext> {
109
+ const clonedContext = this.cloneContext(context);
110
+
111
+ // Skip if disabled or no agentMap provided
112
+ if (this.config.enabled === false || !this.config.agentMap) {
113
+ log('Processor disabled or no agentMap provided, skipping');
114
+ return this.markAsExecuted(clonedContext);
115
+ }
116
+
117
+ // Skip if current agent is supervisor (supervisor needs to see its orchestration history)
118
+ if (this.isCurrentAgentSupervisor()) {
119
+ log('Current agent is supervisor, skipping orchestration filter');
120
+ return this.markAsExecuted(clonedContext);
121
+ }
122
+
123
+ let filteredCount = 0;
124
+ let assistantFiltered = 0;
125
+ let toolFiltered = 0;
126
+
127
+ const filteredMessages = clonedContext.messages.filter((msg: Message) => {
128
+ // Only filter supervisor messages
129
+ if (!this.isSupervisorMessage(msg)) {
130
+ return true;
131
+ }
132
+
133
+ // Check assistant messages with tools
134
+ if (msg.role === 'assistant' && msg.tools && msg.tools.length > 0) {
135
+ const hasOrchestrationTool = msg.tools.some((tool: ToolInfo) =>
136
+ this.isOrchestrationTool(tool),
137
+ );
138
+
139
+ if (hasOrchestrationTool) {
140
+ filteredCount++;
141
+ assistantFiltered++;
142
+ log(`Filtering supervisor orchestration assistant message: ${msg.id}`);
143
+ return false;
144
+ }
145
+ }
146
+
147
+ // Check tool result messages
148
+ if (msg.role === 'tool' && msg.plugin && this.isOrchestrationTool(msg.plugin)) {
149
+ filteredCount++;
150
+ toolFiltered++;
151
+ log(`Filtering supervisor orchestration tool result: ${msg.id}`);
152
+ return false;
153
+ }
154
+
155
+ // Keep other supervisor messages (pure text, non-orchestration tools)
156
+ return true;
157
+ });
158
+
159
+ clonedContext.messages = filteredMessages;
160
+
161
+ // Update metadata
162
+ clonedContext.metadata.orchestrationFilterProcessed = {
163
+ assistantFiltered,
164
+ filteredCount,
165
+ toolFiltered,
166
+ };
167
+
168
+ log(
169
+ `Orchestration filter completed: ${filteredCount} messages filtered (${assistantFiltered} assistant, ${toolFiltered} tool)`,
170
+ );
171
+
172
+ return this.markAsExecuted(clonedContext);
173
+ }
174
+
175
+ /**
176
+ * Check if the current agent is a supervisor
177
+ * Supervisor doesn't need orchestration messages filtered (they need to see their history)
178
+ */
179
+ private isCurrentAgentSupervisor(): boolean {
180
+ if (!this.config.currentAgentId || !this.config.agentMap) {
181
+ return false;
182
+ }
183
+
184
+ const currentAgentInfo = this.config.agentMap[this.config.currentAgentId];
185
+ return currentAgentInfo?.role === 'supervisor';
186
+ }
187
+
188
+ /**
189
+ * Check if a message is from a supervisor agent
190
+ */
191
+ private isSupervisorMessage(msg: Message): boolean {
192
+ if (!msg.agentId || !this.config.agentMap) {
193
+ return false;
194
+ }
195
+
196
+ const agentInfo = this.config.agentMap[msg.agentId];
197
+ return agentInfo?.role === 'supervisor';
198
+ }
199
+
200
+ /**
201
+ * Check if a tool is an orchestration tool that should be filtered
202
+ */
203
+ private isOrchestrationTool(tool: ToolInfo): boolean {
204
+ if (!tool) return false;
205
+
206
+ const identifier = tool.identifier || '';
207
+ const apiName = tool.apiName || '';
208
+
209
+ return this.orchestrationIdentifiers.has(identifier) && this.orchestrationApiNames.has(apiName);
210
+ }
211
+ }
@@ -0,0 +1,261 @@
1
+ import debug from 'debug';
2
+
3
+ import { BaseProcessor } from '../base/BaseProcessor';
4
+ import type { Message, PipelineContext, ProcessorOptions } from '../types';
5
+
6
+ const log = debug('context-engine:processor:GroupRoleTransformProcessor');
7
+
8
+ /**
9
+ * Agent info for message sender identification
10
+ */
11
+ export interface AgentInfo {
12
+ name: string;
13
+ role: 'supervisor' | 'participant';
14
+ }
15
+
16
+ /**
17
+ * Configuration for GroupRoleTransformProcessor
18
+ */
19
+ export interface GroupRoleTransformConfig {
20
+ /**
21
+ * Mapping from agentId to agent info
22
+ * Used to look up agent name for each message
23
+ */
24
+ agentMap: Record<string, AgentInfo>;
25
+ /**
26
+ * The current agent ID that is responding
27
+ * Messages from this agent will remain as assistant role
28
+ */
29
+ currentAgentId: string;
30
+ /**
31
+ * Custom function to generate tool name from identifier and apiName
32
+ */
33
+ genToolName?: (identifier: string, apiName: string) => string;
34
+ }
35
+
36
+ /**
37
+ * Group Role Transform Processor
38
+ *
39
+ * Transforms messages from other agents to user role with speaker tags.
40
+ * This prevents the model from imitating speaker tags in its output.
41
+ *
42
+ * From the model's perspective:
43
+ * - role: assistant = "my" responses (current agent)
44
+ * - role: user = external input (human users and other agents)
45
+ *
46
+ * Processing logic:
47
+ * 1. Current agent's messages: Keep as assistant (no modifications)
48
+ * 2. Other agents' assistant messages: Convert to user with speaker tag
49
+ * 3. Other agents' tool messages: Convert to user with tool_result tag
50
+ *
51
+ * @example
52
+ * ```typescript
53
+ * const processor = new GroupRoleTransformProcessor({
54
+ * currentAgentId: 'travel-advisor',
55
+ * agentMap: {
56
+ * 'weather-expert': { name: 'Weather Expert', role: 'participant' },
57
+ * 'travel-advisor': { name: 'Travel Advisor', role: 'participant' },
58
+ * 'supervisor': { name: 'Supervisor', role: 'supervisor' },
59
+ * }
60
+ * });
61
+ * ```
62
+ */
63
+ export class GroupRoleTransformProcessor extends BaseProcessor {
64
+ readonly name = 'GroupRoleTransformProcessor';
65
+
66
+ constructor(
67
+ private config: GroupRoleTransformConfig,
68
+ options: ProcessorOptions = {},
69
+ ) {
70
+ super(options);
71
+ }
72
+
73
+ protected async doProcess(context: PipelineContext): Promise<PipelineContext> {
74
+ const clonedContext = this.cloneContext(context);
75
+
76
+ // Skip if no currentAgentId or agentMap provided
77
+ if (!this.config.currentAgentId || !this.config.agentMap) {
78
+ log('No currentAgentId or agentMap provided, skipping processing');
79
+ return this.markAsExecuted(clonedContext);
80
+ }
81
+
82
+ let assistantTransformed = 0;
83
+ let toolTransformed = 0;
84
+
85
+ clonedContext.messages = clonedContext.messages.map((msg: Message) => {
86
+ // Process assistant messages
87
+ if (msg.role === 'assistant' && msg.agentId) {
88
+ if (msg.agentId === this.config.currentAgentId) {
89
+ // Current agent: keep as assistant, no modifications
90
+ return msg;
91
+ }
92
+
93
+ // Other agent: transform to user with speaker tag
94
+ const transformed = this.transformAssistantMessage(msg);
95
+ if (transformed !== msg) {
96
+ assistantTransformed++;
97
+ log(`Transformed assistant message from agent: ${msg.agentId} to user`);
98
+ }
99
+ return transformed;
100
+ }
101
+
102
+ // Process tool messages
103
+ if (msg.role === 'tool' && msg.agentId) {
104
+ if (msg.agentId === this.config.currentAgentId) {
105
+ // Current agent: keep as tool
106
+ return msg;
107
+ }
108
+
109
+ // Other agent: transform to user with tool_result tag
110
+ const transformed = this.transformToolMessage(msg);
111
+ if (transformed !== msg) {
112
+ toolTransformed++;
113
+ log(`Transformed tool message from agent: ${msg.agentId} to user`);
114
+ }
115
+ return transformed;
116
+ }
117
+
118
+ return msg;
119
+ });
120
+
121
+ // Update metadata
122
+ clonedContext.metadata.groupRoleTransformProcessed = {
123
+ assistantTransformed,
124
+ toolTransformed,
125
+ };
126
+
127
+ log(
128
+ `Group role transform completed: ${assistantTransformed} assistant messages, ${toolTransformed} tool messages transformed`,
129
+ );
130
+
131
+ return this.markAsExecuted(clonedContext);
132
+ }
133
+
134
+ /**
135
+ * Transform an assistant message from another agent to a user message
136
+ */
137
+ private transformAssistantMessage(msg: Message): Message {
138
+ const agentInfo = this.config.agentMap[msg.agentId];
139
+ if (!agentInfo) {
140
+ // No agent info found, keep original
141
+ return msg;
142
+ }
143
+
144
+ const agentName = agentInfo.name;
145
+ let content = this.buildSpeakerTag(agentName);
146
+
147
+ // Add original content
148
+ const originalContent = this.getStringContent(msg.content);
149
+ if (originalContent) {
150
+ content += originalContent;
151
+ }
152
+
153
+ // Add tool_use section if message has tools
154
+ if (msg.tools && msg.tools.length > 0) {
155
+ content += this.buildToolUseSection(msg.tools);
156
+ }
157
+
158
+ return {
159
+ ...msg,
160
+ content,
161
+ role: 'user',
162
+ // Remove tool-related fields as they're now embedded in content
163
+ tools: undefined,
164
+ };
165
+ }
166
+
167
+ /**
168
+ * Transform a tool result message from another agent to a user message
169
+ */
170
+ private transformToolMessage(msg: Message): Message {
171
+ const agentInfo = this.config.agentMap[msg.agentId];
172
+ if (!agentInfo) {
173
+ // No agent info found, keep original
174
+ return msg;
175
+ }
176
+
177
+ const agentName = agentInfo.name;
178
+ const toolName = this.getToolName(msg.plugin);
179
+ const toolCallId = msg.tool_call_id || 'unknown';
180
+ const resultContent = this.getStringContent(msg.content);
181
+
182
+ const content = `${this.buildSpeakerTag(agentName)}<tool_result id="${toolCallId}" name="${toolName}">
183
+ ${resultContent}
184
+ </tool_result>`;
185
+
186
+ return {
187
+ ...msg,
188
+ content,
189
+ // Remove tool-related fields
190
+ plugin: undefined,
191
+ role: 'user',
192
+ tool_call_id: undefined,
193
+ };
194
+ }
195
+
196
+ /**
197
+ * Build the speaker tag
198
+ */
199
+ private buildSpeakerTag(agentName: string): string {
200
+ return `<speaker name="${agentName}" />\n`;
201
+ }
202
+
203
+ /**
204
+ * Build the tool_use section for assistant messages with tools
205
+ */
206
+ private buildToolUseSection(tools: any[]): string {
207
+ let section = '\n\n<tool_use>\n';
208
+
209
+ for (const tool of tools) {
210
+ const toolName = this.getToolName(tool);
211
+ const toolId = tool.id || 'unknown';
212
+ const args = tool.arguments || '';
213
+
214
+ section += `<tool id="${toolId}" name="${toolName}">\n`;
215
+ section += `${args}\n`;
216
+ section += `</tool>\n`;
217
+ }
218
+
219
+ section += '</tool_use>';
220
+ return section;
221
+ }
222
+
223
+ /**
224
+ * Get tool name from tool object
225
+ */
226
+ private getToolName(tool: any): string {
227
+ if (!tool) return 'unknown';
228
+
229
+ const identifier = tool.identifier || '';
230
+ const apiName = tool.apiName || '';
231
+
232
+ if (this.config.genToolName) {
233
+ return this.config.genToolName(identifier, apiName);
234
+ }
235
+
236
+ if (identifier && apiName) {
237
+ return `${identifier}.${apiName}`;
238
+ }
239
+
240
+ return identifier || apiName || 'unknown';
241
+ }
242
+
243
+ /**
244
+ * Extract string content from message content (handles both string and array formats)
245
+ */
246
+ private getStringContent(content: string | any[]): string {
247
+ if (typeof content === 'string') {
248
+ return content;
249
+ }
250
+
251
+ if (Array.isArray(content)) {
252
+ // Extract text from array content
253
+ return content
254
+ .filter((part: any) => part.type === 'text')
255
+ .map((part: any) => part.text || '')
256
+ .join('\n');
257
+ }
258
+
259
+ return '';
260
+ }
261
+ }