























@@ -1,4 +1,5 @@
11import { afterEach, describe, expect, it, vi } from "vitest";
2+import { isSessionRunActive } from "../session-run-state.ts";
23import {
34applySessionsChangedEvent,
45createSessionAndRefresh,
@@ -528,6 +529,57 @@ describe("loadSessions", () => {
528529}
529530});
530531532+it("keeps stale running session list rows idle when no live run remains", async () => {
533+const request = vi.fn(async (method: string) => {
534+if (method !== "sessions.list") {
535+throw new Error(`unexpected method: ${method}`);
536+}
537+return {
538+ts: 2,
539+path: "(multiple)",
540+count: 1,
541+defaults: { modelProvider: null, model: null, contextTokens: null },
542+sessions: [
543+{
544+key: "main",
545+kind: "direct",
546+updatedAt: 2,
547+hasActiveRun: false,
548+status: "running",
549+},
550+],
551+};
552+});
553+const state = createState(request, {
554+sessionKey: "main",
555+sessionsResult: {
556+ts: 1,
557+path: "(multiple)",
558+count: 1,
559+defaults: { modelProvider: null, model: null, contextTokens: null },
560+sessions: [
561+{
562+key: "main",
563+kind: "direct",
564+updatedAt: 1,
565+hasActiveRun: false,
566+status: "done",
567+},
568+],
569+},
570+} as Partial<SessionsState & { sessionKey: string }>);
571+572+await loadSessions(state);
573+574+const current = state.sessionsResult?.sessions[0];
575+expect(current).toMatchObject({
576+key: "main",
577+hasActiveRun: false,
578+status: "running",
579+});
580+expect(isSessionRunActive(current!)).toBe(false);
581+});
582+531583it("omits the active-window cutoff when archived sessions are shown", async () => {
532584const request = vi.fn(async (method: string) => {
533585if (method !== "sessions.list") {
@@ -1329,6 +1381,83 @@ describe("applySessionsChangedEvent", () => {
13291381expect(requestUpdate).not.toHaveBeenCalled();
13301382});
133113831384+it("keeps stale running session events idle after a local terminal reconcile", () => {
1385+const state = createState(async () => undefined, {
1386+sessionsResult: {
1387+ts: 1,
1388+path: "(multiple)",
1389+count: 1,
1390+defaults: { modelProvider: null, model: null, contextTokens: null },
1391+sessions: [
1392+{
1393+key: "agent:super:main",
1394+kind: "direct",
1395+updatedAt: 1,
1396+hasActiveRun: false,
1397+status: "done",
1398+},
1399+],
1400+},
1401+});
1402+1403+const applied = applySessionsChangedEvent(state, {
1404+sessionKey: "agent:super:main",
1405+sessionId: "sess-main",
1406+phase: "message",
1407+status: "running",
1408+updatedAt: 2,
1409+ts: 2,
1410+});
1411+1412+expect(applied).toEqual({ applied: true, change: "updated" });
1413+const current = state.sessionsResult?.sessions[0];
1414+expect(current).toMatchObject({
1415+key: "agent:super:main",
1416+hasActiveRun: false,
1417+status: "running",
1418+});
1419+expect(isSessionRunActive(current!)).toBe(false);
1420+});
1421+1422+it("revives active state when a new lifecycle start follows stale idle state", () => {
1423+const state = createState(async () => undefined, {
1424+sessionsResult: {
1425+ts: 1,
1426+path: "(multiple)",
1427+count: 1,
1428+defaults: { modelProvider: null, model: null, contextTokens: null },
1429+sessions: [
1430+{
1431+key: "agent:super:main",
1432+kind: "direct",
1433+updatedAt: 1,
1434+hasActiveRun: false,
1435+status: "done",
1436+},
1437+],
1438+},
1439+});
1440+1441+const applied = applySessionsChangedEvent(state, {
1442+sessionKey: "agent:super:main",
1443+sessionId: "sess-main",
1444+phase: "start",
1445+status: "running",
1446+startedAt: 2,
1447+updatedAt: 2,
1448+ts: 2,
1449+});
1450+1451+expect(applied).toEqual({ applied: true, change: "updated" });
1452+const current = state.sessionsResult?.sessions[0];
1453+expect(current).toMatchObject({
1454+key: "agent:super:main",
1455+hasActiveRun: true,
1456+status: "running",
1457+});
1458+expect(isSessionRunActive(current!)).toBe(true);
1459+});
1460+13321461it("updates fresh context usage from websocket event payloads", () => {
13331462const state = createState(async () => undefined, {
13341463sessionsResult: {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。