@lobehub/lobehub 2.0.0-next.360 → 2.0.0-next.362

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 (76) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/Dockerfile +2 -1
  3. package/changelog/v1.json +14 -0
  4. package/locales/en-US/chat.json +3 -1
  5. package/locales/zh-CN/chat.json +2 -0
  6. package/package.json +1 -1
  7. package/packages/const/src/userMemory.ts +1 -0
  8. package/packages/context-engine/src/base/BaseEveryUserContentProvider.ts +204 -0
  9. package/packages/context-engine/src/base/BaseLastUserContentProvider.ts +1 -8
  10. package/packages/context-engine/src/base/__tests__/BaseEveryUserContentProvider.test.ts +354 -0
  11. package/packages/context-engine/src/base/constants.ts +20 -0
  12. package/packages/context-engine/src/engine/messages/MessagesEngine.ts +27 -23
  13. package/packages/context-engine/src/engine/messages/__tests__/MessagesEngine.test.ts +364 -0
  14. package/packages/context-engine/src/providers/PageEditorContextInjector.ts +17 -13
  15. package/packages/context-engine/src/providers/PageSelectionsInjector.ts +65 -0
  16. package/packages/context-engine/src/providers/__tests__/PageSelectionsInjector.test.ts +333 -0
  17. package/packages/context-engine/src/providers/index.ts +3 -1
  18. package/packages/database/src/models/userMemory/model.ts +178 -3
  19. package/packages/database/src/models/userMemory/sources/benchmarkLoCoMo.ts +1 -1
  20. package/packages/memory-user-memory/package.json +2 -1
  21. package/packages/memory-user-memory/promptfoo/evals/activity/basic/buildMessages.ts +40 -0
  22. package/packages/memory-user-memory/promptfoo/evals/activity/basic/eval.yaml +13 -0
  23. package/packages/memory-user-memory/promptfoo/evals/activity/basic/prompt.ts +5 -0
  24. package/packages/memory-user-memory/promptfoo/evals/activity/basic/tests/cases.ts +106 -0
  25. package/packages/memory-user-memory/promptfoo/evals/activity/locomo/buildMessages.ts +104 -0
  26. package/packages/memory-user-memory/promptfoo/evals/activity/locomo/eval.yaml +13 -0
  27. package/packages/memory-user-memory/promptfoo/evals/activity/locomo/prompt.ts +5 -0
  28. package/packages/memory-user-memory/promptfoo/evals/activity/locomo/tests/benchmark-locomo-payload-conv-26.json +149 -0
  29. package/packages/memory-user-memory/promptfoo/evals/activity/locomo/tests/cases.ts +72 -0
  30. package/packages/memory-user-memory/promptfoo/response-formats/activity.json +370 -0
  31. package/packages/memory-user-memory/promptfoo/response-formats/experience.json +14 -0
  32. package/packages/memory-user-memory/promptfoo/response-formats/identity.json +281 -255
  33. package/packages/memory-user-memory/promptfooconfig.yaml +1 -0
  34. package/packages/memory-user-memory/scripts/generate-response-formats.ts +26 -2
  35. package/packages/memory-user-memory/src/extractors/activity.ts +44 -0
  36. package/packages/memory-user-memory/src/extractors/gatekeeper.test.ts +2 -1
  37. package/packages/memory-user-memory/src/extractors/gatekeeper.ts +2 -1
  38. package/packages/memory-user-memory/src/extractors/index.ts +1 -0
  39. package/packages/memory-user-memory/src/prompts/gatekeeper.ts +3 -3
  40. package/packages/memory-user-memory/src/prompts/index.ts +7 -1
  41. package/packages/memory-user-memory/src/prompts/layers/activity.ts +90 -0
  42. package/packages/memory-user-memory/src/prompts/layers/index.ts +1 -0
  43. package/packages/memory-user-memory/src/providers/existingUserMemory.test.ts +25 -1
  44. package/packages/memory-user-memory/src/providers/existingUserMemory.ts +113 -0
  45. package/packages/memory-user-memory/src/schemas/activity.ts +315 -0
  46. package/packages/memory-user-memory/src/schemas/experience.ts +5 -5
  47. package/packages/memory-user-memory/src/schemas/gatekeeper.ts +1 -0
  48. package/packages/memory-user-memory/src/schemas/index.ts +1 -0
  49. package/packages/memory-user-memory/src/services/extractExecutor.ts +29 -0
  50. package/packages/memory-user-memory/src/types.ts +7 -0
  51. package/packages/prompts/src/agents/index.ts +1 -0
  52. package/packages/prompts/src/agents/pageSelectionContext.ts +28 -0
  53. package/packages/types/src/aiChat.ts +4 -0
  54. package/packages/types/src/message/common/index.ts +1 -0
  55. package/packages/types/src/message/common/metadata.ts +8 -0
  56. package/packages/types/src/message/common/pageSelection.ts +36 -0
  57. package/packages/types/src/message/ui/params.ts +16 -0
  58. package/packages/types/src/serverConfig.ts +1 -1
  59. package/packages/types/src/userMemory/layers.ts +52 -0
  60. package/packages/types/src/userMemory/list.ts +20 -2
  61. package/packages/types/src/userMemory/shared.ts +22 -1
  62. package/packages/types/src/userMemory/trace.ts +1 -0
  63. package/packages/types/src/util.ts +9 -1
  64. package/scripts/prebuild.mts +1 -0
  65. package/src/features/ChatInput/Desktop/ContextContainer/ContextList.tsx +1 -1
  66. package/src/features/Conversation/ChatInput/index.tsx +9 -1
  67. package/src/features/Conversation/Messages/User/components/MessageContent.tsx +7 -1
  68. package/src/features/Conversation/Messages/User/components/PageSelections.tsx +62 -0
  69. package/src/features/PageEditor/EditorCanvas/useAskCopilotItem.tsx +5 -1
  70. package/src/libs/next/proxy/define-config.ts +1 -0
  71. package/src/locales/default/chat.ts +3 -2
  72. package/src/server/globalConfig/parseMemoryExtractionConfig.ts +7 -1
  73. package/src/server/routers/lambda/aiChat.ts +7 -0
  74. package/src/server/services/memory/userMemory/__tests__/extract.runtime.test.ts +2 -0
  75. package/src/server/services/memory/userMemory/extract.ts +108 -7
  76. package/src/store/chat/slices/aiChat/actions/conversationLifecycle.ts +5 -19
@@ -0,0 +1,354 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import type { Message, PipelineContext } from '../../types';
4
+ import { BaseEveryUserContentProvider } from '../BaseEveryUserContentProvider';
5
+
6
+ class TestEveryUserContentProvider extends BaseEveryUserContentProvider {
7
+ readonly name = 'TestEveryUserContentProvider';
8
+
9
+ constructor(
10
+ private contentBuilder?: (
11
+ message: Message,
12
+ index: number,
13
+ isLastUser: boolean,
14
+ ) => { content: string; contextType: string } | null,
15
+ ) {
16
+ super();
17
+ }
18
+
19
+ protected buildContentForMessage(
20
+ message: Message,
21
+ index: number,
22
+ isLastUser: boolean,
23
+ ): { content: string; contextType: string } | null {
24
+ if (this.contentBuilder) {
25
+ return this.contentBuilder(message, index, isLastUser);
26
+ }
27
+ // Default: inject content for every user message
28
+ return {
29
+ content: `Content for message ${index}`,
30
+ contextType: 'test_context',
31
+ };
32
+ }
33
+
34
+ // Expose protected methods for testing
35
+ testHasSystemContextWrapper(content: string | any[]) {
36
+ return this.hasSystemContextWrapper(content);
37
+ }
38
+
39
+ testWrapWithSystemContext(content: string, contextType: string) {
40
+ return this.wrapWithSystemContext(content, contextType);
41
+ }
42
+
43
+ testCreateContextBlock(content: string, contextType: string) {
44
+ return this.createContextBlock(content, contextType);
45
+ }
46
+
47
+ testAppendToMessage(message: Message, content: string, contextType: string) {
48
+ return this.appendToMessage(message, content, contextType);
49
+ }
50
+
51
+ testFindLastUserMessageIndex(messages: Message[]) {
52
+ return this.findLastUserMessageIndex(messages);
53
+ }
54
+ }
55
+
56
+ describe('BaseEveryUserContentProvider', () => {
57
+ const createContext = (messages: any[] = []): PipelineContext => ({
58
+ initialState: {
59
+ messages: [],
60
+ model: 'test-model',
61
+ provider: 'test-provider',
62
+ },
63
+ isAborted: false,
64
+ messages,
65
+ metadata: {
66
+ maxTokens: 4000,
67
+ model: 'test-model',
68
+ },
69
+ });
70
+
71
+ describe('findLastUserMessageIndex', () => {
72
+ it('should find the last user message', () => {
73
+ const provider = new TestEveryUserContentProvider();
74
+ const messages = [
75
+ { content: 'Hello', role: 'user' },
76
+ { content: 'Hi', role: 'assistant' },
77
+ { content: 'Question', role: 'user' },
78
+ { content: 'Answer', role: 'assistant' },
79
+ ];
80
+
81
+ expect(provider.testFindLastUserMessageIndex(messages)).toBe(2);
82
+ });
83
+
84
+ it('should return -1 when no user messages exist', () => {
85
+ const provider = new TestEveryUserContentProvider();
86
+ const messages = [{ content: 'System', role: 'system' }];
87
+
88
+ expect(provider.testFindLastUserMessageIndex(messages)).toBe(-1);
89
+ });
90
+ });
91
+
92
+ describe('hasSystemContextWrapper', () => {
93
+ it('should detect existing system context wrapper in string content', () => {
94
+ const provider = new TestEveryUserContentProvider();
95
+
96
+ const withWrapper = `Question
97
+ <!-- SYSTEM CONTEXT (NOT PART OF USER QUERY) -->
98
+ <test>content</test>
99
+ <!-- END SYSTEM CONTEXT -->`;
100
+
101
+ const withoutWrapper = 'Simple question';
102
+
103
+ expect(provider.testHasSystemContextWrapper(withWrapper)).toBe(true);
104
+ expect(provider.testHasSystemContextWrapper(withoutWrapper)).toBe(false);
105
+ });
106
+
107
+ it('should detect existing system context wrapper in array content', () => {
108
+ const provider = new TestEveryUserContentProvider();
109
+
110
+ const withWrapper = [
111
+ {
112
+ text: `Question
113
+ <!-- SYSTEM CONTEXT (NOT PART OF USER QUERY) -->
114
+ <test>content</test>
115
+ <!-- END SYSTEM CONTEXT -->`,
116
+ type: 'text',
117
+ },
118
+ ];
119
+
120
+ const withoutWrapper = [{ text: 'Simple question', type: 'text' }];
121
+
122
+ expect(provider.testHasSystemContextWrapper(withWrapper)).toBe(true);
123
+ expect(provider.testHasSystemContextWrapper(withoutWrapper)).toBe(false);
124
+ });
125
+ });
126
+
127
+ describe('wrapWithSystemContext', () => {
128
+ it('should wrap content with system context markers', () => {
129
+ const provider = new TestEveryUserContentProvider();
130
+ const result = provider.testWrapWithSystemContext('Test content', 'test_type');
131
+
132
+ expect(result).toContain('<!-- SYSTEM CONTEXT (NOT PART OF USER QUERY) -->');
133
+ expect(result).toContain('<context.instruction>');
134
+ expect(result).toContain('<test_type>');
135
+ expect(result).toContain('Test content');
136
+ expect(result).toContain('</test_type>');
137
+ expect(result).toContain('<!-- END SYSTEM CONTEXT -->');
138
+ });
139
+ });
140
+
141
+ describe('createContextBlock', () => {
142
+ it('should create context block without wrapper', () => {
143
+ const provider = new TestEveryUserContentProvider();
144
+ const result = provider.testCreateContextBlock('Block content', 'block_type');
145
+
146
+ expect(result).toBe(`<block_type>
147
+ Block content
148
+ </block_type>`);
149
+ });
150
+ });
151
+
152
+ describe('appendToMessage', () => {
153
+ it('should append with new wrapper to string content without existing wrapper', () => {
154
+ const provider = new TestEveryUserContentProvider();
155
+ const message: Message = { content: 'Original question', role: 'user' };
156
+
157
+ const result = provider.testAppendToMessage(message, 'New content', 'new_type');
158
+
159
+ expect(result.content).toContain('Original question');
160
+ expect(result.content).toContain('<!-- SYSTEM CONTEXT (NOT PART OF USER QUERY) -->');
161
+ expect(result.content).toContain('<new_type>');
162
+ expect(result.content).toContain('New content');
163
+ expect(result.content).toContain('<!-- END SYSTEM CONTEXT -->');
164
+ });
165
+
166
+ it('should insert into existing wrapper in string content', () => {
167
+ const provider = new TestEveryUserContentProvider();
168
+ const message: Message = {
169
+ content: `Original question
170
+
171
+ <!-- SYSTEM CONTEXT (NOT PART OF USER QUERY) -->
172
+ <context.instruction>...</context.instruction>
173
+ <existing_type>
174
+ Existing content
175
+ </existing_type>
176
+ <!-- END SYSTEM CONTEXT -->`,
177
+ role: 'user',
178
+ };
179
+
180
+ const result = provider.testAppendToMessage(message, 'New content', 'new_type');
181
+
182
+ // Should have only one SYSTEM CONTEXT wrapper
183
+ const content = result.content as string;
184
+ const startCount = (content.match(/<!-- SYSTEM CONTEXT/g) || []).length;
185
+ const endCount = (content.match(/<!-- END SYSTEM CONTEXT/g) || []).length;
186
+
187
+ expect(startCount).toBe(1);
188
+ expect(endCount).toBe(1);
189
+ expect(content).toContain('<existing_type>');
190
+ expect(content).toContain('<new_type>');
191
+ expect(content).toContain('New content');
192
+ });
193
+
194
+ it('should handle array content without existing wrapper', () => {
195
+ const provider = new TestEveryUserContentProvider();
196
+ const message: Message = {
197
+ content: [
198
+ { text: 'Original question', type: 'text' },
199
+ { image_url: { url: 'http://example.com/img.png' }, type: 'image_url' },
200
+ ],
201
+ role: 'user',
202
+ };
203
+
204
+ const result = provider.testAppendToMessage(message, 'New content', 'new_type');
205
+
206
+ expect(result.content[0].text).toContain('Original question');
207
+ expect(result.content[0].text).toContain('<!-- SYSTEM CONTEXT');
208
+ expect(result.content[0].text).toContain('<new_type>');
209
+ expect(result.content[1].type).toBe('image_url');
210
+ });
211
+
212
+ it('should add new text part when array content has no text part', () => {
213
+ const provider = new TestEveryUserContentProvider();
214
+ const message: Message = {
215
+ content: [{ image_url: { url: 'http://example.com/img.png' }, type: 'image_url' }],
216
+ role: 'user',
217
+ };
218
+
219
+ const result = provider.testAppendToMessage(message, 'New content', 'new_type');
220
+
221
+ expect(result.content).toHaveLength(2);
222
+ expect(result.content[1].type).toBe('text');
223
+ expect(result.content[1].text).toContain('<!-- SYSTEM CONTEXT');
224
+ expect(result.content[1].text).toContain('New content');
225
+ });
226
+ });
227
+
228
+ describe('process integration', () => {
229
+ it('should inject content to all user messages', async () => {
230
+ const provider = new TestEveryUserContentProvider();
231
+ const context = createContext([
232
+ { content: 'First question', role: 'user' },
233
+ { content: 'First answer', role: 'assistant' },
234
+ { content: 'Second question', role: 'user' },
235
+ { content: 'Second answer', role: 'assistant' },
236
+ { content: 'Third question', role: 'user' },
237
+ ]);
238
+
239
+ const result = await provider.process(context);
240
+
241
+ // All user messages should have content injected
242
+ expect(result.messages[0].content).toContain('First question');
243
+ expect(result.messages[0].content).toContain('<test_context>');
244
+ expect(result.messages[0].content).toContain('Content for message 0');
245
+
246
+ expect(result.messages[2].content).toContain('Second question');
247
+ expect(result.messages[2].content).toContain('<test_context>');
248
+ expect(result.messages[2].content).toContain('Content for message 2');
249
+
250
+ expect(result.messages[4].content).toContain('Third question');
251
+ expect(result.messages[4].content).toContain('<test_context>');
252
+ expect(result.messages[4].content).toContain('Content for message 4');
253
+
254
+ // Assistant messages should be unchanged
255
+ expect(result.messages[1].content).toBe('First answer');
256
+ expect(result.messages[3].content).toBe('Second answer');
257
+ });
258
+
259
+ it('should correctly identify isLastUser parameter', async () => {
260
+ const isLastUserCalls: boolean[] = [];
261
+
262
+ const provider = new TestEveryUserContentProvider((message, index, isLastUser) => {
263
+ isLastUserCalls.push(isLastUser);
264
+ return { content: `Content ${index}`, contextType: 'test' };
265
+ });
266
+
267
+ const context = createContext([
268
+ { content: 'First', role: 'user' },
269
+ { content: 'Answer', role: 'assistant' },
270
+ { content: 'Second', role: 'user' },
271
+ { content: 'Answer', role: 'assistant' },
272
+ { content: 'Third (last)', role: 'user' },
273
+ ]);
274
+
275
+ await provider.process(context);
276
+
277
+ expect(isLastUserCalls).toEqual([false, false, true]);
278
+ });
279
+
280
+ it('should skip injection when buildContentForMessage returns null', async () => {
281
+ const provider = new TestEveryUserContentProvider((message, index) => {
282
+ // Only inject for first user message
283
+ if (index === 0) {
284
+ return { content: 'First only', contextType: 'test' };
285
+ }
286
+ return null;
287
+ });
288
+
289
+ const context = createContext([
290
+ { content: 'First question', role: 'user' },
291
+ { content: 'Answer', role: 'assistant' },
292
+ { content: 'Second question', role: 'user' },
293
+ ]);
294
+
295
+ const result = await provider.process(context);
296
+
297
+ expect(result.messages[0].content).toContain('<test>');
298
+ expect(result.messages[0].content).toContain('First only');
299
+ expect(result.messages[2].content).toBe('Second question');
300
+ });
301
+
302
+ it('should update metadata with injection count', async () => {
303
+ const provider = new TestEveryUserContentProvider();
304
+ const context = createContext([
305
+ { content: 'First', role: 'user' },
306
+ { content: 'Second', role: 'user' },
307
+ { content: 'Third', role: 'user' },
308
+ ]);
309
+
310
+ const result = await provider.process(context);
311
+
312
+ expect(result.metadata.TestEveryUserContentProviderInjectedCount).toBe(3);
313
+ });
314
+
315
+ it('should not set metadata when no injections made', async () => {
316
+ const provider = new TestEveryUserContentProvider(() => null);
317
+ const context = createContext([{ content: 'Question', role: 'user' }]);
318
+
319
+ const result = await provider.process(context);
320
+
321
+ expect(result.metadata.TestEveryUserContentProviderInjectedCount).toBeUndefined();
322
+ });
323
+ });
324
+
325
+ describe('integration with BaseLastUserContentProvider', () => {
326
+ it('should allow BaseLastUserContentProvider to reuse wrapper created by BaseEveryUserContentProvider', async () => {
327
+ // First: BaseEveryUserContentProvider injects to last user message
328
+ const everyProvider = new TestEveryUserContentProvider((message, index, isLastUser) => {
329
+ if (isLastUser) {
330
+ return { content: 'Selection content', contextType: 'user_selections' };
331
+ }
332
+ return null;
333
+ });
334
+
335
+ const context = createContext([
336
+ { content: 'First question', role: 'user' },
337
+ { content: 'Answer', role: 'assistant' },
338
+ { content: 'Last question', role: 'user' },
339
+ ]);
340
+
341
+ const result = await everyProvider.process(context);
342
+
343
+ // The last user message should have a SYSTEM CONTEXT wrapper
344
+ const lastUserContent = result.messages[2].content as string;
345
+ expect(lastUserContent).toContain('<!-- SYSTEM CONTEXT (NOT PART OF USER QUERY) -->');
346
+ expect(lastUserContent).toContain('<user_selections>');
347
+ expect(lastUserContent).toContain('Selection content');
348
+ expect(lastUserContent).toContain('<!-- END SYSTEM CONTEXT -->');
349
+
350
+ // Now BaseLastUserContentProvider can detect and reuse this wrapper
351
+ expect(everyProvider.testHasSystemContextWrapper(lastUserContent)).toBe(true);
352
+ });
353
+ });
354
+ });
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Shared constants for context injection
3
+ */
4
+
5
+ /**
6
+ * System context wrapper markers
7
+ * Used to wrap injected context content so models can distinguish it from user content
8
+ */
9
+ export const SYSTEM_CONTEXT_START = '<!-- SYSTEM CONTEXT (NOT PART OF USER QUERY) -->';
10
+ export const SYSTEM_CONTEXT_END = '<!-- END SYSTEM CONTEXT -->';
11
+
12
+ /**
13
+ * Context instruction text
14
+ * Provides guidance to the model on how to handle injected context
15
+ */
16
+ export const CONTEXT_INSTRUCTION = `<context.instruction>following part contains context information injected by the system. Please follow these instructions:
17
+
18
+ 1. Always prioritize handling user-visible content.
19
+ 2. the context is only required when user's queries rely on it.
20
+ </context.instruction>`;
@@ -28,6 +28,7 @@ import {
28
28
  HistorySummaryProvider,
29
29
  KnowledgeInjector,
30
30
  PageEditorContextInjector,
31
+ PageSelectionsInjector,
31
32
  SystemRoleInjector,
32
33
  ToolSystemRoleProvider,
33
34
  UserMemoryInjector,
@@ -143,19 +144,19 @@ export class MessagesEngine {
143
144
 
144
145
  return [
145
146
  // =============================================
146
- // Phase 2: System Role Injection
147
+ // Phase 1: System Role Injection
147
148
  // =============================================
148
149
 
149
- // 2. System role injection (agent's system role)
150
+ // 1. System role injection (agent's system role)
150
151
  new SystemRoleInjector({ systemRole }),
151
152
 
152
153
  // =============================================
153
- // Phase 2.5: First User Message Context Injection
154
+ // Phase 2: First User Message Context Injection
154
155
  // These providers inject content before the first user message
155
156
  // Order matters: first executed = first in content
156
157
  // =============================================
157
158
 
158
- // 4. User memory injection (conditionally added, injected first)
159
+ // 2. User memory injection (conditionally added, injected first)
159
160
  ...(isUserMemoryEnabled ? [new UserMemoryInjector(userMemory)] : []),
160
161
 
161
162
  // 3. Group context injection (agent identity and group info for multi-agent chat)
@@ -169,7 +170,7 @@ export class MessagesEngine {
169
170
  systemPrompt: agentGroup?.systemPrompt,
170
171
  }),
171
172
 
172
- // 4.5. GTD Plan injection (conditionally added, after user memory, before knowledge)
173
+ // 4. GTD Plan injection (conditionally added, after user memory, before knowledge)
173
174
  ...(isGTDPlanEnabled ? [new GTDPlanInjector({ enabled: true, plan: gtd.plan })] : []),
174
175
 
175
176
  // 5. Knowledge injection (full content for agent files + metadata for knowledge bases)
@@ -179,7 +180,7 @@ export class MessagesEngine {
179
180
  }),
180
181
 
181
182
  // =============================================
182
- // Phase 2.6: Additional System Context
183
+ // Phase 3: Additional System Context
183
184
  // =============================================
184
185
 
185
186
  // 6. Agent Builder context injection (current agent config/meta for editing)
@@ -212,7 +213,10 @@ export class MessagesEngine {
212
213
  historySummary,
213
214
  }),
214
215
 
215
- // 10. Page Editor context injection
216
+ // 12. Page Selections injection (inject user-selected text into each user message that has them)
217
+ new PageSelectionsInjector({ enabled: isPageEditorEnabled }),
218
+
219
+ // 10. Page Editor context injection (inject current page content to last user message)
216
220
  new PageEditorContextInjector({
217
221
  enabled: isPageEditorEnabled,
218
222
  // Use direct pageContentContext if provided (server-side), otherwise build from initialContext + stepContext (frontend)
@@ -232,37 +236,37 @@ export class MessagesEngine {
232
236
  : undefined,
233
237
  }),
234
238
 
235
- // 10.5. GTD Todo injection (conditionally added, at end of last user message)
239
+ // 11. GTD Todo injection (conditionally added, at end of last user message)
236
240
  ...(isGTDTodoEnabled ? [new GTDTodoInjector({ enabled: true, todos: gtd.todos })] : []),
237
241
 
238
242
  // =============================================
239
- // Phase 3: Message Transformation
243
+ // Phase 4: Message Transformation
240
244
  // =============================================
241
245
 
242
- // 11. Input template processing
246
+ // 13. Input template processing
243
247
  new InputTemplateProcessor({ inputTemplate }),
244
248
 
245
- // 11. Placeholder variables processing
249
+ // 14. Placeholder variables processing
246
250
  new PlaceholderVariablesProcessor({
247
251
  variableGenerators: variableGenerators || {},
248
252
  }),
249
253
 
250
- // 12. AgentCouncil message flatten (convert role=agentCouncil to standard assistant + tool messages)
254
+ // 15. AgentCouncil message flatten (convert role=agentCouncil to standard assistant + tool messages)
251
255
  new AgentCouncilFlattenProcessor(),
252
256
 
253
- // 13. Group message flatten (convert role=assistantGroup to standard assistant + tool messages)
257
+ // 16. Group message flatten (convert role=assistantGroup to standard assistant + tool messages)
254
258
  new GroupMessageFlattenProcessor(),
255
259
 
256
- // 14. Tasks message flatten (convert role=tasks to individual task messages)
260
+ // 17. Tasks message flatten (convert role=tasks to individual task messages)
257
261
  new TasksFlattenProcessor(),
258
262
 
259
- // 15. Task message processing (convert role=task to assistant with instruction + content)
263
+ // 18. Task message processing (convert role=task to assistant with instruction + content)
260
264
  new TaskMessageProcessor(),
261
265
 
262
- // 15. Supervisor role restore (convert role=supervisor back to role=assistant for model)
266
+ // 19. Supervisor role restore (convert role=supervisor back to role=assistant for model)
263
267
  new SupervisorRoleRestoreProcessor(),
264
268
 
265
- // 15.5. Group orchestration filter (remove supervisor's orchestration messages like broadcast/speak)
269
+ // 20. Group orchestration filter (remove supervisor's orchestration messages like broadcast/speak)
266
270
  // This must be BEFORE GroupRoleTransformProcessor so we filter based on original agentId/tools
267
271
  ...(isAgentGroupEnabled && agentGroup.agentMap && agentGroup.currentAgentId
268
272
  ? [
@@ -277,7 +281,7 @@ export class MessagesEngine {
277
281
  ]
278
282
  : []),
279
283
 
280
- // 16. Group role transform (convert other agents' messages to user role with speaker tags)
284
+ // 21. Group role transform (convert other agents' messages to user role with speaker tags)
281
285
  // This must be BEFORE ToolCallProcessor so other agents' tool messages are converted first
282
286
  ...(isAgentGroupEnabled && agentGroup.currentAgentId
283
287
  ? [
@@ -289,10 +293,10 @@ export class MessagesEngine {
289
293
  : []),
290
294
 
291
295
  // =============================================
292
- // Phase 4: Content Processing
296
+ // Phase 5: Content Processing
293
297
  // =============================================
294
298
 
295
- // 17. Message content processing (image encoding, etc.)
299
+ // 22. Message content processing (image encoding, etc.)
296
300
  new MessageContentProcessor({
297
301
  fileContext: fileContext || { enabled: true, includeFileUrl: true },
298
302
  isCanUseVideo: capabilities?.isCanUseVideo || (() => false),
@@ -301,7 +305,7 @@ export class MessagesEngine {
301
305
  provider,
302
306
  }),
303
307
 
304
- // 18. Tool call processing
308
+ // 23. Tool call processing
305
309
  new ToolCallProcessor({
306
310
  genToolCallingName: this.toolNameResolver.generate.bind(this.toolNameResolver),
307
311
  isCanUseFC: capabilities?.isCanUseFC || (() => true),
@@ -309,10 +313,10 @@ export class MessagesEngine {
309
313
  provider,
310
314
  }),
311
315
 
312
- // 19. Tool message reordering
316
+ // 24. Tool message reordering
313
317
  new ToolMessageReorder(),
314
318
 
315
- // 20. Message cleanup (final step, keep only necessary fields)
319
+ // 25. Message cleanup (final step, keep only necessary fields)
316
320
  new MessageCleanupProcessor(),
317
321
  ];
318
322
  }