🐛 fix: fix page content mismatch when switch quickly (#11505)

* filter GroupOrchestration action

* fix page issue
This commit is contained in:
Arvin Xu
2026-01-15 10:51:59 +08:00
committed by GitHub
parent b2f44bee8a
commit 0cb1374cf7
5 changed files with 1010 additions and 0 deletions

View File

@@ -7,6 +7,7 @@ import { ContextEngine } from '../../pipeline';
import {
AgentCouncilFlattenProcessor,
GroupMessageFlattenProcessor,
GroupOrchestrationFilterProcessor,
GroupRoleTransformProcessor,
HistoryTruncateProcessor,
InputTemplateProcessor,
@@ -274,6 +275,21 @@ export class MessagesEngine {
// 15. Supervisor role restore (convert role=supervisor back to role=assistant for model)
new SupervisorRoleRestoreProcessor(),
// 15.5. Group orchestration filter (remove supervisor's orchestration messages like broadcast/speak)
// This must be BEFORE GroupRoleTransformProcessor so we filter based on original agentId/tools
...(isAgentGroupEnabled && agentGroup.agentMap && agentGroup.currentAgentId
? [
new GroupOrchestrationFilterProcessor({
agentMap: Object.fromEntries(
Object.entries(agentGroup.agentMap).map(([id, info]) => [id, { role: info.role }]),
),
currentAgentId: agentGroup.currentAgentId,
// Only enabled when current agent is NOT supervisor (supervisor needs to see orchestration history)
enabled: agentGroup.currentAgentRole !== 'supervisor',
}),
]
: []),
// 16. Group role transform (convert other agents' messages to user role with speaker tags)
// This must be BEFORE ToolCallProcessor so other agents' tool messages are converted first
...(isAgentGroupEnabled && agentGroup.currentAgentId

View File

@@ -0,0 +1,211 @@
import debug from 'debug';
import { BaseProcessor } from '../base/BaseProcessor';
import type { Message, PipelineContext, ProcessorOptions } from '../types';
const log = debug('context-engine:processor:GroupOrchestrationFilterProcessor');
/**
* Default orchestration tool identifier
*/
const DEFAULT_ORCHESTRATION_IDENTIFIER = 'lobe-group-management';
/**
* Default orchestration api names that should be filtered
*/
const DEFAULT_ORCHESTRATION_API_NAMES = ['broadcast', 'speak', 'executeTask', 'executeTasks'];
/**
* Agent info for identifying supervisor
*/
export interface OrchestrationAgentInfo {
role: 'supervisor' | 'participant';
}
/**
* Tool info structure
*/
interface ToolInfo {
apiName?: string;
identifier?: string;
}
/**
* Configuration for GroupOrchestrationFilterProcessor
*/
export interface GroupOrchestrationFilterConfig {
/**
* Mapping from agentId to agent info
* Used to identify supervisor messages
*/
agentMap?: Record<string, OrchestrationAgentInfo>;
/**
* The current agent ID that is responding
* If the current agent is supervisor, filtering will be skipped
* (Supervisor needs to see its own orchestration history)
*/
currentAgentId?: string;
/**
* Whether to enable filtering
* @default true
*/
enabled?: boolean;
/**
* Api names of orchestration tools to filter
* @default ['broadcast', 'speak', 'executeTask', 'executeTasks']
*/
orchestrationApiNames?: string[];
/**
* Tool identifiers that are considered orchestration tools
* @default ['lobe-group-management']
*/
orchestrationToolIdentifiers?: string[];
}
/**
* Group Orchestration Filter Processor
*
* Filters out Supervisor's orchestration messages (broadcast, speak, executeTask, etc.)
* from the context to reduce noise for participant agents.
*
* These messages are coordination metadata that participant agents don't need to see.
* Filtering them reduces context window usage and prevents model confusion.
*
* Filtering rules:
* - Supervisor assistant + orchestration tool_use: REMOVE
* - Supervisor tool_result for orchestration tools: REMOVE
* - Supervisor assistant without tools: KEEP (may contain meaningful summaries)
* - Supervisor assistant + non-orchestration tools: KEEP (e.g., search)
*
* @example
* ```typescript
* const processor = new GroupOrchestrationFilterProcessor({
* agentMap: {
* 'supervisor-id': { role: 'supervisor' },
* 'agent-1': { role: 'participant' },
* },
* });
* ```
*/
export class GroupOrchestrationFilterProcessor extends BaseProcessor {
readonly name = 'GroupOrchestrationFilterProcessor';
private config: GroupOrchestrationFilterConfig;
private orchestrationIdentifiers: Set<string>;
private orchestrationApiNames: Set<string>;
constructor(config: GroupOrchestrationFilterConfig = {}, options: ProcessorOptions = {}) {
super(options);
this.config = config;
this.orchestrationIdentifiers = new Set(
config.orchestrationToolIdentifiers || [DEFAULT_ORCHESTRATION_IDENTIFIER],
);
this.orchestrationApiNames = new Set(
config.orchestrationApiNames || DEFAULT_ORCHESTRATION_API_NAMES,
);
}
protected async doProcess(context: PipelineContext): Promise<PipelineContext> {
const clonedContext = this.cloneContext(context);
// Skip if disabled or no agentMap provided
if (this.config.enabled === false || !this.config.agentMap) {
log('Processor disabled or no agentMap provided, skipping');
return this.markAsExecuted(clonedContext);
}
// Skip if current agent is supervisor (supervisor needs to see its orchestration history)
if (this.isCurrentAgentSupervisor()) {
log('Current agent is supervisor, skipping orchestration filter');
return this.markAsExecuted(clonedContext);
}
let filteredCount = 0;
let assistantFiltered = 0;
let toolFiltered = 0;
const filteredMessages = clonedContext.messages.filter((msg: Message) => {
// Only filter supervisor messages
if (!this.isSupervisorMessage(msg)) {
return true;
}
// Check assistant messages with tools
if (msg.role === 'assistant' && msg.tools && msg.tools.length > 0) {
const hasOrchestrationTool = msg.tools.some((tool: ToolInfo) =>
this.isOrchestrationTool(tool),
);
if (hasOrchestrationTool) {
filteredCount++;
assistantFiltered++;
log(`Filtering supervisor orchestration assistant message: ${msg.id}`);
return false;
}
}
// Check tool result messages
if (msg.role === 'tool' && msg.plugin && this.isOrchestrationTool(msg.plugin)) {
filteredCount++;
toolFiltered++;
log(`Filtering supervisor orchestration tool result: ${msg.id}`);
return false;
}
// Keep other supervisor messages (pure text, non-orchestration tools)
return true;
});
clonedContext.messages = filteredMessages;
// Update metadata
clonedContext.metadata.orchestrationFilterProcessed = {
assistantFiltered,
filteredCount,
toolFiltered,
};
log(
`Orchestration filter completed: ${filteredCount} messages filtered (${assistantFiltered} assistant, ${toolFiltered} tool)`,
);
return this.markAsExecuted(clonedContext);
}
/**
* Check if the current agent is a supervisor
* Supervisor doesn't need orchestration messages filtered (they need to see their history)
*/
private isCurrentAgentSupervisor(): boolean {
if (!this.config.currentAgentId || !this.config.agentMap) {
return false;
}
const currentAgentInfo = this.config.agentMap[this.config.currentAgentId];
return currentAgentInfo?.role === 'supervisor';
}
/**
* Check if a message is from a supervisor agent
*/
private isSupervisorMessage(msg: Message): boolean {
if (!msg.agentId || !this.config.agentMap) {
return false;
}
const agentInfo = this.config.agentMap[msg.agentId];
return agentInfo?.role === 'supervisor';
}
/**
* Check if a tool is an orchestration tool that should be filtered
*/
private isOrchestrationTool(tool: ToolInfo): boolean {
if (!tool) return false;
const identifier = tool.identifier || '';
const apiName = tool.apiName || '';
return this.orchestrationIdentifiers.has(identifier) && this.orchestrationApiNames.has(apiName);
}
}

View File

@@ -0,0 +1,770 @@
import { describe, expect, it } from 'vitest';
import type { PipelineContext } from '../../types';
import { GroupOrchestrationFilterProcessor } from '../GroupOrchestrationFilter';
describe('GroupOrchestrationFilterProcessor', () => {
const createContext = (messages: any[]): PipelineContext => ({
initialState: { messages: [] },
isAborted: false,
messages,
metadata: {},
});
const defaultConfig = {
agentMap: {
'agent-a': { role: 'participant' as const },
'agent-b': { role: 'participant' as const },
'supervisor': { role: 'supervisor' as const },
},
currentAgentId: 'agent-a', // Default to participant agent
};
describe('filtering supervisor orchestration messages', () => {
it('should filter supervisor assistant message with broadcast tool', async () => {
const processor = new GroupOrchestrationFilterProcessor(defaultConfig);
const context = createContext([
{ content: 'User question', id: 'msg_1', role: 'user' },
{
agentId: 'supervisor',
content: 'Let me coordinate the agents...',
id: 'msg_2',
role: 'assistant',
tools: [
{
apiName: 'broadcast',
arguments: '{"agentIds": ["agent-a", "agent-b"], "instruction": "Please respond"}',
id: 'call_1',
identifier: 'lobe-group-management',
},
],
},
{ content: 'Agent response', id: 'msg_3', role: 'assistant' },
]);
const result = await processor.process(context);
expect(result.messages).toHaveLength(2);
expect(result.messages[0].id).toBe('msg_1');
expect(result.messages[1].id).toBe('msg_3');
});
it('should filter supervisor assistant message with speak tool', async () => {
const processor = new GroupOrchestrationFilterProcessor(defaultConfig);
const context = createContext([
{
agentId: 'supervisor',
content: 'Asking agent-a to respond',
id: 'msg_1',
role: 'assistant',
tools: [
{
apiName: 'speak',
arguments: '{"agentId": "agent-a", "instruction": "Please help"}',
id: 'call_1',
identifier: 'lobe-group-management',
},
],
},
]);
const result = await processor.process(context);
expect(result.messages).toHaveLength(0);
});
it('should filter supervisor assistant message with executeTask tool', async () => {
const processor = new GroupOrchestrationFilterProcessor(defaultConfig);
const context = createContext([
{
agentId: 'supervisor',
content: 'Executing task',
id: 'msg_1',
role: 'assistant',
tools: [
{
apiName: 'executeTask',
arguments: '{"task": "do something"}',
id: 'call_1',
identifier: 'lobe-group-management',
},
],
},
]);
const result = await processor.process(context);
expect(result.messages).toHaveLength(0);
});
it('should filter supervisor assistant message with executeTasks tool', async () => {
const processor = new GroupOrchestrationFilterProcessor(defaultConfig);
const context = createContext([
{
agentId: 'supervisor',
content: 'Executing multiple tasks',
id: 'msg_1',
role: 'assistant',
tools: [
{
apiName: 'executeTasks',
arguments: '{"tasks": ["task1", "task2"]}',
id: 'call_1',
identifier: 'lobe-group-management',
},
],
},
]);
const result = await processor.process(context);
expect(result.messages).toHaveLength(0);
});
});
describe('filtering supervisor tool results', () => {
it('should filter supervisor tool result for broadcast', async () => {
const processor = new GroupOrchestrationFilterProcessor(defaultConfig);
const context = createContext([
{ content: 'User question', id: 'msg_1', role: 'user' },
{
agentId: 'supervisor',
content: 'Triggered broadcast to agents: agent-a, agent-b',
id: 'msg_2',
plugin: {
apiName: 'broadcast',
identifier: 'lobe-group-management',
},
role: 'tool',
tool_call_id: 'call_1',
},
{ content: 'Instruction from supervisor', id: 'msg_3', role: 'user' },
]);
const result = await processor.process(context);
expect(result.messages).toHaveLength(2);
expect(result.messages[0].id).toBe('msg_1');
expect(result.messages[1].id).toBe('msg_3');
});
it('should filter supervisor tool result for speak', async () => {
const processor = new GroupOrchestrationFilterProcessor(defaultConfig);
const context = createContext([
{
agentId: 'supervisor',
content: 'Triggered speak to agent-a',
id: 'msg_1',
plugin: {
apiName: 'speak',
identifier: 'lobe-group-management',
},
role: 'tool',
tool_call_id: 'call_1',
},
]);
const result = await processor.process(context);
expect(result.messages).toHaveLength(0);
});
});
describe('keeping non-orchestration messages', () => {
it('should keep supervisor assistant message without tools', async () => {
const processor = new GroupOrchestrationFilterProcessor(defaultConfig);
const context = createContext([
{
agentId: 'supervisor',
content: 'Here is a summary of the discussion...',
id: 'msg_1',
role: 'assistant',
},
]);
const result = await processor.process(context);
expect(result.messages).toHaveLength(1);
expect(result.messages[0].content).toBe('Here is a summary of the discussion...');
});
it('should keep supervisor assistant message with non-orchestration tools', async () => {
const processor = new GroupOrchestrationFilterProcessor(defaultConfig);
const context = createContext([
{
agentId: 'supervisor',
content: 'Let me search for information',
id: 'msg_1',
role: 'assistant',
tools: [
{
apiName: 'search',
arguments: '{"query": "test"}',
id: 'call_1',
identifier: 'web-search',
},
],
},
]);
const result = await processor.process(context);
expect(result.messages).toHaveLength(1);
expect(result.messages[0].id).toBe('msg_1');
});
it('should keep supervisor tool result for non-orchestration tools', async () => {
const processor = new GroupOrchestrationFilterProcessor(defaultConfig);
const context = createContext([
{
agentId: 'supervisor',
content: '{"results": ["item1", "item2"]}',
id: 'msg_1',
plugin: {
apiName: 'search',
identifier: 'web-search',
},
role: 'tool',
tool_call_id: 'call_1',
},
]);
const result = await processor.process(context);
expect(result.messages).toHaveLength(1);
});
it('should keep all participant agent messages', async () => {
const processor = new GroupOrchestrationFilterProcessor(defaultConfig);
const context = createContext([
{
agentId: 'agent-a',
content: 'Participant response',
id: 'msg_1',
role: 'assistant',
tools: [
{
apiName: 'broadcast',
arguments: '{}',
id: 'call_1',
identifier: 'lobe-group-management',
},
],
},
{
agentId: 'agent-b',
content: 'Tool result',
id: 'msg_2',
plugin: {
apiName: 'broadcast',
identifier: 'lobe-group-management',
},
role: 'tool',
tool_call_id: 'call_1',
},
]);
const result = await processor.process(context);
// Participant messages are never filtered, even with orchestration tools
expect(result.messages).toHaveLength(2);
});
it('should keep user messages unchanged', async () => {
const processor = new GroupOrchestrationFilterProcessor(defaultConfig);
const context = createContext([
{ content: 'User question 1', id: 'msg_1', role: 'user' },
{ content: 'User question 2', id: 'msg_2', role: 'user' },
]);
const result = await processor.process(context);
expect(result.messages).toHaveLength(2);
});
it('should keep messages without agentId', async () => {
const processor = new GroupOrchestrationFilterProcessor(defaultConfig);
const context = createContext([
{
content: 'System message',
id: 'msg_1',
role: 'system',
},
{
content: 'Assistant without agentId',
id: 'msg_2',
role: 'assistant',
tools: [
{
apiName: 'broadcast',
arguments: '{}',
id: 'call_1',
identifier: 'lobe-group-management',
},
],
},
]);
const result = await processor.process(context);
expect(result.messages).toHaveLength(2);
});
});
describe('configuration options', () => {
it('should skip processing when disabled', async () => {
const processor = new GroupOrchestrationFilterProcessor({
...defaultConfig,
enabled: false,
});
const context = createContext([
{
agentId: 'supervisor',
content: 'Orchestration message',
id: 'msg_1',
role: 'assistant',
tools: [
{
apiName: 'broadcast',
arguments: '{}',
id: 'call_1',
identifier: 'lobe-group-management',
},
],
},
]);
const result = await processor.process(context);
expect(result.messages).toHaveLength(1);
});
it('should skip processing when current agent is supervisor', async () => {
const processor = new GroupOrchestrationFilterProcessor({
...defaultConfig,
currentAgentId: 'supervisor', // Supervisor as current agent
});
const context = createContext([
{
agentId: 'supervisor',
content: 'Orchestration message',
id: 'msg_1',
role: 'assistant',
tools: [
{
apiName: 'broadcast',
arguments: '{}',
id: 'call_1',
identifier: 'lobe-group-management',
},
],
},
{
agentId: 'supervisor',
content: 'Tool result',
id: 'msg_2',
plugin: {
apiName: 'broadcast',
identifier: 'lobe-group-management',
},
role: 'tool',
tool_call_id: 'call_1',
},
]);
const result = await processor.process(context);
// Supervisor should see all messages including orchestration ones
expect(result.messages).toHaveLength(2);
});
it('should skip processing when no agentMap provided', async () => {
const processor = new GroupOrchestrationFilterProcessor({});
const context = createContext([
{
agentId: 'supervisor',
content: 'Orchestration message',
id: 'msg_1',
role: 'assistant',
tools: [
{
apiName: 'broadcast',
arguments: '{}',
id: 'call_1',
identifier: 'lobe-group-management',
},
],
},
]);
const result = await processor.process(context);
expect(result.messages).toHaveLength(1);
});
it('should skip processing when no currentAgentId provided', async () => {
const processor = new GroupOrchestrationFilterProcessor({
agentMap: defaultConfig.agentMap,
// No currentAgentId
});
const context = createContext([
{
agentId: 'supervisor',
content: 'Orchestration message',
id: 'msg_1',
role: 'assistant',
tools: [
{
apiName: 'broadcast',
arguments: '{}',
id: 'call_1',
identifier: 'lobe-group-management',
},
],
},
]);
const result = await processor.process(context);
// Without currentAgentId, can't determine if supervisor, so treat as participant and filter
// Actually, isCurrentAgentSupervisor returns false when no currentAgentId, so filtering happens
expect(result.messages).toHaveLength(0);
});
it('should use custom orchestration tool identifiers', async () => {
const processor = new GroupOrchestrationFilterProcessor({
...defaultConfig,
orchestrationToolIdentifiers: ['custom-orchestration'],
});
const context = createContext([
{
agentId: 'supervisor',
content: 'Custom orchestration',
id: 'msg_1',
role: 'assistant',
tools: [
{
apiName: 'broadcast',
arguments: '{}',
id: 'call_1',
identifier: 'custom-orchestration',
},
],
},
{
agentId: 'supervisor',
content: 'Default orchestration - should not be filtered',
id: 'msg_2',
role: 'assistant',
tools: [
{
apiName: 'broadcast',
arguments: '{}',
id: 'call_2',
identifier: 'lobe-group-management',
},
],
},
]);
const result = await processor.process(context);
expect(result.messages).toHaveLength(1);
expect(result.messages[0].id).toBe('msg_2');
});
it('should use custom orchestration api names', async () => {
const processor = new GroupOrchestrationFilterProcessor({
...defaultConfig,
orchestrationApiNames: ['customBroadcast'],
});
const context = createContext([
{
agentId: 'supervisor',
content: 'Custom api name',
id: 'msg_1',
role: 'assistant',
tools: [
{
apiName: 'customBroadcast',
arguments: '{}',
id: 'call_1',
identifier: 'lobe-group-management',
},
],
},
{
agentId: 'supervisor',
content: 'Default broadcast - should not be filtered',
id: 'msg_2',
role: 'assistant',
tools: [
{
apiName: 'broadcast',
arguments: '{}',
id: 'call_2',
identifier: 'lobe-group-management',
},
],
},
]);
const result = await processor.process(context);
expect(result.messages).toHaveLength(1);
expect(result.messages[0].id).toBe('msg_2');
});
});
describe('edge cases', () => {
it('should handle empty messages array', async () => {
const processor = new GroupOrchestrationFilterProcessor(defaultConfig);
const context = createContext([]);
const result = await processor.process(context);
expect(result.messages).toHaveLength(0);
});
it('should handle message with empty tools array', async () => {
const processor = new GroupOrchestrationFilterProcessor(defaultConfig);
const context = createContext([
{
agentId: 'supervisor',
content: 'Message with empty tools',
id: 'msg_1',
role: 'assistant',
tools: [],
},
]);
const result = await processor.process(context);
// Empty tools array means no orchestration tools, so message is kept
expect(result.messages).toHaveLength(1);
});
it('should handle tool without identifier', async () => {
const processor = new GroupOrchestrationFilterProcessor(defaultConfig);
const context = createContext([
{
agentId: 'supervisor',
content: 'Tool without identifier',
id: 'msg_1',
role: 'assistant',
tools: [
{
apiName: 'broadcast',
arguments: '{}',
id: 'call_1',
// Missing identifier
},
],
},
]);
const result = await processor.process(context);
// Tool without identifier doesn't match orchestration pattern
expect(result.messages).toHaveLength(1);
});
it('should handle tool without apiName', async () => {
const processor = new GroupOrchestrationFilterProcessor(defaultConfig);
const context = createContext([
{
agentId: 'supervisor',
content: 'Tool without apiName',
id: 'msg_1',
role: 'assistant',
tools: [
{
arguments: '{}',
id: 'call_1',
identifier: 'lobe-group-management',
// Missing apiName
},
],
},
]);
const result = await processor.process(context);
// Tool without apiName doesn't match orchestration pattern
expect(result.messages).toHaveLength(1);
});
it('should track filter counts in metadata', async () => {
const processor = new GroupOrchestrationFilterProcessor(defaultConfig);
const context = createContext([
{
agentId: 'supervisor',
content: 'Broadcast',
id: 'msg_1',
role: 'assistant',
tools: [
{
apiName: 'broadcast',
arguments: '{}',
id: 'call_1',
identifier: 'lobe-group-management',
},
],
},
{
agentId: 'supervisor',
content: 'Tool result',
id: 'msg_2',
plugin: {
apiName: 'broadcast',
identifier: 'lobe-group-management',
},
role: 'tool',
tool_call_id: 'call_1',
},
{
agentId: 'supervisor',
content: 'Speak',
id: 'msg_3',
role: 'assistant',
tools: [
{
apiName: 'speak',
arguments: '{}',
id: 'call_2',
identifier: 'lobe-group-management',
},
],
},
]);
const result = await processor.process(context);
expect(result.metadata.orchestrationFilterProcessed).toEqual({
assistantFiltered: 2,
filteredCount: 3,
toolFiltered: 1,
});
});
});
describe('comprehensive end-to-end filtering', () => {
it('should correctly filter a full group conversation with orchestration messages', async () => {
const processor = new GroupOrchestrationFilterProcessor({
agentMap: {
'agent-a': { role: 'participant' },
'agent-b': { role: 'participant' },
'supervisor': { role: 'supervisor' },
},
});
const inputMessages = [
// 1. User's original question
{ content: '帮我规划杭州行程', id: 'msg_1', role: 'user' },
// 2. Supervisor broadcasts - SHOULD BE FILTERED
{
agentId: 'supervisor',
content: '好的,让我协调专家们...',
id: 'msg_2',
role: 'assistant',
tools: [
{
apiName: 'broadcast',
arguments: '{"agentIds": ["agent-a", "agent-b"], "instruction": "请给建议"}',
id: 'call_1',
identifier: 'lobe-group-management',
},
],
},
// 3. Broadcast tool result - SHOULD BE FILTERED
{
agentId: 'supervisor',
content: 'Triggered broadcast to agents: agent-a, agent-b',
id: 'msg_3',
plugin: {
apiName: 'broadcast',
identifier: 'lobe-group-management',
},
role: 'tool',
tool_call_id: 'call_1',
},
// 4. Actual instruction (injected by broadcast) - SHOULD BE KEPT
{ content: '请各位专家给出杭州行程建议', id: 'msg_4', role: 'user' },
// 5. Agent A response - SHOULD BE KEPT
{ agentId: 'agent-a', content: '推荐西湖景区', id: 'msg_5', role: 'assistant' },
// 6. Agent B response - SHOULD BE KEPT
{ agentId: 'agent-b', content: '推荐楼外楼', id: 'msg_6', role: 'assistant' },
// 7. Supervisor uses speak - SHOULD BE FILTERED
{
agentId: 'supervisor',
content: '让 agent-a 总结一下',
id: 'msg_7',
role: 'assistant',
tools: [
{
apiName: 'speak',
arguments: '{"agentId": "agent-a", "instruction": "请总结"}',
id: 'call_2',
identifier: 'lobe-group-management',
},
],
},
// 8. Speak tool result - SHOULD BE FILTERED
{
agentId: 'supervisor',
content: 'Triggered speak to agent-a',
id: 'msg_8',
plugin: {
apiName: 'speak',
identifier: 'lobe-group-management',
},
role: 'tool',
tool_call_id: 'call_2',
},
// 9. Supervisor's summary (pure text, no tools) - SHOULD BE KEPT
{
agentId: 'supervisor',
content: '以上就是专家们的建议汇总',
id: 'msg_9',
role: 'assistant',
},
// 10. Supervisor uses search tool - SHOULD BE KEPT
{
agentId: 'supervisor',
content: '让我搜索一下更多信息',
id: 'msg_10',
role: 'assistant',
tools: [
{
apiName: 'search',
arguments: '{"query": "杭州景点"}',
id: 'call_3',
identifier: 'web-search',
},
],
},
];
const context = createContext(inputMessages);
const result = await processor.process(context);
// Should have: msg_1, msg_4, msg_5, msg_6, msg_9, msg_10 (6 messages)
expect(result.messages).toHaveLength(6);
expect(result.messages.map((m) => m.id)).toEqual([
'msg_1',
'msg_4',
'msg_5',
'msg_6',
'msg_9',
'msg_10',
]);
// Verify metadata
expect(result.metadata.orchestrationFilterProcessed).toEqual({
assistantFiltered: 2, // msg_2, msg_7
filteredCount: 4, // msg_2, msg_3, msg_7, msg_8
toolFiltered: 2, // msg_3, msg_8
});
});
});
});

View File

@@ -1,6 +1,11 @@
// Transformer processors
export { AgentCouncilFlattenProcessor } from './AgentCouncilFlatten';
export { GroupMessageFlattenProcessor } from './GroupMessageFlatten';
export {
type GroupOrchestrationFilterConfig,
GroupOrchestrationFilterProcessor,
type OrchestrationAgentInfo,
} from './GroupOrchestrationFilter';
export { GroupRoleTransformProcessor } from './GroupRoleTransform';
export { HistoryTruncateProcessor } from './HistoryTruncate';
export { InputTemplateProcessor } from './InputTemplate';

View File

@@ -191,6 +191,14 @@ export const createDocumentSlice: StateCreator<
// Both documentId and editor are guaranteed to be defined when this callback is called
if (!document || !documentId || !editor) return;
// Check if this response is still for the current active document
// This prevents race conditions when quickly switching between documents
const currentActiveId = get().activeDocumentId;
if (currentActiveId && currentActiveId !== documentId) {
// User has already switched to another document, discard this stale response
return;
}
// Initialize document with editor
get().initDocumentWithEditor({
autoSave,