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);
|
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 handleSchedulerMessage = useCallback((message: WsOutbound): boolean => {
|
||||||
const currentSchedulerView = schedulerViewRef.current;
|
const currentSchedulerView = schedulerViewRef.current;
|
||||||
if (!currentSchedulerView) return false;
|
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);
|
const chatMsg = serverMessageToChatMessage(message);
|
||||||
if (chatMsg) {
|
if (chatMsg) {
|
||||||
setSchedulerView((prev) =>
|
setSchedulerView((prev) =>
|
||||||
|
|||||||
@ -388,6 +388,39 @@ describe('useChat - handleServerMessage characterization', () => {
|
|||||||
expect(result.current.messages.find((m) => m.id === 'm1')).toBeUndefined();
|
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', () => {
|
it('12. tool_result with tool_name=todo_write triggers a list_todos command in main view', () => {
|
||||||
const { result, sendMessage } = renderUseChat();
|
const { result, sendMessage } = renderUseChat();
|
||||||
const todoWriteResult: ToolResult = {
|
const todoWriteResult: ToolResult = {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user