diff --git a/ui/src/ui/app-gateway.sessions.node.test.ts b/ui/src/ui/app-gateway.sessions.node.test.ts index c4d2a45f1da..b9382a62bcc 100644 --- a/ui/src/ui/app-gateway.sessions.node.test.ts +++ b/ui/src/ui/app-gateway.sessions.node.test.ts @@ -150,7 +150,7 @@ describe("handleGatewayEvent sessions.changed", () => { expect(loadSessionsMock).not.toHaveBeenCalled(); }); - it("reloads sessions when an applied message-phase event inserts a session row", () => { + it("does not reload sessions when a message-phase event inserts a session row", () => { loadSessionsMock.mockReset(); applySessionsChangedEventMock .mockReset() @@ -170,11 +170,10 @@ describe("handleGatewayEvent sessions.changed", () => { }); expect(applySessionsChangedEventMock).toHaveBeenCalledTimes(1); - expect(loadSessionsMock).toHaveBeenCalledTimes(1); - expect(loadSessionsMock).toHaveBeenCalledWith(host); + expect(loadSessionsMock).not.toHaveBeenCalled(); }); - it("reloads sessions when a message-phase event cannot patch local state", () => { + it("does not reload sessions when a message-phase event cannot patch local state", () => { loadSessionsMock.mockReset(); applySessionsChangedEventMock.mockReset().mockReturnValue({ applied: false }); const host = createHost(); @@ -186,8 +185,39 @@ describe("handleGatewayEvent sessions.changed", () => { seq: 1, }); - expect(loadSessionsMock).toHaveBeenCalledTimes(1); - expect(loadSessionsMock).toHaveBeenCalledWith(host); + expect(loadSessionsMock).not.toHaveBeenCalled(); + }); + + it("does not reload sessions for chat lifecycle events", () => { + loadSessionsMock.mockReset(); + applySessionsChangedEventMock.mockReset().mockReturnValue({ applied: true, change: "updated" }); + const host = createHost(); + + handleGatewayEvent(host, { + type: "event", + event: "sessions.changed", + payload: { sessionKey: "agent:main:main", phase: "start", runId: "run-1" }, + seq: 1, + }); + + expect(applySessionsChangedEventMock).toHaveBeenCalledTimes(1); + expect(loadSessionsMock).not.toHaveBeenCalled(); + }); + + it("does not reload sessions for chat send acknowledgement events", () => { + loadSessionsMock.mockReset(); + applySessionsChangedEventMock.mockReset().mockReturnValue({ applied: true, change: "updated" }); + const host = createHost(); + + handleGatewayEvent(host, { + type: "event", + event: "sessions.changed", + payload: { sessionKey: "agent:main:main", reason: "send" }, + seq: 1, + }); + + expect(applySessionsChangedEventMock).toHaveBeenCalledTimes(1); + expect(loadSessionsMock).not.toHaveBeenCalled(); }); }); diff --git a/ui/src/ui/app-gateway.ts b/ui/src/ui/app-gateway.ts index eafddc934c2..7257bc60cc5 100644 --- a/ui/src/ui/app-gateway.ts +++ b/ui/src/ui/app-gateway.ts @@ -156,12 +156,18 @@ function isTerminalChatState( return state === "final" || state === "aborted" || state === "error"; } -function isSessionMessagePhasePayload(payload: unknown): boolean { +function isChatTurnSessionChangedPayload(payload: unknown): boolean { + if (!payload || typeof payload !== "object" || Array.isArray(payload)) { + return false; + } + const record = payload as { phase?: unknown; reason?: unknown }; return ( - Boolean(payload) && - typeof payload === "object" && - !Array.isArray(payload) && - (payload as { phase?: unknown }).phase === "message" + record.phase === "start" || + record.phase === "message" || + record.phase === "end" || + record.phase === "error" || + record.reason === "send" || + record.reason === "steer" ); } @@ -749,12 +755,8 @@ function handleGatewayEventUnsafe(host: GatewayHost, evt: GatewayEventFrame) { } if (evt.event === "sessions.changed") { - const applyResult = applySessionsChangedEvent(host as unknown as SessionsState, evt.payload); - if ( - applyResult.applied && - applyResult.change === "updated" && - isSessionMessagePhasePayload(evt.payload) - ) { + applySessionsChangedEvent(host as unknown as SessionsState, evt.payload); + if (isChatTurnSessionChangedPayload(evt.payload)) { return; } void loadSessions(host as unknown as SessionsState);