fix(web): 修复正在执行的会话消息泄露到定时任务视图
handleSchedulerMessage (Tier 1 路由) 原先无条件吞掉所有可转换的 chat 消息,导致用户打开定时任务历史视图时,当前主会话正在执行 的 stream_delta/assistant_response 等实时消息被错误追加到 schedulerView.messages。 修复:调度器视图是历史会话查看器,只接收不带 topic_id 的历史 加载消息;实时流式消息(stream_delta/stream_end)和带 topic_id 的实时消息 fall through 到 Tier 2/3 路由到主视图,不再泄露。 - useSchedulerView.ts: handleSchedulerMessage 增加流式和 topic_id 过滤,实时消息 fall through - useChat.test.ts: 新增用例 16 验证流式和带 topic_id 消息不进入 schedulerView,无 topic_id 历史消息仍正常进入
This commit is contained in:
parent
f8c984aef4
commit
00735395ca
@ -75,11 +75,25 @@ export function useSchedulerView(): UseSchedulerViewReturn {
|
||||
setSchedulerView(null);
|
||||
}, []);
|
||||
|
||||
/** Tier 1 路由:调度器视图激活时,chat 消息追加到 schedulerView;非 chat 消息 fall through */
|
||||
/** Tier 1 路由:调度器视图激活时,仅接收属于该定时任务会话的历史消息。
|
||||
* 实时流式消息(stream_delta/stream_end)必然属于当前正在执行的会话,不走历史视图;
|
||||
* 带 topic_id 的消息属于实时执行的会话(历史加载的消息不带 topic_id),也不进入。
|
||||
* 两类消息均 fall through 到 Tier 2/3 路由到主视图,避免"正在执行的会话泄露到定时任务视图"。 */
|
||||
const handleSchedulerMessage = useCallback((message: WsOutbound): boolean => {
|
||||
const currentSchedulerView = schedulerViewRef.current;
|
||||
if (!currentSchedulerView) return false;
|
||||
|
||||
// 实时流式消息不进入调度器历史视图(历史加载不会产生流式)
|
||||
if (message.type === 'stream_delta' || message.type === 'stream_end') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 带 topic_id 的消息属于实时执行的会话,不进入历史视图
|
||||
const topicId = (message as { topic_id?: string }).topic_id;
|
||||
if (topicId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const chatMsg = serverMessageToChatMessage(message);
|
||||
if (chatMsg) {
|
||||
setSchedulerView((prev) =>
|
||||
|
||||
@ -388,6 +388,39 @@ describe('useChat - handleServerMessage characterization', () => {
|
||||
expect(result.current.messages.find((m) => m.id === 'm1')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('16. scheduler view active: realtime stream_delta and topic-tagged messages do NOT leak into schedulerView', () => {
|
||||
const { result } = renderUseChat();
|
||||
const lookup: SchedulerJobSessionLookup = { channel: 'scheduler', chat_id: 'job-chat' };
|
||||
act(() => result.current.enterSchedulerJobView(lookup, 'job1', 'job desc'));
|
||||
expect(result.current.schedulerView).not.toBeNull();
|
||||
|
||||
// 实时流式消息不应进入历史视图(即使 schedulerView 激活)
|
||||
const realtimeStream: StreamDelta = {
|
||||
type: 'stream_delta',
|
||||
id: 'leak1',
|
||||
delta: 'leaking content',
|
||||
topic_id: 't-realtime',
|
||||
};
|
||||
act(() => result.current.handleServerMessage(realtimeStream));
|
||||
expect(result.current.schedulerView?.messages.find((m) => m.id === 'leak1')).toBeUndefined();
|
||||
|
||||
// 带 topic_id 的 assistant_response 也不应进入历史视图
|
||||
const taggedAssistant: AssistantResponse = {
|
||||
type: 'assistant_response',
|
||||
id: 'leak2',
|
||||
content: 'tagged response',
|
||||
role: 'assistant',
|
||||
topic_id: 't-realtime',
|
||||
};
|
||||
act(() => result.current.handleServerMessage(taggedAssistant));
|
||||
expect(result.current.schedulerView?.messages.find((m) => m.id === 'leak2')).toBeUndefined();
|
||||
|
||||
// 无 topic_id 的历史消息仍正常进入(保持用例 11 的行为)
|
||||
act(() => result.current.handleServerMessage(assistantResponse));
|
||||
expect(result.current.schedulerView?.messages).toHaveLength(1);
|
||||
expect(result.current.schedulerView?.messages[0].content).toBe('Hello world');
|
||||
});
|
||||
|
||||
it('12. tool_result with tool_name=todo_write triggers a list_todos command in main view', () => {
|
||||
const { result, sendMessage } = renderUseChat();
|
||||
const todoWriteResult: ToolResult = {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user