



























@@ -1004,6 +1004,149 @@ describe("tui-event-handlers: streaming watchdog", () => {
10041004handlers.dispose?.();
10051005});
100610061007+it("rearms the watchdog on active-run tool events even when tool verbosity is off", () => {
1008+const { state, setActivityStatus, handlers } = createHarness({
1009+streamingWatchdogMs: 5_000,
1010+});
1011+state.sessionInfo.verboseLevel = "off";
1012+1013+handlers.handleChatEvent({
1014+runId: "run-tools",
1015+sessionKey: state.currentSessionKey,
1016+state: "delta",
1017+message: { content: "first" },
1018+} satisfies ChatEvent);
1019+1020+vi.advanceTimersByTime(3_000);
1021+1022+handlers.handleAgentEvent({
1023+runId: "run-tools",
1024+stream: "tool",
1025+data: { phase: "start", toolCallId: "tool-1", name: "read" },
1026+} satisfies AgentEvent);
1027+1028+vi.advanceTimersByTime(3_000);
1029+1030+expect(setActivityStatus).not.toHaveBeenCalledWith("idle");
1031+expect(state.activeChatRunId).toBe("run-tools");
1032+1033+vi.advanceTimersByTime(2_001);
1034+1035+expect(setActivityStatus).toHaveBeenLastCalledWith("idle");
1036+expect(state.activeChatRunId).toBeNull();
1037+1038+handlers.dispose?.();
1039+});
1040+1041+it("pauses the watchdog while disconnected and rearms it on reconnect without clearing the active run", () => {
1042+const { state, setActivityStatus, loadHistory, handlers } = createHarness({
1043+streamingWatchdogMs: 5_000,
1044+});
1045+1046+handlers.handleChatEvent({
1047+runId: "run-reconnect",
1048+sessionKey: state.currentSessionKey,
1049+state: "delta",
1050+message: { content: "hello" },
1051+} satisfies ChatEvent);
1052+1053+handlers.pauseStreamingWatchdog();
1054+vi.advanceTimersByTime(10_000);
1055+1056+expect(state.activeChatRunId).toBe("run-reconnect");
1057+expect(setActivityStatus).not.toHaveBeenCalledWith("idle");
1058+1059+handlers.reconnectStreamingWatchdog();
1060+1061+expect(setActivityStatus).toHaveBeenCalledWith("streaming");
1062+expect(state.activeChatRunId).toBe("run-reconnect");
1063+1064+vi.advanceTimersByTime(5_001);
1065+1066+expect(setActivityStatus).toHaveBeenLastCalledWith("idle");
1067+expect(state.activeChatRunId).toBeNull();
1068+expect(loadHistory).toHaveBeenCalledTimes(1);
1069+1070+handlers.dispose?.();
1071+});
1072+1073+it("reloads history only once when reconnect recovery and deferred history refresh overlap", () => {
1074+const { state, loadHistory, noteLocalRunId, handlers } = createHarness({
1075+streamingWatchdogMs: 5_000,
1076+});
1077+1078+handlers.handleChatEvent({
1079+runId: "run-reconnect",
1080+sessionKey: state.currentSessionKey,
1081+state: "delta",
1082+message: { content: "hello" },
1083+} satisfies ChatEvent);
1084+1085+noteLocalRunId("run-local-empty");
1086+handlers.handleChatEvent({
1087+runId: "run-local-empty",
1088+sessionKey: state.currentSessionKey,
1089+state: "final",
1090+} satisfies ChatEvent);
1091+1092+handlers.pauseStreamingWatchdog();
1093+handlers.reconnectStreamingWatchdog();
1094+vi.advanceTimersByTime(5_001);
1095+1096+expect(loadHistory).toHaveBeenCalledTimes(1);
1097+1098+handlers.dispose?.();
1099+});
1100+1101+it("resets to idle when reconnect drops an active run that is no longer tracked", () => {
1102+const { state, setActivityStatus, handlers } = createHarness({
1103+streamingWatchdogMs: 5_000,
1104+});
1105+state.activeChatRunId = "run-stale";
1106+state.activityStatus = "streaming";
1107+1108+handlers.reconnectStreamingWatchdog();
1109+1110+expect(state.activeChatRunId).toBeNull();
1111+expect(state.activityStatus).toBe("idle");
1112+expect(setActivityStatus).toHaveBeenLastCalledWith("idle");
1113+1114+handlers.dispose?.();
1115+});
1116+1117+it("keeps reconnect recovery armed when only terminal lifecycle arrives after reconnect", () => {
1118+const { state, chatLog, setActivityStatus, loadHistory, handlers } = createHarness({
1119+streamingWatchdogMs: 5_000,
1120+});
1121+1122+handlers.handleChatEvent({
1123+runId: "run-lifecycle-only",
1124+sessionKey: state.currentSessionKey,
1125+state: "delta",
1126+message: { content: "hello" },
1127+} satisfies ChatEvent);
1128+1129+handlers.pauseStreamingWatchdog();
1130+handlers.reconnectStreamingWatchdog();
1131+1132+handlers.handleAgentEvent({
1133+runId: "run-lifecycle-only",
1134+stream: "lifecycle",
1135+data: { phase: "end" },
1136+} satisfies AgentEvent);
1137+1138+vi.advanceTimersByTime(5_001);
1139+1140+expect(setActivityStatus).toHaveBeenLastCalledWith("idle");
1141+expect(state.activeChatRunId).toBeNull();
1142+expect(loadHistory).toHaveBeenCalledTimes(1);
1143+expect(chatLog.addSystem).not.toHaveBeenCalledWith(
1144+expect.stringContaining("streaming watchdog"),
1145+);
1146+1147+handlers.dispose?.();
1148+});
1149+10071150it("cancels the watchdog when the run finalizes normally", () => {
10081151const { state, chatLog, setActivityStatus, handlers } = createHarness({
10091152streamingWatchdogMs: 5_000,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。