

























@@ -44,6 +44,7 @@ import {
4444createSessionEventSubscriberRegistry,
4545createSessionMessageSubscriberRegistry,
4646createToolEventRecipientRegistry,
47+type AgentEventHandlerOptions,
4748} from "./server-chat.js";
4849import { loadGatewaySessionRow } from "./server-chat.load-gateway-session-row.runtime.js";
4950import { loadSessionEntry } from "./session-utils.js";
@@ -79,13 +80,16 @@ describe("agent event handler", () => {
7980resolveSessionKeyForRun?: (runId: string) => string | undefined;
8081lifecycleErrorRetryGraceMs?: number;
8182isChatSendRunActive?: (runId: string) => boolean;
83+clearTrackedActiveRun?: AgentEventHandlerOptions["clearTrackedActiveRun"];
8284}) {
8385const nowSpy =
8486params?.now === undefined ? undefined : vi.spyOn(Date, "now").mockReturnValue(params.now);
8587const broadcast = vi.fn();
8688const broadcastToConnIds = vi.fn();
8789const nodeSendToSession = vi.fn();
8890const clearAgentRunContext = vi.fn();
91+const clearTrackedActiveRun =
92+vi.fn<NonNullable<AgentEventHandlerOptions["clearTrackedActiveRun"]>>();
8993const agentRunSeq = new Map<string, number>();
9094const chatRunState = createChatRunState();
9195const toolEventRecipients = createToolEventRecipientRegistry();
@@ -106,6 +110,7 @@ describe("agent event handler", () => {
106110loadGatewaySessionRowForSnapshot: loadGatewaySessionRow,
107111lifecycleErrorRetryGraceMs: params?.lifecycleErrorRetryGraceMs,
108112isChatSendRunActive: params?.isChatSendRunActive,
113+clearTrackedActiveRun: params?.clearTrackedActiveRun ?? clearTrackedActiveRun,
109114});
110115111116return {
@@ -114,6 +119,7 @@ describe("agent event handler", () => {
114119 broadcastToConnIds,
115120 nodeSendToSession,
116121 clearAgentRunContext,
122+ clearTrackedActiveRun,
117123 agentRunSeq,
118124 chatRunState,
119125 toolEventRecipients,
@@ -1846,6 +1852,95 @@ describe("agent event handler", () => {
18461852resetAgentRunContextForTest();
18471853});
184818541855+it("clears tracked active runs before terminal sessions.changed broadcasts", () => {
1856+vi.mocked(loadGatewaySessionRow).mockReturnValue({
1857+key: "session-finished",
1858+kind: "direct",
1859+updatedAt: 1_650,
1860+status: "running",
1861+startedAt: 900,
1862+});
1863+const {
1864+ broadcastToConnIds,
1865+ clearTrackedActiveRun,
1866+ chatRunState,
1867+ sessionEventSubscribers,
1868+ handler,
1869+} = createHarness();
1870+sessionEventSubscribers.subscribe("conn-session");
1871+chatRunState.registry.add("provider-run", {
1872+sessionKey: "session-finished",
1873+clientRunId: "client-run",
1874+});
1875+1876+handler({
1877+runId: "provider-run",
1878+seq: 2,
1879+stream: "lifecycle",
1880+ts: 1_800,
1881+data: {
1882+phase: "end",
1883+startedAt: 900,
1884+endedAt: 1_700,
1885+},
1886+});
1887+1888+expect(clearTrackedActiveRun).toHaveBeenCalledWith({
1889+runId: "provider-run",
1890+clientRunId: "client-run",
1891+sessionKey: "session-finished",
1892+});
1893+expect(requireMockArg(broadcastToConnIds, 0, 0, "sessions changed event")).toBe(
1894+"sessions.changed",
1895+);
1896+expect(clearTrackedActiveRun.mock.invocationCallOrder[0]).toBeLessThan(
1897+broadcastToConnIds.mock.invocationCallOrder[0] ?? Number.POSITIVE_INFINITY,
1898+);
1899+});
1900+1901+it("keeps chat send retry guards while hiding terminal session projection across session aliases", () => {
1902+const trackedActiveRuns = new Map<
1903+string,
1904+{ sessionKey: string; projectSessionActive?: boolean }
1905+>([
1906+["provider-run", { sessionKey: "session-finished" }],
1907+["client-run", { sessionKey: "requested-session" }],
1908+]);
1909+const { chatRunState, handler } = createHarness({
1910+clearTrackedActiveRun: ({ runId, clientRunId }) => {
1911+for (const candidateRunId of new Set([runId, clientRunId])) {
1912+const entry = trackedActiveRuns.get(candidateRunId);
1913+if (entry) {
1914+entry.projectSessionActive = false;
1915+}
1916+}
1917+},
1918+});
1919+chatRunState.registry.add("provider-run", {
1920+sessionKey: "session-finished",
1921+clientRunId: "client-run",
1922+});
1923+1924+handler({
1925+runId: "provider-run",
1926+seq: 2,
1927+stream: "lifecycle",
1928+ts: 1_800,
1929+data: {
1930+phase: "end",
1931+startedAt: 900,
1932+endedAt: 1_700,
1933+},
1934+});
1935+1936+const providerGuard = trackedActiveRuns.get("provider-run");
1937+const retryGuard = trackedActiveRuns.get("client-run");
1938+expect(providerGuard?.projectSessionActive).toBe(false);
1939+expect(retryGuard).toBeDefined();
1940+expect(retryGuard?.sessionKey).toBe("requested-session");
1941+expect(retryGuard?.projectSessionActive).toBe(false);
1942+});
1943+18491944it("keeps aborted chat run markers through terminal lifecycle cleanup", () => {
18501945const { broadcast, chatRunState, handler } = createHarness();
18511946chatRunState.registry.add("run-aborted", {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。