feat(web): 流式帧按会话过滤,非激活会话不再串入当前气泡
- WsDownEvent 增 sessionId;流式分支(message/thinking/tool_call/tool_execution/stream_chunk/stream_end 及 legacy thinking/tool_used)统一过 streamFrameApplies - 会话列表未解析(activeSessionId 为空)时保持改前行为,避免误丢主人自己的流 - 抽出 lib/streamScope.ts 承载该判断,ws.ts 回到 500 行红线内 - messageSync.test.ts 增 21 项断言(跨会话丢弃/激活追加/无 session 兼容/未解析回退)
This commit is contained in:
1 parent
61cc32c44b
commit
04a8ee0727
4 files changed
+171
-7
No files matched your search
@@ -14,6 +14,7 @@ import {
|
||||
toRecord,
|
||||
} from "./wsParse";
|
||||
import { MODES, type AwakenState, type ChatMessage, type Mode, type WsUpEvent } from "./types";
|
||||
import { streamFrameApplies } from "./streamScope";
|
||||
|
||||
/**
|
||||
* Realtime client for `/ws/chat`. Reconnects with exponential backoff and
|
||||
@@ -83,10 +84,15 @@ export function dispatchFrame(ev: { type: string; sessionId?: string; data: unkn
|
||||
|
||||
switch (ev.type) {
|
||||
case "stream_chunk": {
|
||||
if (!streamFrameApplies(ev, r, s.activeSessionId)) break;
|
||||
if (r && typeof r.delta === "string") s.appendChunk(r.delta);
|
||||
break;
|
||||
}
|
||||
case "stream_end": {
|
||||
// Another session's turn ending must not close the streaming bubble on
|
||||
// screen or clear this conversation's busy badge (both are single-slot
|
||||
// state: the last message's flag, and one shell-wide `sessionBusy`).
|
||||
if (!streamFrameApplies(ev, r, s.activeSessionId)) break;
|
||||
s.endStream();
|
||||
// The assistant reply stopped streaming, so the immediate turn is over:
|
||||
// close the busy/pending window so the badge does not stick. If the backend
|
||||
@@ -104,6 +110,7 @@ export function dispatchFrame(ev: { type: string; sessionId?: string; data: unkn
|
||||
break;
|
||||
}
|
||||
case "message.delta": {
|
||||
if (!streamFrameApplies(ev, r, s.activeSessionId)) break;
|
||||
if (r && typeof r.delta === "string") s.appendChunk(r.delta);
|
||||
break;
|
||||
}
|
||||
@@ -167,10 +174,14 @@ export function dispatchFrame(ev: { type: string; sessionId?: string; data: unkn
|
||||
break;
|
||||
}
|
||||
case "thinking.start": {
|
||||
// Guarded like the deltas: only the active conversation may reset the
|
||||
// accumulator (which also bounds a switched-away session's stale text).
|
||||
if (!streamFrameApplies(ev, r, s.activeSessionId)) break;
|
||||
pendingThinking = "";
|
||||
break;
|
||||
}
|
||||
case "thinking.delta": {
|
||||
if (!streamFrameApplies(ev, r, s.activeSessionId)) break;
|
||||
if (r && typeof r.delta === "string") {
|
||||
pendingThinking += r.delta;
|
||||
s.setThinking(pendingThinking);
|
||||
@@ -178,12 +189,14 @@ export function dispatchFrame(ev: { type: string; sessionId?: string; data: unkn
|
||||
break;
|
||||
}
|
||||
case "thinking.end": {
|
||||
if (!streamFrameApplies(ev, r, s.activeSessionId)) break;
|
||||
if (pendingThinking) s.setThinking(pendingThinking);
|
||||
pendingThinking = "";
|
||||
break;
|
||||
}
|
||||
case "tool_call.start":
|
||||
case "tool_call.end": {
|
||||
if (!streamFrameApplies(ev, r, s.activeSessionId)) break;
|
||||
const tool = parseToolCall(ev.data);
|
||||
if (tool) s.addTool(tool);
|
||||
break;
|
||||
@@ -194,11 +207,15 @@ export function dispatchFrame(ev: { type: string; sessionId?: string; data: unkn
|
||||
break;
|
||||
}
|
||||
case "tool_execution.update": {
|
||||
// Same card as tool_call.* — same guard.
|
||||
if (!streamFrameApplies(ev, r, s.activeSessionId)) break;
|
||||
const tool = parseToolCall(ev.data);
|
||||
if (tool) s.addTool(tool);
|
||||
break;
|
||||
}
|
||||
case "thinking": {
|
||||
// Legacy single-shot CoT frame — renders into the same bubble.
|
||||
if (!streamFrameApplies(ev, r, s.activeSessionId)) break;
|
||||
if (typeof ev.data === "string") {
|
||||
s.setThinking(ev.data);
|
||||
} else if (r && typeof r.text === "string") {
|
||||
@@ -212,6 +229,8 @@ export function dispatchFrame(ev: { type: string; sessionId?: string; data: unkn
|
||||
break;
|
||||
}
|
||||
case "tool_used": {
|
||||
// Legacy tool echo — same card as tool_call.*.
|
||||
if (!streamFrameApplies(ev, r, s.activeSessionId)) break;
|
||||
const tool = parseTool(ev.data);
|
||||
if (tool) s.addTool(tool);
|
||||
break;
|
||||
|
||||
Reference in new issue
Block a user