refactor(frontend): 拆 types.ts(871) / ws.ts::dispatchFrame(266)(纯搬移 + barrel)

执行方案 DevDocs/audit/_plan.md 阶段 0 · G0-D:
- lib/types.ts(871) → lib/types/ 11 个域文件 + 14 行 `export *` barrel(仿 lib/api.ts 先例)
  chat / ws / session / agent / flow / persona / api / admin(269) / adminWrite(176) / kook /
  notification
- lib/ws.ts(494) → 203 行入口 + lib/ws/handlers/ 8 文件;dispatchFrame(266 行) 改为按组
  分发链(7 个 handler 各持一组 case,命中 return true,未命中走原 default 兜底),
  33 个 case 标签一一守恒

机械证明:type 导出集 86 个守恒(拆后并集 diff 为空);去掉空行与 import/export 语句后
**784=784 行、排序 diff 为空**;dispatchFrame 的 case 标签集 33=33 守恒;ws.ts 的
scheduleReconnect 至文件尾与 HEAD 字节级一致。
65 个 lib/types 消费方 + 3 个 lib/ws 消费方**零改动**通过 —— tsc 0 错误正是这一点的强判据。

验证:npx tsc --noEmit 0 错误;npm run build 成功;9 个前端逻辑测试全绿(含真实驱动
拆后 dispatchFrame 的 messageSync 54 checks 与 personaSession 23 checks);
最大文件 admin.ts 269 行、最大函数 openSocket 72 行(均达标)。
This commit is contained in:
AskaEth committed 2026-10-04 10:54:06 +08:00
1 parent e9afca9b29
commit 6e0323b446
21 files changed
+1288 -1183

No files matched your search

@@ -0,0 +1,47 @@
import { streamFrameApplies } from "../../streamScope";
import type { FrameEvent, FramePayload, StoreState } from "./shared";
/** 流式正文帧处理组:stream_chunk / stream_end / message.start / message.delta /
* message.end。命中(含组内主动丢弃)返回 true,未命中返回 false 交给下一组。 */
export function handleStreamEvent(ev: FrameEvent, r: FramePayload, s: StoreState): boolean {
switch (ev.type) {
case "stream_chunk": {
if (!streamFrameApplies(ev, r, s.activeSessionId)) return true;
if (r && typeof r.delta === "string") s.appendChunk(r.delta);
return true;
}
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)) return true;
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
// keeps a task/subagent running it re-emits session_busy (design/15 §5).
s.setSessionBusy(false, 0);
if (r && typeof r.stopReason === "string") {
s.setContext({ lastAgentMode: s.context.lastAgentMode });
}
return true;
}
// Structured stream events (design/04 §1.1, aligned with OpenClaw). These
// arrive in place of the legacy stream_chunk when the loop streams events.
case "message.start": {
// The assistant bubble is already opened by useChat; nothing to do here.
return true;
}
case "message.delta": {
if (!streamFrameApplies(ev, r, s.activeSessionId)) return true;
if (r && typeof r.delta === "string") s.appendChunk(r.delta);
return true;
}
case "message.end": {
// Body-text segment closed; the turn may continue with tool calls, so we
// do NOT end the stream here (stream_end owns that).
return true;
}
default:
return false;
}
}