





















@@ -2,7 +2,13 @@
22import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
33import { MALFORMED_STREAMING_FRAGMENT_ERROR_MESSAGE } from "../shared/assistant-error-format.js";
44import { createEventHandlers } from "./tui-event-handlers.js";
5-import type { AgentEvent, BtwEvent, ChatEvent, TuiStateAccess } from "./tui-types.js";
5+import type {
6+AgentEvent,
7+BtwEvent,
8+ChatEvent,
9+SessionChangedEvent,
10+TuiStateAccess,
11+} from "./tui-types.js";
612713type MockFn = ReturnType<typeof vi.fn>;
814type HandlerChatLog = {
@@ -133,6 +139,7 @@ describe("tui-event-handlers: handleAgentEvent", () => {
133139chatLog?: HandlerChatLog;
134140btw?: HandlerBtwPresenter;
135141localMode?: boolean;
142+refreshSessionInfo?: () => Promise<void>;
136143}) => {
137144const state = makeState(params?.state);
138145const context = makeContext(state);
@@ -144,10 +151,12 @@ describe("tui-event-handlers: handleAgentEvent", () => {
144151 state,
145152localMode: params?.localMode,
146153setActivityStatus: context.setActivityStatus,
154+refreshSessionInfo: params?.refreshSessionInfo,
147155loadHistory: context.loadHistory,
148156noteLocalRunId: context.noteLocalRunId,
149157isLocalRunId: context.isLocalRunId,
150158forgetLocalRunId: context.forgetLocalRunId,
159+clearLocalRunIds: context.clearLocalRunIds,
151160isLocalBtwRunId: context.isLocalBtwRunId,
152161forgetLocalBtwRunId: context.forgetLocalBtwRunId,
153162clearLocalBtwRunIds: context.clearLocalBtwRunIds,
@@ -873,6 +882,122 @@ describe("tui-event-handlers: handleAgentEvent", () => {
873882expect(tui.requestRender).not.toHaveBeenCalled();
874883});
875884885+it("reloads the current session and clears stale run state after sessions.changed reset", () => {
886+const refreshSessionInfo = vi.fn<() => Promise<void>>(async () => undefined);
887+const {
888+ state,
889+ chatLog,
890+ btw,
891+ tui,
892+ loadHistory,
893+ setActivityStatus,
894+ noteLocalRunId,
895+ isLocalRunId,
896+ handleChatEvent,
897+ handleAgentEvent,
898+ handleSessionsChangedEvent,
899+} = createHandlersHarness({
900+state: {
901+activeChatRunId: null,
902+currentSessionId: "session-before",
903+sessionInfo: { verboseLevel: "on", updatedAt: 100 },
904+},
905+ refreshSessionInfo,
906+});
907+908+handleChatEvent({
909+runId: "run-old",
910+sessionKey: state.currentSessionKey,
911+state: "final",
912+message: { content: [{ type: "text", text: "done" }] },
913+});
914+noteLocalRunId("run-local");
915+state.activeChatRunId = "run-stale";
916+state.pendingChatRunId = "run-pending";
917+state.pendingOptimisticUserMessage = true;
918+state.pendingSubmitDraft = { runId: "run-pending", text: "pending" };
919+state.activityStatus = "streaming";
920+loadHistory.mockClear();
921+refreshSessionInfo.mockClear();
922+chatLog.startTool.mockClear();
923+btw.clear.mockClear();
924+tui.requestRender.mockClear();
925+setActivityStatus.mockClear();
926+927+handleSessionsChangedEvent({
928+sessionKey: "main",
929+reason: "reset",
930+sessionId: "session-after",
931+updatedAt: 200,
932+} satisfies SessionChangedEvent);
933+934+expect(state.activeChatRunId).toBeNull();
935+expect(state.pendingChatRunId).toBeNull();
936+expect(state.pendingSubmitDraft).toBeNull();
937+expect(state.pendingOptimisticUserMessage).toBe(false);
938+expect(state.activityStatus).toBe("idle");
939+expect(state.currentSessionId).toBe("session-after");
940+expect(state.sessionInfo.updatedAt).toBe(200);
941+expect(isLocalRunId("run-local")).toBe(false);
942+expect(setActivityStatus).toHaveBeenCalledWith("idle");
943+expect(btw.clear).toHaveBeenCalledTimes(1);
944+expect(loadHistory).toHaveBeenCalledTimes(1);
945+expect(refreshSessionInfo).not.toHaveBeenCalled();
946+expect(tui.requestRender).toHaveBeenCalledTimes(1);
947+948+handleAgentEvent({
949+runId: "run-old",
950+stream: "tool",
951+data: { phase: "start", toolCallId: "tc-old", name: "exec" },
952+});
953+954+expect(chatLog.startTool).not.toHaveBeenCalled();
955+});
956+957+it("ignores sessions.changed reset events for other sessions", () => {
958+const { state, loadHistory, setActivityStatus, handleSessionsChangedEvent } =
959+createHandlersHarness({
960+state: { activeChatRunId: "run-current", activityStatus: "streaming" },
961+});
962+963+handleSessionsChangedEvent({
964+sessionKey: "agent:other:main",
965+reason: "reset",
966+} satisfies SessionChangedEvent);
967+968+expect(state.activeChatRunId).toBe("run-current");
969+expect(state.activityStatus).toBe("streaming");
970+expect(loadHistory).not.toHaveBeenCalled();
971+expect(setActivityStatus).not.toHaveBeenCalledWith("idle");
972+});
973+974+it("ignores selected-global sessions.changed reset events from other agents", () => {
975+const { state, loadHistory, setActivityStatus, handleSessionsChangedEvent } =
976+createHandlersHarness({
977+state: {
978+agentDefaultId: "main",
979+currentAgentId: "work",
980+currentSessionKey: "global",
981+activeChatRunId: "run-current",
982+activityStatus: "streaming",
983+},
984+});
985+986+handleSessionsChangedEvent({
987+sessionKey: "global",
988+agentId: "main",
989+reason: "reset",
990+sessionId: "session-other-agent",
991+updatedAt: 300,
992+} satisfies SessionChangedEvent);
993+994+expect(state.activeChatRunId).toBe("run-current");
995+expect(state.activityStatus).toBe("streaming");
996+expect(state.currentSessionId).not.toBe("session-other-agent");
997+expect(loadHistory).not.toHaveBeenCalled();
998+expect(setActivityStatus).not.toHaveBeenCalledWith("idle");
999+});
1000+8761001it("accepts tool events after chat final for the same run", () => {
8771002const { state, chatLog, tui, handleChatEvent, handleAgentEvent } = createHandlersHarness({
8781003state: { activeChatRunId: null },
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。